Spring @Transactional 只读传播

Posted

技术标签:

【中文标题】Spring @Transactional 只读传播【英文标题】:Spring @Transactional read-only propagation 【发布时间】:2010-12-09 12:08:37 【问题描述】:

我正在尝试使用命令模式来允许我的 Web 层在单个事务的上下文中使用 Hibernate 实体(从而避免延迟加载异常)。然而,我现在对如何处理交易感到困惑。

我的命令调用带有@Transactional 注释的服务层方法。其中一些服务层方法是只读的 - 例如@Transactional(readOnly = true) - 有些是读/写的。

我的服务层公开了一个命令处理程序,它代表 Web 层执行传递给它的命令。

@Transactional
public Command handle(Command cmd) throws CommandException

我认为我将命令处理程序的handle 方法设置为事务性是正确的。这就是混淆的地方。如果一个命令的实现调用了多个服务层方法,那么命令处理程序就无法知道在命令中调用的操作是只读的、读/写的还是组合的两者之一。

我不明白这个例子中的传播是如何工作的。如果我要创建handle() 方法readOnly = true,那么如果该命令随后调用带有@Transactional(realOnly = false) 注释的服务层方法会发生什么?

【问题讨论】:

那么这两个相互矛盾的答案中哪一个是正确的?有没有人麻烦检查一下? 由于handle()可以调用写入的方法,事务必须允许写入。作为解决方案,那将是正确的。如果你真的想要,你可以调查以编程方式启动 TX 并切换只读 - 也许通过 Command 的属性 - 但我严重怀疑这是否值得。 【参考方案1】:

首先,由于 Spring 本身不做持久化,它无法指定 readOnly 的确切含义。该属性只是对提供者的提示,其行为取决于,在这种情况下,是 Hibernate。

如果将readOnly 指定为true,则当前Hibernate Session 中的刷新模式将设置为FlushMode.NEVER,从而阻止该会话提交事务。

另外,setReadOnly(true) 会在 JDBC Connection 上被调用,这也是对底层数据库的提示。如果您的数据库支持它(很可能支持),这与FlushMode.NEVER 的效果基本相同,但它更强大,因为您甚至无法手动刷新。

现在让我们看看事务传播是如何工作的。

如果您没有将readOnly 显式设置为true,您将拥有读/写事务。根据事务属性(例如REQUIRES_NEW),有时您的事务会在某个时间点暂停,然后启动一个新事务并最终提交,然后恢复第一个事务。

好的,我们快到了。让我们看看是什么将readOnly 带入了这个场景。

如果 read/write 事务中的方法调用需要 readOnly 事务的方法,第一个应该暂停,否则会发生刷新/提交在第二种方法结束时。

相反,如果您从需要 read/writereadOnly 事务中调用方法,同样,第一个将被挂起,因为它无法刷新/已提交,而第二种方法需要它。

readOnly-to-readOnlyread/write-to-read/write 情况下,外部事务不需要暂停(除非您显然,以其他方式指定传播)。

【讨论】:

你确定吗? “只读”真的会覆盖指定的传播策略吗?我很难找到参考资料,但至少找到了相反的帖子:imranbohoran.blogspot.ch/2011/01/… 如果你调用了一个只读的bean,然后这个bean又调用了另一个读写的bean,一个新的事务不会启动,第二个bean参与现有的只读事务,并且第二个 bean 所做的更改未提交。 不正确——正如@dancarter 所说,在 readOnly 事务中调用的 read/write 方法将默默地无法提交,至少在 Spring 的休眠集成。由于最外层的 TX 拦截器是只读的,因此永远不会刷新 Hibernate 会话。并且不会执行任何 SQL 更新。 (这是使用默认传播属性——您可以尝试REQUIRES_NEW,但这不是大多数情况下的正确解决方案。) 另外你在这里完全错了“因为 Spring 本身不做持久化,它不能指定 readOnly 的确切含义。”。事实上,spring 是事务管理器,所以 spring 可以并且确实指定了 readOnly 的含义。 @Transactional 注解本身就是一个spring注解。 Spring 作为 API 提供者和实现者可以准确地指定 API 的含义。 您已经稍微纠正了答案,但我仍然觉得它含糊不清且具有误导性。 “相反,如果您从需要读/写的只读事务中调用一个方法,那么第一个将被挂起,”不,第二个读/写注释方法将参与现有的只读事务。交易不会自动暂停。仅当您使用 REQUIRES_NEW 告诉 spring 暂停它们时,它们才会暂停【参考方案2】:

从 readOnly=true 调用 readOnly=false 不起作用,因为上一个事务仍在继续。

在您的示例中,服务层上的 handle() 方法正在启动新的读写事务。如果handle方法又调用了注解为只读的服务方法,则只读将不起作用,因为它们将参与现有的读写事务。

