Issue
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:
- Test1: Create 8 test objects (Hibernate has id values 9 and 10 still allocated)
- Recreate database
- Test2: Create 12 test objects (Hibernate uses 9 and 10 for IDs, then loads new IDs from database sequence which was meanwhile reseted (1-10) and fails when trying to insert a 2nd entity with ID 9)
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?
Solution
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!
Answered By - OleG