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
或通过实体图将它们与父实体一起加载。
参考文献:
- https://www.baeldung.com/spring-open-session-in-view
- https://www.baeldung.com/hibernate-initialize-proxy-exception
进一步澄清:
Spring 在进入 loadAllEntities()
方法之前创建一个事务并打开一个新会话 (A),并在返回时 commits/closes 它们。当您调用 entity.getSomeOtherEntity()
时,加载 entity
的原始会话 (A) 消失了(即 entity
已分离),而是有一个新会话 (B) 在进入 doOnEach()
事务方法。显然 Session (B) 对 entity
及其关系一无所知,同时 entity
中 someOtherEntity
的 Hibernate 代理引用原始 Session (A) 并且不对会话 (B) 有所了解。要使 someOtherEntity
的 Hibernate 代理实际使用当前活动会话 (B),您可以调用 Hibernate.initialize()
.
当数据来自另一个(假设已关闭的)事务时,我在访问 运行 事务中的数据时遇到问题。我有三个如下所示的 类,其中一个实体(称为 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
或通过实体图将它们与父实体一起加载。
参考文献:
- https://www.baeldung.com/spring-open-session-in-view
- https://www.baeldung.com/hibernate-initialize-proxy-exception
进一步澄清:
Spring 在进入 loadAllEntities()
方法之前创建一个事务并打开一个新会话 (A),并在返回时 commits/closes 它们。当您调用 entity.getSomeOtherEntity()
时,加载 entity
的原始会话 (A) 消失了(即 entity
已分离),而是有一个新会话 (B) 在进入 doOnEach()
事务方法。显然 Session (B) 对 entity
及其关系一无所知,同时 entity
中 someOtherEntity
的 Hibernate 代理引用原始 Session (A) 并且不对会话 (B) 有所了解。要使 someOtherEntity
的 Hibernate 代理实际使用当前活动会话 (B),您可以调用 Hibernate.initialize()
.