You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate多租户模式下Envers审计事务提交报错求助

Hibernate多租户+Envers审计事务提交失败:Negative revision numbers异常排查

问题场景

项目迁移至Hibernate多租户特性,通过以下方式注入EntityManager:

@Inject
protected EntityManager em;

对应的生产者方法:

@Produces
@Default
@RequestScoped
public EntityManager create() {
 String principalName = principal.getName();
 String clientIdentifier = getClientIdentifier(principalName);
 MultiTenantResolver tenantResolver = (MultiTenantResolver) ((SessionFactoryImplementor) entityManagerFactory).getCurrentTenantIdentifierResolver();
 tenantResolver.setTenantIdentifier(clientIdentifier);
 EntityManager entityManager = entityManagerFactory.createEntityManager();
 logger.debug("=========== created entity manager " + entityManager);
 return entityManager;
}

EntityManager创建正常,能为每个请求连接到对应租户数据库,但执行包含@Audited实体的事务时,提交失败,抛出异常:

Caused by: org.hibernate.envers.exception.AuditException: Negative revision numbers are not allowed
    at org.hibernate@6.2.6.Final//org.hibernate.envers.internal.revisioninfo.DefaultRevisionInfoGenerator.saveRevisionData(DefaultRevisionInfoGenerator.java:64)
    at org.hibernate@6.2.6.Final//org.hibernate.envers.internal.synchronization.AuditProcess.getCurrentRevisionData(AuditProcess.java:138)
    at org.hibernate@6.2.6.Final//org.hibernate.envers.internal.synchronization.AuditProcess.executeInSession(AuditProcess.java:115)
    at org.hibernate@6.2.6.Final//org.hibernate.envers.internal.synchronization.AuditProcess.doBeforeTransactionCompletion(AuditProcess.java:175)
    at org.hibernate@6.2.6.Final//org.hibernate.envers.internal.synchronization.AuditProcessManager$1.doBeforeTransactionCompletion(AuditProcessManager.java:47)
    at org.hibernate@6.2.6.Final//org.hibernate.engine.spi.ActionQueue$BeforeTransactionCompletionProcessQueue.beforeTransactionCompletion(ActionQueue.java:1010)
    at org.hibernate@6.2.6.Final//org.hibernate.engine.spi.ActionQueue.beforeTransactionCompletion(ActionQueue.java:548)
    at org.hibernate@6.2.6.Final//org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:1967)
    at org.hibernate@6.2.6.Final//org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:439)
    at org.hibernate@6.2.6.Final//org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl.beforeCompletion(JtaTransactionCoordinatorImpl.java:336)
    at org.hibernate@6.2.6.Final//org.hibernate.resource.transaction.backend.jta.internal.synchronization.SynchronizationCallbackCoordinatorNonTrackingImpl.beforeCompletion(SynchronizationCallbackCoordinatorNonTrackingImpl.java:47)
    at org.hibernate@6.2.6.Final//org.hibernate.resource.transaction.backend.jta.internal.synchronization.RegisteredSynchronization.beforeCompletion(RegisteredSynchronization.java:37)
    at org.jboss.as.transactions@29.0.1.Final//org.jboss.as.txn.service.internal.tsr.JCAOrderedLastSynchronizationList.beforeCompletion(JCAOrderedLastSynchronizationList.java:113)
    at org.wildfly.transaction.client@3.0.0.Final//org.wildfly.transaction.client.AbstractTransaction.performConsumer(AbstractTransaction.java:236)
    at org.wildfly.transaction.client@3.0.0.Final//org.wildfly.transaction.client.AbstractTransaction.performConsumer(AbstractTransaction.java:247)
    at org.wildfly.transaction.client@3.0.0.Final//org.wildfly.transaction.client.AbstractTransaction$AssociatingSynchronization.beforeCompletion(AbstractTransaction.java:292)
    at org.jboss.jts//com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple.beforeCompletion(SynchronizationImple.java:76)
    at org.jboss.jts//com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.beforeCompletion(TwoPhaseCoordinator.java:360)
    at org.jboss.jts//com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator.end(TwoPhaseCoordinator.java:91)
    at org.jboss.jts//com.arjuna.ats.arjuna.AtomicAction.commit(AtomicAction.java:162)
    at org.jboss.jts//com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple.commitAndDisassociate(TransactionImple.java:1295)
    ... 125 more

排查方向

  • 修正租户标识符的传递方式:当前代码直接修改全局CurrentTenantIdentifierResolver的租户ID,存在线程安全问题,会导致多请求租户串用。正确做法是在创建EntityManager时通过参数传递租户ID:
    Map<String, Object> props = new HashMap<>();
    props.put(AvailableSettings.MULTI_TENANT_IDENTIFIER, clientIdentifier);
    EntityManager entityManager = entityManagerFactory.createEntityManager(props);
    
    CurrentTenantIdentifierResolver的设计目的是自动解析当前租户(比如从线程上下文、请求头获取),而非手动设置全局值。
  • 检查租户数据库的Revision表序列:默认的REVINFO表(Envers审计版本表)主键使用自增或序列生成。若某个租户库的序列未初始化、被重置为负数,或自增字段起始值错误,会导致生成负的revision号。直接查询租户库的REVINFO表,确认主键字段的生成策略及当前序列值。
  • 验证Envers与多租户的兼容性:确认使用的Hibernate 6.2.6版本中,Envers对当前多租户模式(数据库隔离/ schema隔离)的支持是否正常。若为schema隔离模式,需确保Envers正确使用租户对应的schema;若为数据库隔离,需确认每个租户库都已创建Envers所需的审计表。
  • 检查线程上下文与租户绑定:排查是否存在请求线程复用(如线程池)导致租户上下文残留的情况,确保每个请求的EntityManager都绑定了正确的租户ID,且事务过程中租户上下文未丢失。
  • 启用详细日志排查:开启org.hibernate.envers和org.hibernate.multitenancy的DEBUG级别日志,观察事务提交时Envers使用的租户ID、Revision数据的保存过程、ID生成的具体步骤,定位异常发生时的上下文信息。

内容的提问来源于stack exchange,提问作者jmiguel77

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 01:30:57