Spring 当具有来自另一个事务的延迟加载的 Hibernate 对象被传递给方法时,@Transactional 不工作

Spring @Transactional not working when Hibernate object with lazy loading coming from another transaction is passed down to method

当数据来自另一个(假设已关闭的)事务时,我在访问 运行 事务中的数据时遇到问题。我有三个如下所示的 类,其中一个实体(称为 MyEntity)还具有另一个通过 Hibernate 映射连接的实体,称为“OtherEntity”,延迟加载设置为 true。请注意我如何进行两笔交易:

但是,即使我在方法中有一个活动事务(TransactionSynchronizationManager.isActualTransactionActive 是真的),这在“无会话”循环内失败。

我不太明白这个问题。在我看来,即使第一个交易应该完成,第二个交易使用的对象“属于”第一个交易?也许是竞争条件?

@Service
class ServiceA {
    
    @Autowired
    private ServiceB serviceB;

    @Autowired
    private ServiceC serviceC;

    public void test() {
        List<MyEntity> allEntities = serviceC.loadAllEntities(); //First transaction ran, getting a list of entities, but due to lazy loading we havent loaded all the data
        for(MyEntity i : allEntities) {
            serviceB.doOnEach(i); //On each element a new transaction should start
        }
    }

}

@Service
class ServiceB {

    @Transactional
    public void doOnEach(MyEntity entity) {
        System.out.println(TransactionSynchronizationManager.isActualTransactionActive()); //true, therefore we have an active transaction here
        OtherEntity other = entity.getSomeOtherEntity(); //Want to load the "lazy loaded" entity here
        //"No Session" exception is thrown here
    }

}

@Service
class ServiceC {
    
    @Autowired
    private MyRepository myRepository;

    @Transactional
    public List<MyEntity> loadAllEntities() {
        return myRepository.findAll();
    }

}

一个解决方案是在“doOnEach”方法中重新加载“MyEntity”实例,但在我看来这不是最佳解决方案,尤其是在大列表中。为什么我要重新加载所有应该存在的数据?

感谢任何帮助。

显然实际代码比这复杂得多,但出于业务原因我必须有这些单独的事务,所以请不要重写核心逻辑的“解决方案”。我只是想了解这里发生了什么。

在对 loadAllEntities() 的调用完成后,Spring 代理提交事务并关闭关联的 Hibernate 会话。这意味着你不能再让 Hibernate 透明地加载 non-loaded 惰性关联而不明确地告诉它这样做。

如果出于某种原因你真的希望延迟加载你的关联实体,你有两个选择是在你的 doOnEach() 方法中使用 Hibernate.initialize(entity.getSomeOtherEntity()) 或设置 spring.jpa.open-in-view 属性 为 true 让 OpenSessionInViewInterceptor 为你做。

否则,最好通过存储库查询中的 JOIN FETCH 或通过实体图将它们与父实体一起加载。

参考文献:


进一步澄清:

Spring 在进入 loadAllEntities() 方法之前创建一个事务并打开一个新会话 (A),并在返回时 commits/closes 它们。当您调用 entity.getSomeOtherEntity() 时,加载 entity 的原始会话 (A) 消失了(即 entity 已分离),而是有一个新会话 (B) 在进入 doOnEach()事务方法。显然 Session (B) 对 entity 及其关系一无所知,同时 entitysomeOtherEntity 的 Hibernate 代理引用原始 Session (A) 并且不对会话 (B) 有所了解。要使 someOtherEntity 的 Hibernate 代理实际使用当前活动会话 (B),您可以调用 Hibernate.initialize().