I'm using Hibernate 3.5.6-Final with an Oracle database for production and a H2 database for integration tests. The Hibernate mapping for ID creation looks like this with every entity extending EasyPersistentObject:
@MappedSuperclass
public class EasyPersistentObject implements Serializable {
@Id
@SequenceGenerator(name = "hibernate_seq", sequenceName = "hibernate_id_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "hibernate_seq")
protected Integer id;
Before each JUnit integration test I am removing all data from the database with
new SchemaExport(configuration).create(false, true);
Everything works fine until I increment the allocationSize for sequence generation. Raising this to e.g. 10 will break several tests with UniqueKeyConstraintViolations when inserting test data.
For example:
So my question: Is there a way to reset Hibernates allocated IDs before each test as well?
Addition: I am not using PersistenceManager from JPA but pure Hibernate SessionFactory which is created like this:
@Bean(name = "easySF")
public SessionFactory easySessionFactory(@Qualifier("easyConfig") AnnotationConfiguration configuration) {
configuration.setInterceptor(new HibernateInterceptor());
return configuration.buildSessionFactory();
}
@Bean(name = "easyConfig")
protected AnnotationConfiguration easyHibernateConfiguration() {
AnnotationConfiguration configuration = new AnnotationConfiguration();
configuration.setProperties(createHibernateProperties());
for (Class annotatedClass : getAnnotatedClasses()) {
configuration.addAnnotatedClass(annotatedClass);
}
return configuration;
}
Do I really need to reload my whole Spring context to get the Hibernate ID generators reseted?
IDENTITY: Hibernate relies on an auto-incremented database column to generate the primary key, SEQUENCE: Hibernate requests the primary key value from a database sequence, TABLE: Hibernate uses a database table to simulate a sequence.
allocationSize. (Optional) The amount to increment by when allocating sequence numbers from the sequence.
SEQUENCE is the generation type recommended by the Hibernate documentation. The generated values are unique per sequence. If we don't specify a sequence name, Hibernate will reuse the same hibernate_sequence for different types.
The sequence generator is used to generate primary key values & it's used to generate numeric sequence values like 1, 2, 3, 4, 5 etc. For example, you want to assign sequence values to the source records, then you can use sequence generator.
Perhaps one of the solution is to run each JUNIT test in new sessionfactory. so open and close session factory using @Before
and @After
Pros
Cons
Update
Based on comment another way is to reset sequence in every JUNIT test in @Before method
ALTER SEQUENCE Test.sequence RESTART WITH 1
I was facing the same Problem and didn't find an inbuild way to do it. We're using hibernate 4.2.7.
After debugging deeply into hibernate I ended up extending the sequence generator. We create entities using the standard sequence generator:
@SequenceGenerator(name = "SomeSeq", sequenceName = "DB_SEQ", allocationSize = 50)
Hibernate creates an org.hibernate.id.SequenceHiLoGenerator for each entity. The SequnceHiLoGenerator delegates to an OptimizerFactory.LegacyHiLoAlgorithmOptimizer instance.
In order to reset the sequence counters and force syncing with the database sequence you have to reset internal variables in the LegacyHiLoAlgorithmOptimizer. Unfortunately these variables are private and not modifyable. I tried to find a way using inheritance but I didn't find an elegant solution. Finally I
created a source copy of the SequenceHiLoGenerator and extended it with a simple reset functionality:
public class ResetableIdGenerator extends SequenceGenerator {
public static int cycle = 0; // global, indicating current test cycle
protected int startingCycle = 0; // instance, indicating the test cycle the LegacyHiLoAlgorithmOptimizer was used the last time
[...]
public synchronized Serializable generate(final SessionImplementor session, Object obj) {
// create a new HiLoOptimizer if there's a new test cycle
if (startingCycle < cycle) {
hiloOptimizer = new OptimizerFactory.LegacyHiLoAlgorithmOptimizer(getIdentifierType().getReturnedClass(),
maxLo);
startingCycle = cycle;
}
[....]
Modify the entities to use the custom generator:
@GenericGenerator(name = "SomeSeq", strategy = "yourpackage.ResetableIdGenerator", parameters = {
@Parameter(name = "sequence", value = "DB_SEQ"), @Parameter(name = "max_lo", value = "49") })
Reset the sequence generator inbetween your test (@before or @after):
// reset Hibernate Sequences
ResetableIdGenerator.cycle++;
I know this isn't a good solution - It's a hack. But it works and maybe it helps to find a better solution.
EDIT 20170504: My initial post contained a mistake: The parameters "sequenceName" and "allocationSize" are JPA parameters. The GenericGenerator is from hibernate. Instead of "sequenceName" you have to use "sequence", instead of "allocationSize" you have to use "max_lo" and set it to allocationSize-1. I updated the code example. Sorry!
Today I faced the same problem. Since I couldn't find another solution, I tried to take the solution from OleG. Unfortunately meanwhile org.hibernate.id.SequenceGenerator
is marked deprecated. Therefore, I used the org.hibernate.id.enhanced.SequenceStyleGenerator
. If anyone else needs it, here is my customized solution:
public class ResettableSequenceStyleGenerator extends SequenceStyleGenerator {
private static int cycle = 0;
private int instanceCycle = cycle;
private Type configure_type = null;
private Properties configure_params = null;
private ServiceRegistry configure_serviceRegistry = null;
private Database registerExportables_database = null;
private SqlStringGenerationContext initialize_context = null;
@Override
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
configure_type = type;
configure_params = params;
configure_serviceRegistry = serviceRegistry;
super.configure(type, params, serviceRegistry);
}
@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
if (instanceCycle != cycle) {
super.configure(configure_type, configure_params, configure_serviceRegistry);
super.registerExportables(registerExportables_database);
super.initialize(initialize_context);
instanceCycle = cycle;
}
return super.generate(session, object);
}
@Override
public void registerExportables(Database database) {
registerExportables_database = database;
super.registerExportables(database);
}
@Override
public void initialize(SqlStringGenerationContext context) {
initialize_context = context;
super.initialize(context);
}
public static void resetAllInstances() {
cycle++;
}
}
Set the ResettableSequenceStyleGenerator
as strategy in the GenericGenerator
annotation as described in OleG's article:
@GenericGenerator(name = "SomeSeq", strategy = "yourpackage.ResettableSequenceStyleGenerator", parameters = ...)
In my IntegrationTest class I then reset the sequences before each test method:
@Before
public void resetSequences() {
ResettableSequenceStyleGenerator.resetAllInstances();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With