我使用Hibernate 3.5.6-Final与Oracle数据库进行生产,并使用H2数据库进行集成测试。用于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;
在每次JUnit集成测试之前,我将使用
从数据库中删除所有数据new SchemaExport(configuration).create(false, true);
一切正常,直到我增加allocationSize以生成序列。将此提高到例如在插入测试数据时,10将使用UniqueKeyConstraintViolations打破几个测试。
例如:
所以我的问题:有没有办法在每次测试之前重置Hibernates分配的ID?
增加: 我没有使用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());
configuration.addResource("NamedQueries.hbm.xml");
for (Class annotatedClass : getAnnotatedClasses()) {
configuration.addAnnotatedClass(annotatedClass);
}
configuration.setListener("post-load", new PreloadEventListener());
return configuration;
}
我是否真的需要重新加载我的整个Spring上下文以重置Hibernate ID生成器?
答案 0 :(得分:3)
我遇到了同样的问题,并没有找到一种内置的方法来做到这一点。我们正在使用hibernate 4.2.7。
深入调试到hibernate后,我最终扩展了序列生成器。我们使用标准序列生成器创建实体:
@SequenceGenerator(name = "SomeSeq", sequenceName = "DB_SEQ", allocationSize = 50)
Hibernate为每个实体创建一个org.hibernate.id.SequenceHiLoGenerator。 SequnceHiLoGenerator委托给OptimizerFactory.LegacyHiLoAlgorithmOptimizer实例。
为了重置序列计数器并强制与数据库序列同步,您必须重置LegacyHiLoAlgorithmOptimizer中的内部变量。不幸的是,这些变量是私有的,不可修改。我试图找到一种使用继承的方法,但我没有找到一个优雅的解决方案。最后我是 创建了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;
}
[....]
修改实体以使用自定义生成器:
@GenericGenerator(name = "SomeSeq", strategy = "yourpackage.ResetableIdGenerator", parameters = {
@Parameter(name = "sequence", value = "DB_SEQ"), @Parameter(name = "max_lo", value = "49") })
在测试之间重置序列生成器(@before或@after):
// reset Hibernate Sequences
ResetableIdGenerator.cycle++;
我知道这不是一个好的解决方案 - 这是一个黑客攻击。但它有效,也许有助于找到更好的解决方案。
编辑20170504:我的初始帖子包含一个错误:参数&#34; sequenceName&#34;和&#34; allocationSize&#34;是JPA参数。 GenericGenerator来自hibernate。 而不是&#34; sequenceName&#34;你必须使用&#34; sequence&#34;而不是&#34; allocationSize&#34;你必须使用&#34; max_lo&#34;并将其设置为allocationSize-1。 我更新了代码示例。遗憾!
答案 1 :(得分:1)
或许其中一个解决方案是在新的sessionfactory中运行每个JUNIT测试。所以使用@Before
和@After
赞成
缺点
<强>更新强>
根据评论,另一种方法是在@Before方法
中的每个JUNIT测试中重置序列ALTER SEQUENCE Test.sequence RESTART WITH 1
答案 2 :(得分:1)
今天我遇到了同样的问题。由于找不到其他解决方案,因此我尝试从OleG获得该解决方案。不幸的是,与此同时org.hibernate.id.SequenceGenerator
被标记为已弃用。因此,我使用了org.hibernate.id.enhanced.SequenceStyleGenerator
。如果其他人需要它,这是我的定制解决方案:
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++;
}
}
如OleG的文章所述,在ResettableSequenceStyleGenerator
批注中将GenericGenerator
设置为策略:
@GenericGenerator(name = "SomeSeq", strategy = "yourpackage.ResettableSequenceStyleGenerator", parameters = ...)
然后在我的IntegrationTest类中,在每个测试方法之前重置序列:
@Before
public void resetSequences() {
ResettableSequenceStyleGenerator.resetAllInstances();
}
答案 3 :(得分:0)
这将强制hibernate重新加载其ID生成器:
myEntityManagerFactory.close();
myEntityManagerFactory = Persistence.createEntityManagerFactory(...);
答案 4 :(得分:0)
伙计们。就我而言,我不想使用自定义类更改模型生成器,因为序列重置对于集成测试是必要的,并且为测试调整代码并不是很好。 我费了好大劲才找到解决办法,终于找到了。首先,我在data.sql 中创建了一个名为truncate_tables_and_sequences 的函数。此函数截断所有表并使用 EXECUTE 'ALTER SEQUENCE ' || 重置所有序列quote_ident(stmt.sequencename) || '从 1 开始'
然后,在我有一个用于 JUnit 测试的自定义侦听器之后,该侦听器在每次测试运行之前执行,并且此侦听器运行我的 PostgresTestDataInitializer,它重置所有实体中所有 SequenceStyleGenerator 的值。反射用于重置计数器。不理想,但对于集成测试来说还可以。
这是代码示例:
@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();
}
}