简体   繁体   English

如何重置 Hibernate 序列生成器?

[英]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.我将 Hibernate 3.5.6-Final 与用于生产的 Oracle 数据库和用于集成测试的 H2 数据库一起使用。 The Hibernate mapping for ID creation looks like this with every entity extending EasyPersistentObject: ID 创建的 Hibernate 映射看起来像这样,每个实体都扩展了 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在每次 JUnit 集成测试之前,我都会从数据库中删除所有数据

new SchemaExport(configuration).create(false, true);

Everything works fine until I increment the allocationSize for sequence generation.一切正常,直到我为序列生成增加了 allocationSize。 Raising this to eg 10 will break several tests with UniqueKeyConstraintViolations when inserting test data.将其提高到例如 10 将在插入测试数据时使用 UniqueKeyConstraintViolations 破坏多个测试。

For example:例如:

  1. Test1: Create 8 test objects (Hibernate has id values 9 and 10 still allocated) Test1:创建 8 个测试对象(Hibernate 仍然分配了 id 值 9 和 10)
  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) Test2:创建 12 个测试对象(Hibernate 使用 9 和 10 作为 ID,然后从同时重置(1-10)的数据库序列中加载新 ID,并在尝试插入 ID 为 9 的第二个实体时失败)

So my question: Is there a way to reset Hibernates allocated IDs before each test as well?所以我的问题有没有办法在每次测试之前重置 Hibernates 分配的 ID?

Addition: I am not using PersistenceManager from JPA but pure Hibernate SessionFactory which is created like this:另外:我没有使用 JPA 中的 PersistenceManager,而是使用纯 Hibernate SessionFactory,它是这样创建的:

@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?我真的需要重新加载整个 Spring 上下文才能重置 Hibernate ID 生成器吗?

Perhaps one of the solution is to run each JUNIT test in new sessionfactory.也许解决方案之一是在新的 sessionfactory 中运行每个 JUNIT 测试。 so open and close session factory using @Before and @After所以使用@Before@After打开和关闭会话工厂

Pros优点

  • you get sequence generators from first你首先得到序列生成器

Cons缺点

  • It takes a few more seconds to all JUNIT testcases所有 JUNIT 测试用例都需要几秒钟的时间

Update更新

Based on comment another way is to reset sequence in every JUNIT test in @Before method根据评论,另一种方法是在 @Before 方法中的每个 JUNIT 测试中重置序列

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.我们正在使用休眠 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. Hibernate 为每个实体创建一个 org.hibernate.id.SequenceHiLoGenerator。 The SequnceHiLoGenerator delegates to an OptimizerFactory.LegacyHiLoAlgorithmOptimizer instance. SequnceHiLoGenerator 委托给 OptimizerFactory.LegacyHiLoAlgorithmOptimizer 实例。

In order to reset the sequence counters and force syncing with the database sequence you have to reset internal variables in the LegacyHiLoAlgorithmOptimizer.为了重置序列计数器并强制与数据库序列同步,您必须重置 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:创建了 SequenceHiLoGenerator 的源副本,并使用简单的重置功能对其进行了扩展:

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):在测试之间重置序列生成器(@before 或 @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.编辑 20170504:我的初始帖子包含一个错误:参数“sequenceName”和“allocationSize”是 JPA 参数。 The GenericGenerator is from hibernate. GenericGenerator 来自休眠。 Instead of "sequenceName" you have to use "sequence", instead of "allocationSize" you have to use "max_lo" and set it to allocationSize-1.您必须使用“sequence”而不是“sequenceName”,而不是“allocationSize”,您必须使用“max_lo”并将其设置为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.由于找不到其他解决方案,我尝试从OleG那里获取解决方案。 Unfortunately meanwhile org.hibernate.id.SequenceGenerator is marked deprecated.不幸的是,同时org.hibernate.id.SequenceGenerator被标记为已弃用。 Therefore, I used the org.hibernate.id.enhanced.SequenceStyleGenerator .因此,我使用了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;

    @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);
            instanceCycle = cycle;
        }

        return super.generate(session, object);
    }

    @Override
    public void registerExportables(Database database) {

        registerExportables_database = database;

        super.registerExportables(database);
    }

    public static void resetAllInstances() {
        cycle++;
    }
}

