如何在Spring Data中使用@Transactional?

106

我刚开始接触一个使用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);
}
5个回答

170
你实际上的问题是关于什么?使用 @Repository 注解还是 @Transactional
在 Spring Data 仓库接口中,根本不需要使用 @Repository 注解,因为你声明的接口将由 Spring Data 基础设施创建并激活异常转换的代理支持。因此,在 Spring Data 仓库接口上使用此注解实际上没有任何效果。
对于 JPA 模块,我们在代理后面的实现类(SimpleJpaRepository) 上使用 @Transactional 注解,这是出于两个原因: 首先,在 JPA 中,持久化和删除对象需要事务。因此,我们需要确保正在运行事务,我们通过在方法上添加 @Transactional 来实现这一点。
findAll()findOne(…) 这样的读取方法也使用了 @Transactional(readOnly = true) 注解,虽然这不是必须的,但会触发事务基础结构中的几个优化项(将FlushMode设置为MANUAL,以便让持久性提供程序在关闭EntityManager时跳过脏检查)。此外,该标志还设置在 JDBC 连接上,这会在该级别上进一步优化。
根据你使用的数据库,它可以省略表锁甚至拒绝你可能意外触发的写操作。因此,我们建议对查询方法也使用 @Transactional(readOnly = true),你可以通过将该注解添加到仓库接口来轻松实现。确保在你声明或重新装饰的操纵方法中添加一个普通的@Transactional 注解。

14
简而言之,我应该在所有 DAO 方法中,在增加/编辑/删除查询上使用 @Transactional,在选择查询上使用 @Transaction(readOnly = true)? - Byron Voorbach
27
没问题。最简单的方法是在接口上使用@Transactional(readOnly = true)(因为它通常包含大部分的查找方法),并使用普通的@Transactional覆盖每个修改查询方法的设置。这实际上就是SimpleJpaRepository中所采用的方式。 - Oliver Drotbohm
10
这段文章中几乎所有的内容都是错的。通过表明你不在写入,JDBC驱动可以(并且将)提高与数据库交互的性能。它还可以检测和拒绝意外发出的写操作。此外,Spring在只读模式下禁用了JPA / Hibernate刷新,这可能会对性能产生巨大影响,特别是在读取大型对象图时,因为提供程序不需要对其执行脏检查。该标志对事务本身可能没有太大影响,但远非考虑的全部。 - Oliver Drotbohm
1
@MartinAndersson 注意,任何与数据库的交互都在事务中运行。在这里查看一个很好的解释和更多学习资料:https://dev59.com/K2Yr5IYBdhLWcg3wnrZ0 - Lubo
2
顺便提一下,在任何 @Configuration 类上使用 @EnableTransactionManagement,以便 @Transactional 正常工作。 - hello_earth
显示剩余4条评论

3
在你的示例中,这取决于你的存储库是否具有@Transactional
如果是的话,在你的情况下,服务应该不使用@Transactional(因为没有使用它的意义)。如果您计划添加处理其他表/存储库的更多逻辑到服务中,则可以稍后添加@Transactional - 那时使用它就有意义了。
如果没有,则如果您想确保您没有隔离问题,例如您没有读取尚未提交的内容,那么您的服务应该使用@Transactional
- 如果谈论一般的存储库(作为CRUD集合接口):
1. 我会说:不,你应该不使用@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()方法,不关心读取此时找到的任何内容的限制。
我会说如果您将存储库视为始终返回有效实体(无脏读等)的存储库,则应该是的(保存用户免于使用不正确)。也就是说,您的存储库应该解决隔离问题(并发性和数据一致性),就像上面的示例一样:
我们希望(存储库)确保在添加新实体之前,它将首先检查是否已经存在具有相同名称的实体,如果存在,则插入所有锁定工作单元。 (与上述服务级别相同,但现在我们将此责任转移到存储库)
比方说,不能有两个处于“进行中”状态并带有相同名称的任务(业务规则)。
 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) {...}

第二个更像DDD风格的仓库。


我猜如果有更多需要涵盖:

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

0
我们还使用 @Transactional 注释来锁定记录,以便另一个线程/请求不会更改读取。

0

在同时创建 / 更新一个或多个实体时,我们使用 @Transactional 注释。如果带有 @Transactional 的方法抛出异常,则该注释将帮助回滚之前的插入操作。


0

你应该使用@Repository注解。

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


14
通常在使用Spring时这是正确的,但是由于Spring Data存储库已经由Spring代理支持,因此使用@Repository并没有任何区别。 - Aleksander Blomskøld

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接