如果这些方法必须是只读的,那么您可以使用 Propagation.REQUIRES_NEW 对其进行注释,然后它们将启动新的只读事务,而不是参与现有的读写事务。

这是一个工作示例,CircuitStateRepository 是一个 spring-data JPA 存储库。

BeanS 调用 transactional=read-only Bean1,后者进行查找并调用 transactional=read-write Bean2,后者保存一个新对象。

Bean1 启动一个只读 tx。

31 09:39:44.199 [pool-1-thread-1] 调试 osorm.jpa.JpaTransactionManager - 使用名称 [nz.co.vodafone.wcim.business.Bean1.startSomething] 创建新事务:PROPAGATION_REQUIRED,ISOLATION_DEFAULT ,只读; ''

Bean 2 参与其中。

31 09:39:44.230 [pool-1-thread-1] 调试 o.s.orm.jpa.JpaTransactionManager - 参与现有事务

没有向数据库提交任何内容。

现在将 Bean2 @Transactional 注解改为添加propagation=Propagation.REQUIRES_NEW

Bean1 启动一个只读 tx。

31 09:31:36.418 [pool-1-thread-1] 调试 osorm.jpa.JpaTransactionManager - 使用名称 [nz.co.vodafone.wcim.business.Bean1.startSomething] 创建新事务:PROPAGATION_REQUIRED,ISOLATION_DEFAULT ,只读; ''

Bean2 开始一个新的读写 tx

31 09:31:36.449 [pool-1-thread-1] 调试 osorm.jpa.JpaTransactionManager - 暂停当前事务,创建名为 [nz.co.vodafone.wcim.business.Bean2.createSomething] 的新事务

Bean2 所做的更改现在已提交到数据库。

这是示例,使用 spring-data、hibernate 和 oracle 测试。

@Named
public class BeanS     
    @Inject
    Bean1 bean1;

    @Scheduled(fixedRate = 20000)
    public void runSomething() 
        bean1.startSomething();
    


@Named
@Transactional(readOnly = true)
public class Bean1     
    Logger log = LoggerFactory.getLogger(Bean1.class);

    @Inject
    private CircuitStateRepository csr;

    @Inject
    private Bean2 bean2;

    public void startSomething()     
        Iterable<CircuitState> s = csr.findAll();
        CircuitState c = s.iterator().next();
        log.info("GOT CIRCUIT ", c.getCircuitId());
        bean2.createSomething(c.getCircuitId());    
    


@Named
@Transactional(readOnly = false)
public class Bean2     
    @Inject
    CircuitStateRepository csr;

    public void createSomething(String circuitId) 
        CircuitState c = new CircuitState(circuitId + "-New-" + new DateTime().toString("hhmmss"), new DateTime());

        csr.save(c);
     

【讨论】:

惊人的答案,但认为您应该总结如下,因为答案格式最初会引起一些混乱。 1. 从 readOnly=true 调用 readOnly=false 不起作用,因为之前的事务还在继续。 2. 从 readOnly=true WORKS 调用 (propagation = Propagation.REQUIRES_NEW),因为新事务已创建。【参考方案3】:

默认情况下,事务传播是必需的,这意味着同一事务将从事务调用者传播到事务被调用者。在这种情况下,只读状态也将传播。例如。如果一个只读事务会调用一个读写事务,则整个事务都是只读的。

您可以使用 Open Session in View 模式来允许延迟加载吗?这样你的句柄方法就不需要是事务性的了。

【讨论】:

正如@sijk 所说,只读状态会向内传播——没有警告或任何关于 Hibernate 不提交的原因的诊断:( 我正在使用 jpa+hibernate+spring,并且在只读事务称为读写事务并且所有操作都在读写事务中的情况下,提交了持久化的实体但更改了实体通过 getter/setter 没有得到承诺。相当混乱。【参考方案4】:

似乎忽略了当前活动事务的设置,它只将设置应用于新事务:

org.springframework.transaction.PlatformTransactionManager TransactionStatus getTransaction(TransactionDefinition 定义) 抛出事务异常 根据指定的传播行为返回当前活动的事务或创建新事务。 请注意,隔离级别或超时等参数只会应用于新事务,因此在参与活动事务时会被忽略。 此外,并非每个事务管理器都支持所有事务定义设置:当遇到不受支持的设置时,正确的事务管理器实现应该抛出异常。 上述规则的一个例外是只读标志,如果不支持显式只读模式,则应忽略该标志。本质上,只读标志只是潜在优化的提示。

【讨论】:

以上是关于Spring @Transactional 只读传播的主要内容,如果未能解决你的问题,请参考以下文章

Spring中@Transactional(readOnly = false)的作用是啥?

@Transactional的参数意义及使用。spring中事务注解的配置情况

Spring 之注解事务 @Transactional

spring boot 事务

Spring事务性只读和隔离级别

spring事务 只读此文