Set the ResettableSequenceStyleGenerator as strategy in the GenericGenerator annotation as described in OleG's article:如 OleG 的文章所述,在GenericGenerator注释中将ResettableSequenceStyleGenerator设置为策略:

@GenericGenerator(name = "SomeSeq", strategy = "yourpackage.ResettableSequenceStyleGenerator", parameters = ...)

In my IntegrationTest class I then reset the sequences before each test method:在我的 IntegrationTest 类中,我然后在每个测试方法之前重置序列:

@Before
public void resetSequences() {
    ResettableSequenceStyleGenerator.resetAllInstances();
}

This will force hibernate to reload its ID generators:这将强制休眠重新加载其 ID 生成器:

myEntityManagerFactory.close(); myEntityManagerFactory.close();

myEntityManagerFactory = Persistence.createEntityManagerFactory(...); myEntityManagerFactory = Persistence.createEntityManagerFactory(...);

guys.伙计们。 In my case, I didn't want to change model generators with custom classes because sequence reset was necessary for integration tests and it's not very good to adapt the code for tests.就我而言,我不想使用自定义类更改模型生成器,因为序列重置对于集成测试来说是必要的,并且为测试调整代码并不是很好。 I struggled a lot to find a solution and finally, it was found.我费了好大劲才找到解决办法,终于找到了。 First of all, I created a function in data.sql with the name truncate_tables_and_sequences name.首先,我在 data.sql 中创建了一个名为truncate_tables_and_sequences的函数。 This function truncates all the tables and resets all the sequences with EXECUTE 'ALTER SEQUENCE ' ||此函数截断所有表并使用EXECUTE 'ALTER SEQUENCE ' ||重置所有序列quote_ident(stmt.sequencename) || quote_ident(stmt.sequencename) || ' RESTART WITH 1' '从 1 开始'

Then after I have a custom listener for JUnit tests that is executed before each test run and this listener runs my PostgresTestDataInitializer that resets values for all SequenceStyleGenerator in all entities.然后在我有一个在每次测试运行之前执行的 JUnit 测试的自定义侦听器之后,这个侦听器运行我的PostgresTestDataInitializer ,它重置所有实体中所有 SequenceStyleGenerator 的值。 Reflection is used to reset the counter.反射用于重置计数器。 Not ideal, but for integration tests it's ok.不理想,但对于集成测试来说还可以。

This is the code sample:这是代码示例:

@Component
public class PostgresTestDataInitializer implements TestDataInitializer {

    private final TransactionUtils transactionUtils;

    private final EntityManager entityManager;

    @Value("${spring.datasource.username:postgres}")
    private String databaseUserName;

    public PostgresTestDataInitializer(TransactionUtils transactionUtils,
                                       EntityManager entityManager) {
        this.transactionUtils = transactionUtils;
        this.entityManager = entityManager;
    }

    public void resetAllDbData() throws Exception {
        transactionUtils.executeInNewTransaction(this::clearAllDataAndSequences);
        resetSequenceGenerators();
    }

    private void resetSequenceGenerators() throws IllegalAccessException {
        MetamodelImpl metamodel = (MetamodelImpl)entityManager.getMetamodel();
        for(EntityPersister entityPersister : metamodel.entityPersisters().values()) {
            if (entityPersister.hasIdentifierProperty()
                    && entityPersister.getIdentifierGenerator() != null
                    && entityPersister.getIdentifierGenerator() instanceof SequenceStyleGenerator sequenceStyleGenerator
                    && sequenceStyleGenerator.getOptimizer() instanceof PooledOptimizer pooledOptimizer) {

                Field noTenantStateField = ReflectionUtils.findField(PooledOptimizer.class, "noTenantState");
                noTenantStateField.setAccessible(true);
                noTenantStateField.set(pooledOptimizer, null);
            }
        }
    }

    private void clearAllDataAndSequences() {
        entityManager
                .createStoredProcedureQuery("truncate_tables_and_sequences")
                .registerStoredProcedureParameter("username", String.class, ParameterMode.IN)
                .setParameter("username", databaseUserName)
                .execute();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM