Spring @Transactional只读传播


73

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

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

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

@Transactional
public Command handle( Command cmd ) throws CommandException

我假设我对使命令处理程序的handle()方法具有事务性是正确的。这就是造成混乱的地方。如果命令的实现调用了多个服务层方法,则命令处理程序将无法知道命令内调用的操作是只读,读/写还是组合操作在两个中。

我不了解此示例中的传播方式。如果要创建该handle()方法readOnly=true,那么如果该命令随后调用带有注释的服务层方法,会发生什么情况@Transactional(realOnly=false)

非常感谢您对此有所了解,并欢迎您提出宝贵意见。

安德鲁


1
那么,这两个矛盾的答案中哪一个是正确的?有人不愿意检查吗?
LuGo 2013年

1
由于handle() 可以调用写入方法,因此事务必须允许写入。作为解决方案,这将是正确的。如果确实需要,您可以研究以编程方式启动TX并切换为ReadOnly(也许通过Command的属性),但是我严重怀疑这样做是否值得。
Thomas W

Answers:


90

首先,由于Spring本身并不执行持久性,因此无法指定readOnly确切含义。该属性仅是提供者的提示,其行为取决于(在这种情况下)Hibernate。

如果指定readOnlytrue,则刷新模式将设置为FlushMode.NEVER当前的Hibernate Session中的模式,以防止会话提交事务。

此外,将在JDBC连接上调用setReadOnly(true),这也是对基础数据库的提示。如果您的数据库支持它(很可能会这样做),则其效果与基本上相同FlushMode.NEVER,但是它更强大,因为您甚至无法手动刷新。

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

如果未显式设置readOnlytrue,则将具有读/写事务。根据事务属性(例如REQUIRES_NEW),有时您的事务在某个时候被挂起,新的事务开始并最终提交,然后恢复第一笔事务。

好,我们快到了。让我们看看导致readOnly这种情况的原因。

如果读/写事务中的方法调用需要readOnly事务的方法,则应暂停第一个方法,因为否则第二个方法的末尾将发生刷新/提交。

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

readOnly-to-readOnly读/写到读/写的情况下,不需要暂停外部事务(显然,除非另外指定传播)。


3
你确定吗 ?“只读”是否真的会覆盖指定的传播策略?我有一个很难找到至少引用,但foud这个帖子,指出相反:imranbohoran.blogspot.ch/2011/01/...
皮埃尔·亨利

24
如果您调用一个具有只读功能的bean,然后该bean以读写方式调用另一个bean,则不会启动新事务,第二个bean参与现有的只读事务,第二个bean进行的更改不承诺。
丹·卡特

11
错误的-就像@dancarter所说的那样,至少在Spring的Hibernate集成中,在readOnly事务中调用的读/写方法将无提示地提交。由于最外层的TX拦截器是只读的,因此Hibernate会话永远不会刷新..并且不会执行SQL更新。(这是具有默认传播属性的,您可以尝试使用,但这并不是大多数情况下的正确解决方案。)REQUIRES_NEW
Thomas W

@dancarter是的。但是,如果我们在只读事务中调用读/写事务,那么我们做错了。相反,也可以。
ozgur

1
您已经更正了该答案,但我仍然觉得它含糊不清且具有误导性。“相反,如果您从需要读/写的readOnly事务中调用方法,则第一个方法将被挂起。”不,第二个带注释的读/写方法将参与现有的只读事务。事务不会自动暂停。仅当您通过使用REQUIRES_NEW通知spring暂停它们时,它们才会暂停
dan carter

27

从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,readOnly; ''

  • Bean 2处于其中。

    31 09:39:44.230 [pool-1-thread-1]调试osorm.jpa.JpaTransactionManager-参与现有交易

    什么都没有提交给数据库。

现在更改Bean2@Transactional批注以添加propagation=Propagation.REQUIRES_NEW

  • Bean1启动只读tx。

    31 09:31:36.418 [pool-1-thread-1] DEBUG 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);
     }
}

4
令人惊叹的答案,但您应该总结如下,因为答案的格式最初会引起混乱。1.从readOnly = true调用readOnly = false无效,因为上一个事务继续进行。2.由于已创建新事务,因此从readOnly = true调用(propagation = Propagation.REQUIRES_NEW)。
HopeKingKing 2018年

11

默认情况下,需要进行事务传播,这意味着同一事务将从事务调用方传播到事务被调用方。在这种情况下,只读状态也会传播。例如,如果只读事务将调用读写事务,则整个事务将为只读。

您可以使用“在视图中打开会话”模式来允许延迟加载吗?这样,您的handle方法完全不需要事务。


1
正如@sijk所说,只读状态会向内传播-不会发出警告或任何有关Hibernate不提交原因的诊断信息:(
Thomas W

我正在使用jpa + hibernate + spring,在名为readwrite事务的只读事务且所有操作都在readwrite事务中的情况下,持久化的实体已提交,但通过getter / setter方法更改的实体未提交。相当混乱。
拉尔斯·

6

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

org.springframework.transaction.PlatformTransactionManager
TransactionStatus getTransaction(TransactionDefinition定义)
                         抛出TransactionException
根据指定的传播行为,返回当前活动的事务或创建一个新的事务。
请注意,诸如隔离级别或超时之类的参数将仅应用于新事务,因此在参与活动事务时将被忽略。
此外,并非每个事务管理器都支持所有事务定义设置:当遇到不受支持的设置时,正确的事务管理器实现应引发异常。
上述规则的一个例外是只读标志,如果不支持显式只读模式,则应忽略该标志。本质上,只读标志只是潜在优化的提示。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.