【问题标题】:Should I add @Transactional(readOnly = true) on custom methods?我应该在自定义方法上添加 @Transactional(readOnly = true) 吗?
【发布时间】:2022-11-21 00:28:36
【问题描述】:

假设我有以下存储库:

public interface ApplicationRepository extends JpaRepository<Application, Integer> {

    public boolean existsByCode(String code);

    public Optional<Application> findByCode(String code);

}

以及以下服务:

@Service
@RequiredArgsConstructor
public class ApplicationService {

    private final ApplicationRepository appRepo;

    public Application findById(Integer id) throws RecordNotFoundException {
        return appRepo.findById(id)
                .orElseThrow(() -> new RecordNotFoundException("Application with id: " + id + " could not be found"));
    }

    public boolean existsByCode(String code) {
        return appRepo.existsByCode(code);
    }

    public Application findByCode(String code) throws RecordNotFoundException {
        return appRepo.findByCode(code).orElseThrow(
                () -> new RecordNotFoundException("Application with code: " + code + " could not be found"));
    }

}
  1. 由于默认的存储库方法有@Transactional(readOnly = true),我应该在我的自定义方法上添加注释吗?如果是这样,最好在服务方法或存储库上添加注释?

  2. 如果我有第三种方法,它调用另外两个标有@Transactional(readOnly = true) 的方法,是否最好也用注解标记这个方法?

【问题讨论】:

  • 请阅读:Can I ask only one question per post? --- 这两个问题都无法明确回答。这取决于交易边界,即什么应该是交易的一部分。对于第二个问题尤其如此。
  • 是什么让你说“默认存储库方法有@Transactional(readOnly = true)?我在source code of JpaRepository或其超类型中没有看到任何这样的注释?

标签: java spring-boot hibernate jpa spring-data-jpa


【解决方案1】:

Spring参考手册writes

@Transactional 注释是指定接口、类或方法必须具有事务语义的元数据(例如,“调用此方法时启动全新的只读事务,暂停任何现有事务”)。默认的@Transactional 设置如下:

  • 传播设置为 PROPAGATION_REQUIRED。
  • 隔离级别为 ISOLATION_DEFAULT。
  • 事务是读写的。
  • 事务超时默认为底层事务系统的默认超时,如果不支持超时则为无。
  • 任何 RuntimeException 或 Error 都会触发回滚,而任何已检查的异常都不会。

PROPAGATION_REQUIRED 的 JavaDoc 说:

支持当前交易;如果不存在,则创建一个新的。

也就是说,如果 @Transactional 服务调用一个或多个 @Transactional 存储库,该服务将管理事务,即事务将在进入服务方法时开始,并在离开时提交或回滚。存储库上的@Transactional 将检测现有事务,但什么都不做,导致存储库参与现有事务。

在服务级别启动事务通常是可取的。首先,拥有单个事务意味着所有数据都在单个事务中读取,与在单独的事务中加载的数据(另一个事务之间可能修改了数据)相比,为您提供了更强的一致性保证。另一方面,开始事务是有成本的,尤其是对于 JPA,其中每个事务都有自己的持久性上下文。这就是为什么休眠团队 speaks out 反对“Session-per-operation 反模式”。

【讨论】:

  • 我敢打赌“休眠团队”还没有决定什么是反模式,什么不是:vladmihalcea.com/the-open-session-in-view-anti-pattern
  • 好吧,你打错了,因为 hibernate 团队一直反对 Session-per-operation 反模式和 open-session-in-view 反模式,并一直推荐 session-per-request 模式。如果您实际阅读了我提供的链接,您会看到每个请求的会话模式并没有说明会话应该在视图中打开,因此这些建议是完全一致的。
  • “这里的请求一词与系统的概念有关,该系统对来自客户端/用户的一系列请求做出反应。Web 应用程序是此类系统的主要示例,尽管肯定不是唯一的”——OSIV 正是这样说的正在实施,而且我不是唯一持这种观点的人:baeldung.com/spring-open-session-in-view:“每个请求的会话是一种将持久性会话和请求生命周期联系在一起的事务模式。毫不奇怪,Spring 自带了这种模式的实现,名为 OpenSessionInViewInterceptor”
  • 如果 HBN 文档中的要点是关于 http 请求定义事务边界,它甚至比“OSIV 反模式”更糟糕 - 这样做有太多缺点:一旦你在 NBH 中开始事务你就别无选择(或者它太复杂了)开始从副本读取数据,你在丰富数据的同时持有数据库连接,错误代码回滚事务中的无关异常等。一般来说,业务操作应该定义事务边界,有时这些边界与http请求重合,但在大多数时候他们没有。
  • 是的,实际上,我分享了更极端的想法,比如stackoverflow.com/a/73605252/3426309
【解决方案2】:

我不会太关注readOnly = true。从 Spring/Hibernate 的角度来看,指定会导致以下行为:

JpaTransactionManager#doBegin调用HibernateJpaDialect#beginTransaction,这又会触发以下代码:

if (definition.isReadOnly()) {
   session.setDefaultReadOnly(true);
}

Session#setDefaultReadOnly 有以下 javadoc:

更改加载到此会话中的实体和代理的默认值 从可修改模式到只读模式,或从可修改模式到只读模式 模式。只读实体不进行脏检查和快照 不维持持久状态。只读实体可以是 已修改,但不会保留更改。初始化代理时, 加载的实体将具有与相同的只读/可修改设置 未初始化的代理有,不管会话的当前 环境。

所以,@Transactional(readOnly = true) 定义可能会为您节省一些内存和 CPU 周期(它不会阻止 HBN 修改数据库中的数据),但是为了从最外部的事务定义中获得一些好处,必须将其声明为只读。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-06-06
    • 2012-08-14
    • 2023-03-10
    • 2021-04-23
    • 1970-01-01
    • 2017-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多