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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 14:47:59