Hibernate版本升级后列级多租户应用保存实体触发StaleObjectStateException问题求助
Hibernate版本升级后列级多租户应用保存实体触发StaleObjectStateException问题求助
各位好,我最近把项目里的Hibernate版本从6.3.0.CR1升级到了6.6.13.Final,完全按照官方迁移指南操作后,遇到了一个诡异的问题:
在我的列级多租户应用中,当我先以租户A的身份查询一个实体,然后切换到租户B(该实体关联的Shop所属租户是B),再调用save方法保存这个实体时,就会抛出StaleObjectStateException;但如果租户B和租户A是同一个,保存操作完全正常。
实体与多租户核心代码
先贴一下我的实体类结构:
class Shop { @Id @Column(name = "shop_uuid") @Builder.Default private UUID shopUuid = UUID.randomUUID(); @TenantId @Column(name = "tenant_id", nullable = false) private String tenantId; @OneToMany(fetch = FetchType.LAZY) @JoinColumn(name = "shop_uuid", referencedColumnName = "shop_uuid") private List<Product> products; } class Product { @Id @GeneratedValue(strategy = GenerationType.UUID) @Column(name = "product_uuid") private UUID productUuid; @Column(name = "product_amount") private Integer amount; @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "shop_uuid", referencedColumnName = "shop_uuid", nullable = false) private Shop shop; }
再贴一下多租户相关的上下文和解析器代码:
public class TenantContext { private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>(); private TenantContext() { } public static String getCurrentTenant() { return CURRENT_TENANT.get(); } public static void setCurrentTenant(String tenant) { CURRENT_TENANT.set(tenant); } public static void clear() { CURRENT_TENANT.remove(); } } public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver, HibernatePropertiesCustomizer { @Override public String resolveCurrentTenantIdentifier() { return TenantContext.getCurrentTenant(); } @Override public boolean validateExistingCurrentSessions() { return false; } @Override public void customize(Map<String, Object> hibernateProperties) { hibernateProperties.put("hibernate.tenant_identifier_resolver", this); } @Override public boolean isRoot(Object tenantId) { // 自定义的根租户判断逻辑 return ...; } }
问题重现逻辑
我是这样触发问题的:
// 1. 设置初始租户为"default" TenantContext.setCurrentTenant("default"); // 2. 以default租户身份查询目标Product var product = productRepository.findById(productUuid).get(); // 3. 切换到该Product关联的Shop所属租户(假设此租户≠"default") TenantContext.setCurrentTenant(product.getShop().getTenantId()); // 4. 保存Product,此时若租户与default不同,直接抛出异常 productRepository.save(product);
异常栈信息
当跨租户保存时,抛出的异常如下:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [] at app//org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:426) at app//org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:214) at app//org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:152) at app//org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:136) at app//org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:89) at app//org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127) at app//org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:854) at app//org.hibernate.internal.SessionImpl.merge(SessionImpl.java:840) at java.base@17.0.14/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base@17.0.14/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base@17.0.14/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base@17.0.14/java.lang.reflect.Method.invoke(Method.java:569) at app//org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:320) at app/jdk.proxy3/jdk.proxy3.$Proxy267.merge(Unknown Source) at app//org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:639) at java.base@17.0.14/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base@17.0.14/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base@17.0.14/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base@17.0.14/java.lang.reflect.Method.invoke(Method.java:569) at app//org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) at app//org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277) at app//org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) at app//org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) ...
求助内容
我已经确认租户切换逻辑没问题,执行save之前TenantContext里的租户已经正确切换到目标租户;实体的关联关系也正常,查询出的Product确实关联了属于目标租户的Shop。
有没有大佬遇到过Hibernate 6.x升级后列级多租户的类似问题?或者能帮我分析下可能的原因,比如是不是Hibernate在处理跨租户的merge操作时逻辑有变化?
内容来源于stack exchange
相关产品推荐
相关产品推荐

