【问题标题】:Spring data : @Transactional and propogationSpring数据:@Transactional和传播
【发布时间】:2017-06-11 00:34:24
【问题描述】:

我有以下代码:

public void method1(String id){
  Object object = repository.findOne(id);
  object.setState("running");
  repository.save(object);
  try{
    object2.method2(object); //This method takes 2 hours to complete
    object.setState("complete");
  }catch(Exception e){
    object.setState("failed");
  }
  repository.save(object);
}

因此,在调用需要数小时才能执行的方法之前,我将状态更改为“正在运行”。我的对象是 JPA Entity(带有延迟加载的集合),method2() 尝试加载所有链接的 entities

现在,在method2,我得到了

无法初始化代理 - 没有会话

错误,因为它在事务之外(预期行为)。为了防止这种情况,有两种解决方案:

  • @Transactional 注释method1。这可以解决它,但是在方法执行完成之前,state 不会反映到其他事务中。
  • 更改实体配置中的fetch mode 并将其设为Eager。这也可以解决它,但我不希望每次都获取eager

还有其他方法可以让它工作吗?

【问题讨论】:

  • 抱歉,您有多少数据,加载链接实体需要两个小时听起来很疯狂。不加载它们就不能在数据库中操作这些实体吗?
  • method2 实际上不执行任何更新或删除操作。它所做的只是read,所以也许更简单的选择是将实体转换为Dto并将其发送到method2
  • 如果不操作数据,为什么还要加载数据。如果您正在根据实体操作其他内容,则可以选择您需要的数据而不是所有链接的实体。通常像这样长的加载时间表明滥用了急切加载或其他一些概念错误。在下面检查我的答案。

标签: java spring orm spring-data-jpa spring-transactions


【解决方案1】:

这个怎么样:

选项 1

1) 创建状态变化的服务方法如下:

@Transactional( propagation = Propagation.REQUIRES_NEW)
public void changeStatusInNewTransaction(String id, String status){
  Object object = repository.findOne(id);
  object.setState(status);
  repository.save(object);
}

2)将原来的方法改成如下:

@Autowired
Service service;

@Transactional
public void method1(String id){
  service.changeStatusInNewTransaction(id, "running");
  Object object = repository.findOne(id);
  try{
    object2.method2(object); //This method takes 2 hours to complete
    object.setState("complete");
  }catch(Exception e){
    object.setState("failed");
  }
  repository.save(object);
}

由于这种设置,一切都可以在一个 @Transactional 方法下运行,但是当状态要更改为“正在运行”时:

  • 当前事务将被暂停
  • 将创建一个新的
  • 将更改状态并提交事务
  • 父事务将继续,您可以处理您的大操作,而不会出现其他用户在 2 小时内看不到状态更改的问题。..

选项 2

1) 创建状态变化的服务方法如下:

@Transactional
public void changeStatusInNewTransaction(String id, String status){
  Object object = repository.findOne(id);
  object.setState(status);
  repository.save(object);
}

2)为长时间处理创建事务方法

@Transactional
public void performLongProcessing(String id){
    Object object = repository.findOne(id);
    object2.method2(object); //This method takes 2 hours to complete
    object.setState("complete");
    repository.save(objects;
}

3) 标记main方法在没有事务的情况下运行:

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void method1(String id){
  service.changeStatusInNewTransaction(id, "running");

  try{
    service.performLongProcessing(id);
  }catch(Exception e){
    service.changeStatusInNewTransaction(id, "failed");
  }

}

【讨论】:

  • 有趣。我已经尝试过了,但不幸的是它不起作用。这是javadoc所说的Actual transaction suspension will not work out-of-the-box on all transaction managers,我需要检查我的事务管理器是否支持这个。
  • 是的,您的数据库也必须支持这种操作
  • 我脑子里突然冒出另一个想法。
【解决方案2】:

围绕一个方法执行几个小时的事务,似乎是一个设计错误,所以method1() 不应该有@Transactional!当你开始一个事务时,你需要一个连接,并且这个连接将在整个持续时间内从你的连接池中分配,这极大地限制了可扩展性(并且让你的 DBA 生气)。

无法初始化代理 - 没有会话

您收到此错误是因为(在方法 1 上没有 @Transactional)在调用 repository.save() 后您的实体已分离,并且您无法加载惰性集合。一个快速的解决方案是将 EntityManager 注入 object2 并在 method2() 内调用 EntityManager.refresh() 这不需要事务,因为您只是在读取数据。

没有理由使用任何类型的事务传播来解决这个问题。

【讨论】:

  • 关于长时间保持交易开放的好点。我真的没有在method2 中写任何东西,也许我应该尝试将必填字段(或将其转换为dto)传递给method2,而不是传递实际的实体。
猜你喜欢
  • 2018-05-14
  • 2010-12-09
  • 2012-01-19
  • 2015-07-24
  • 2018-08-20
  • 2017-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多