Spring Data JPA + Hibernate 将方法标记为事务

Posted

技术标签:

【中文标题】Spring Data JPA + Hibernate 将方法标记为事务【英文标题】:Spring Data JPA + Hibernate Marking methods as Transactional 【发布时间】:2021-03-10 02:41:31 【问题描述】:

我正在为我的应用程序使用 Spring data JPA 的存储库。目前我正在使用 spring data jpa 存储库默认提供的基本 CRUD 操作,对于复杂的连接查询,我正在编写自定义 JPQL 查询,如下所示:

public interface ContinentRepository extends JpaRepository<Continent, Long>

    @Query(value = "SELECT u FROM Continent u JOIN FETCH ... WHERE u.id = ?1")
    public List<Continent> findContinent(Long id);

在我的服务类中,我正在自动装配这个存储库并执行数据库操作。

@Service
public class MyService

    @Autowired
    public ContinentRepository cr;

    public void read()
    
        var result1 = cr.findContinent(1);
        var result2 = cr.findContinent(2);
    


    @Transactional
    public void write()
    
        var c = new Continent();
        // setters
        c = cr.save(c);
    

目前我只是将write() 标记为org.springframework.transaction.annotation.Transactional

    我是否也应该用Transactional(readOnly = true) 标记read() 方法?因为它只执行读取操作。 我是否也应该将findContinent(Long id) 标记为事务性(readOnly = true)?我读到所有默认方法都标记为 Transactional https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions 在存储库接口中,我应该在方法级别还是在接口级别标记Transactional注解。 (另外,我怀疑大多数自定义方法都是只读的) 在 Service 层和 Repository 层都有 @Transactional 好不好?

【问题讨论】:

【参考方案1】:
    我还应该用 Transactional(readOnly = true) 标记 read() 方法吗?因为它只执行读取操作。

不是很必要,但它可能会根据此博客 https://vladmihalcea.com/spring-read-only-transaction-hibernate-optimization/ 对缓存内存消耗进行一些优化

    我是否也应该将 findContinent(Long id) 标记为 Transactional(readOnly = true)?我读到所有默认方法为 标记为事务性 https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions

与第一种情况相同的答案

    在存储库接口中,我应该在方法级别还是在接口级别标记Transactional注解。 (另外,我 怀疑大多数自定义方法都是只读的)

一般我是加到方法层的,因为我对回滚等参数有更多的控制权

    在 Service 层和 Repository 层都有 @Transactional 好不好?

我建议您使用服务级别,因为在那里您可以更新许多表(因此您可以使用许多存储库),并且您希望您的整个更新都是事务性的。

【讨论】:

@Transactional(readOnly = true) 是多余的并不是严格意义上的。这取决于隔离级别。虽然对于READ COMMITED 确实是多余的,但对于REPEATABLE READ 和更高的隔离级别,其行为不同,其中不会立即释放读锁 如果我用 Transactional 注释 Service 类,并用 Transactional 注释该服务类内的方法(但与类级别 Transactional 注释相比具有不同的属性),那么将应用哪个注释。事务注解是否支持方法级别的覆盖属性? 另外,当一个服务方法调用另一个服务的方法并且都具有REQUIRED 的传播但其他属性不同时。例如:服务A中带有@Transactional的方法调用服务B中带有@Transactional(readOnly=true)的方法,那么在这种情况下是否创建了一个新事务? 这篇文章描述了注释应用的优先顺序baeldung.com/…但是我在spring docs中找不到任何这样的东西。 @JavaLearner 如果您使用@Transactional 注释服务并在内部调用使用@Transactional 注释的存储库方法,那么您需要为回购事务注释指定传播REQUIRED,以便加入已经存在的交易并且不创建另一个交易。但是,您应该检查您的数据库是否支持这一点(它应该)。一般来说,我不会这样做,因为我只在服务中调用存储库,并且存储库类是包保护的,因此不会被注入到位于其包之外的类中。【参考方案2】:
    我还应该用 Transactional(readOnly = true) 标记 read() 方法吗?因为它只执行读取操作。

是的。这是一个很好的做法,您会立即知道此方法不会更改数据库中的任何内容,更重要的是,您将使用此服务方法禁用数据库中的一些无意更改。如果此方法尝试对 DB 进行任何更新,则会抛出异常并调用回滚。

    我是否也应该将 findContinent(Long id) 标记为 Transactional(readOnly = true)?我读到所有默认方法为 标记为事务性 https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions

取决于您如何使用它。如果您总是从@Sevice 调用它,那么不会。我认为这是我所知道的常见用例。

    在存储库界面中,我是否应该标记 Transactional 方法级别或接口级别的注释。 (另外,我 怀疑大多数自定义方法都是只读的)

同 2。

    在 Service 和 Repository 中都有 @Transactional 是否很好 层?

同 2。

【讨论】:

关于#1,它取决于提供者docs.spring.io/spring-framework/docs/current/javadoc-api/org/… 如果你使用 Spring Data 和 Hibernate,这是真的。我是从那次经验中谈起的,它是最常用的堆栈。对我来说,如果实现不解释只读是可以接受的。当您在更大的团队中工作时,这一点非常重要。

以上是关于Spring Data JPA + Hibernate 将方法标记为事务的主要内容,如果未能解决你的问题,请参考以下文章

spring-data-jpa 和 spring-boot-starter-data-jpa 的区别

spring-data详解之spring-data-jpa:简单三步快速上手spring-data-jpa开发

spring data jpa怎么和solr整合

spring data jpa问题

Spring Data 系列 Spring+JPA(spring-data-commons)

spring-data-jpa软删除方案