ChatGPT解决这个技术问题 Extra ChatGPT

Spring @Transactional 只读传播

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

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

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

@Transactional
public Command handle(Command cmd) throws CommandException

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

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

那么这两个相互矛盾的答案中哪一个是正确的呢?有没有人费心检查?
由于handle()可能调用写入方法,因此事务必须允许写入。那会很好&正确作为解决方案。如果你真的想要,你可以调查以编程方式启动 TX &切换 readOnly - 也许通过 Command 的属性 - 但我严重怀疑它是否值得付出努力。

n
naXa stands with Ukraine

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

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

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

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

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

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

如果读/写事务中的方法调用需要只读事务的方法,则应挂起第一个方法,否则将在第二个方法结束时发生刷新/提交。

相反,如果您从需要读/写的只读事务中调用一个方法,那么第一个方法将被挂起,因为它不能被刷新/提交,而第二个方法需要它。

在 readOnly-to-readOnly 和 read/write-to-read/write 情况下,不需要暂停外部事务(除非您另外指定传播,显然)。


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

从 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] 调试 osorm.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] DEBUG 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),因为新事务已创建。
s
sijk

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

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


正如@sijk 所说,只读状态会向内传播——没有警告或任何关于为什么 Hibernate 不提交的诊断:(
我正在使用 jpa+hibernate+spring,并且在只读事务称为读写事务并且所有操作都在读写事务中的情况下,持久化的实体已提交,但通过 getter/setter 更改的实体未提交.相当混乱。
A
Andrew Chen

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

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