Hibernate的多租户功能:用于ID生成的序列混乱了

后端开发 2026-07-08

看起来Hibernate似乎把来自分离租户的序列中缓存的ID值混在一起了。我无法找到问题所在或复现,但在生产环境中会出现序列的下一个值小于表中最大id的情况。例如:最大id = 100,但seq.next_value = 90。这样最终会导致id已存在的异常。

技术栈:

  • Spring Boot 3.5.13
  • Hibernate 6.6.45
  • MariaDB 10.6.23

使用Spring的 AbstractRoutingDataSource配置:

public class RoutingDataSource extends AbstractRoutingDataSource { ... }
@Bean
@Primary
protected DataSource dataSource(TenantService tenantService, DeploymentMutex deploymentMutex) {
   return new RoutingDataSource(tenantService, flywayProps, deploymentMutex);
}

@Bean
protected LocalContainerEntityManagerFactoryBean entityManagerFactory(
    DataSource dataSource,
    TenantService tenantService,
    CustomHibernateConnectionProvider connectionProvider,
    CustomHibernateTenantIdentifierResolver tenantIdentifierResolver) {

   var em = new LocalContainerEntityManagerFactoryBean();
   em.getJpaPropertyMap().put(CacheSettings.USE_SECOND_LEVEL_CACHE, false);
   em.getJpaPropertyMap().put("hibernate.cache.region.factory_class", "infinispan");
   em.getJpaPropertyMap().put("hibernate.cache.infinispan.cfg", "org/infinispan/hibernate/cache/commons/builder/infinispan-configs-local.xml");
   em.getJpaPropertyMap().put(MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider);
   em.getJpaPropertyMap().put(MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdentifierResolver);
   em.setDataSource(dataSource);
   em.setPackagesToScan(scanPackages);
   em.setPersistenceUnitName(persistenceUnitName);
   em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
   return em;
}
@Component
class CustomHibernateConnectionProvider extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl<String> {

  private DataSource dataSource;
  public CustomHibernateConnectionProvider(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  @Override
  protected DataSource selectAnyDataSource() {
    return dataSource;
  }
  @Override
  protected DataSource selectDataSource(String tenantIdentifier) {
    return dataSource;
  }
}
@Component
class CustomHibernateTenantIdentifierResolver implements CurrentTenantIdentifierResolver<String> {

   private final TenantService tenantService;

   CustomHibernateTenantIdentifierResolver(TenantService tenantService) {
      this.tenantService = tenantService;
   }
   public String resolveCurrentTenantIdentifier() {
      return tenantService.getCurrentTenantName();
   }
   public boolean validateExistingCurrentSessions() {
      return true;
   }
}
@Entity
@Audited
public class EntityA {
   @Id
   @GeneratedValue(strategy = SEQUENCE, generator = "seq_entity_a")
   @SequenceGenerator(name = "seq_entity_a", sequenceName = "seq_entity_a", allocationSize = 30)
   private Long id;

   ...
}
CREATE OR REPLACE SEQUENCE seq_entity_a START WITH 1 INCREMENT BY 30

tenantService.getCurrentTenantName() 正在使用 TenantContextHolder 将租户标识符存储在一个 ThreadLocal 中。

你能看到我的配置有什么问题吗?

解决方案

根据你的实现,以下是一些观察点:

  1. 模型定义:请检查每个租户是否拥有自己的序列对象(seq_entity_a_tenant1、seq_entity_a_tenant2),而不是共享seq_entity_a。

  2. 分配大小匹配:让Hibernate的 allocationSize与数据库序列的增量保持一致。如果数据库增量为1,而Hibernate使用30,就会产生不匹配。

  3. 检查JPA配置:尝试使用属性hibernate.id.sequence_mismatch_strategy=FIX。当检测到不一致时,这会强制Hibernate与数据库序列重新同步。

  4. 其它:如果多租户环境中的序列不可靠,考虑减小分配大小,或使用GenerationType.IDENTITY。

  5. 让Hibernate通过MultiTenantConnectionProvider专门处理租户选择。

  6. 确保在每次切换租户后清除tenantContext。

可能的修复方法:尝试为每个租户使用单独的序列。

@SequenceGenerator(
     name = "seq_entity_a"
     sequenceName = "seq_entity_a_#{tenantIdentifier}"
     allocationSize = 30
)

你可能需要一个自定义的序列生成器,或者可以使用Hibernate的 ImplicitNamingStrategy,在序列名中追加租户ID。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章