如何将 @Transactional 与 Spring Data 一起使用?

Posted

技术标签:

【中文标题】如何将 @Transactional 与 Spring Data 一起使用?【英文标题】:How to use @Transactional with Spring Data? 【发布时间】:2012-05-10 19:29:39 【问题描述】:

我刚开始从事 Spring-data、Hibernate、mysql、JPA 项目。我切换到 spring-data 这样我就不必担心手动创建查询了。

我注意到,当您使用 spring-data 时,不需要使用 @Transactional,因为我也尝试了没有注释的查询。

我应该/不应该使用 @Transactional 注释是否有具体原因?

作品:

@Transactional
public List listStudentsBySchool(long id) 
    return repository.findByClasses_School_Id(id);

同样有效:

public List listStudentsBySchool(long id) 
    return repository.findByClasses_School_Id(id);

提前致谢!

【问题讨论】:

【参考方案1】:

你应该使用@Repository注解

这是因为 @Repository 用于将未经检查的 SQL 异常转换为 Spring Excpetion,而您应该处理的唯一异常是 DataAccessException

【讨论】:

这在使用 Spring 时通常是正确的,但由于 Spring Data 存储库已经由 Spring 代理支持 - 使用 @Repository 没有任何区别。【参考方案2】:

您的问题实际上是关于什么的? @Repository注解或@Transactional的用法。

@Repository 根本不需要,因为您声明的接口将由 Spring Data 基础架构创建并激活异常转换的代理支持。所以在 Spring Data 存储库接口上使用这个注解根本没有任何效果。

@Transactional - 对于 JPA 模块,我们在支持代理 (SimpleJpaRepository) 的实现类上有这个注释。这有两个原因:首先,持久化和删除对象需要 JPA 中的事务。因此,我们需要确保事务正在运行,我们通过使用@Transactional 注释方法来做到这一点。

findAll()findOne(…) 这样的读取方法正在使用@Transactional(readOnly = true),这不是绝对必要的,但会触发事务基础结构中的一些优化(将FlushMode 设置为MANUAL 以让持久性提供程序可能跳过脏检查当关闭EntityManager)。除此之外,该标志也在 JDBC 连接上设置,这会导致该级别的进一步优化。

根据您使用的数据库,它可以忽略表锁,甚至拒绝您可能意外触发的写入操作。因此,我们建议使用@Transactional(readOnly = true) 作为查询方法,您可以轻松地实现将该注释添加到您的存储库接口。确保将一个普通的@Transactional 添加到您可能在该接口中声明或重新装饰的操作方法中。

【讨论】:

简而言之:我应该在添加/编辑/删除查询上使用@Transactional,在我所有 DAO 方法的选择查询上使用@Transaction(readOnly = true)? 没错。最简单的方法是在接口上使用@Transactional(readOnly = true)(因为它通常包含大部分查找器方法)并使用普通@Transactional 覆盖每个修改查询方法的此设置。这实际上是在SimpleJpaRepositoy 中完成的方式。 @Oliver 感谢您的全面解释..但是在通过其他链接 [transaction-pit-falls] ibm.com/developerworks/java/library/j-ts1/index.html#listing8> 时。上面写着 "底线是,当你使用基于 ORM 的框架时,只读标志是毫无用处的,在大多数情况下会被忽略。但如果你仍然坚持使用它,请始终设置传播模式到支持..读完后我不确定我是否应该单独使用(readOnly = true)..它是否应该始终与传播模式一起使用作为支持。 本文这部分内容接近一切都是错误的。通过表明您没有在编写,JDBC 驱动可以(将)提高数据库交互的性能。它还可以检测和拒绝意外发出的写入。最重要的是,Spring 在只读模式下禁用 JPA/Hibernate 刷新,如果您读取大型对象图,这会极大地影响性能,因为提供者不需要对其执行脏检查。虽然该标志可能对交易本身没有太大影响,但到目前为止还不是所有要考虑的事情。 @MartinAndersson 注意,任何与数据库的对话都在事务中运行。在这里查看很好的解释和更多要了解的内容:***.com/questions/13539213/…【参考方案3】:

我认为这个问题有点宽泛,不能在数据访问层的注释上减少。我们需要考虑应用程序的整个堆栈、我们想要应用的事务策略等等。 Mark Richards 在 IBM developerworks 站点上有一组非常全面的关于此主题的文章。你可以在这里找到第一个:https://developer.ibm.com/articles/j-ts1/

最好的问候

【讨论】:

链接已失效。【参考方案4】:

我们还使用@Transactional 注释来锁定记录,这样另一个线程/请求就不会改变读取。

【讨论】:

【参考方案5】:

当我们同时创建/更新一个实体时,我们使用@Transactional 注解。如果有@Transactional的方法抛出异常,注解有助于回滚之前的插入。

【讨论】:

【参考方案6】:

在您的示例中,这取决于您的存储库是否有 @Transactional

如果是,那么在您的情况下(按原样)服务 - 不应该使用@Transactional(因为没有必要使用它)。如果您打算在处理其他表/存储库的服务中添加更多逻辑,您可以稍后添加@Transactional - 然后会有一点。

如果不是,那么您的服务应该使用@Transactional,以确保您没有隔离问题,例如您没有阅读尚未通勤的内容。

--

如果谈论一般的存储库(作为 crud 收集接口):

    我会说:不,你不应该使用@Transactional

为什么不:如果我们相信存储库在业务上下文之外,并且它不应该知道传播或隔离(锁定级别)。它无法猜测它可能涉及到哪个事务上下文。

存储库是“无业务的”(如果您相信的话)

说,你有一个存储库:

class MyRepository
   void add(entity) ...
   void findByName(name) ...

还有一个业务逻辑,比如 MyService

 class MyService() 

   @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.SERIALIZABLE)
   void doIt() 
      var entity = myRepository.findByName("some-name");
      if(record.field.equal("expected")) 
        ... 
        myRepository.add(newEntity)
      
   

 

即在这种情况下:MyService 决定它想要将存储库纳入什么。

在这种情况下,propagation="Required" 将确保两个存储库方法 -findByName()add() 都将参与单个事务,并且isolation="Serializable" 将确保没有人可以干预它。它将为涉及 get() 和 add() 的表保持锁定。

但是其他一些服务可能想以不同的方式使用 MyRepository,根本不涉及任何事务,比如它使用 findByName() 方法,对读取任何它现在可以找到的任何内容不感兴趣。

    我会说是的,如果您将存储库视为始终返回有效实体(无脏读)等的存储库(避免用户错误地使用它)。 IE。您的存储库应处理隔离问题(并发性和数据一致性),例如:

我们希望(存储库)确保当我们add(newEntity) 时,它会首先检查是否已经存在具有相同名称的实体,如果是这样 - 插入,全部在一个锁定工作单元中。 (与我们在上面的服务级别所做的相同,但我们不会将此责任转移到存储库)

比如说,不能有 2 个任务同名“进行中”状态(业务规则)

 class TaskRepository
   @Transactional(propagation=Propagation.REQUIRED, 
   isolation=Isolation.SERIALIZABLE)
   void add(entity) 
      var name = entity.getName()
      var found = this.findFirstByName(name);
      if(found == null || found.getStatus().equal("in-progress")) 
      
        .. do insert
      
   
   @Transactional
   void findFirstByName(name) ...

2nd 更像是 DDD 风格的存储库。


我想如果:

  class Service 
    @Transactional(isolation=.., propagation=...) // where .. are different from what is defined in taskRepository()
    void doStuff() 
      taskRepository.add(task);
    
  

【讨论】:

以上是关于如何将 @Transactional 与 Spring Data 一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot整合Shiro 涉及跨域和@Cacheable缓存/@Transactional事务注解失效问题

对比做过的SSH 与 SSM

为啥将@Transactional 与@Service 一起使用而不是与@Controller 一起使用

为啥将@Transactional 与@Service 一起使用而不是与@Controller 一起使用

为什么将@Transactional与@Service一起使用而不是@Controller

@Transactional 与 JPA 和 Hibernate 有啥用?