Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reset Hibernate sequence generators?

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:

  1. Test1: Create 8 test objects (Hibernate has id values 9 and 10 still allocated)
  2. Recreate database
  3. 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?

like image 557
GreenTurtle Avatar asked Apr 24 '17 11:04

GreenTurtle


People also ask

How does hibernate sequence generator work?

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.

What is allocationSize in sequence generator?

allocationSize. (Optional) The amount to increment by when allocating sequence numbers from the sequence.

What is sequence hibernate?

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.

What does a sequence generator do?

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.


3 Answers

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

  • you get sequence generators from first

Cons

  • It takes a few more seconds to all JUNIT testcases

Update

Based on comment another way is to reset sequence in every JUNIT test in @Before method

ALTER SEQUENCE Test.sequence RESTART WITH 1
like image 169
rajadilipkolli Avatar answered Oct 20 '22 15:10

rajadilipkolli


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!

like image 5
OleG Avatar answered Oct 20 '22 17:10

OleG


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();
}
like image 4
Pilosa Avatar answered Oct 20 '22 15:10

Pilosa