我正在尝试使用命令模式来允许我的 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 的属性 - 但我严重怀疑它是否值得付出努力。
首先,由于 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 情况下,不需要暂停外部事务(除非您另外指定传播,显然)。
从 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);
}
}
默认情况下,事务传播是必需的,这意味着相同的事务将从事务调用者传播到事务被调用者。在这种情况下,只读状态也将传播。例如,如果一个只读事务将调用一个读写事务,则整个事务将是只读的。
您可以使用 Open Session in View 模式来允许延迟加载吗?这样,您的句柄方法根本不需要是事务性的。
它似乎忽略了当前活动事务的设置,它只将设置应用于新事务:
org.springframework.transaction.PlatformTransactionManager TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException 根据指定的传播行为返回当前活动的事务或创建新的事务。请注意,隔离级别或超时等参数只会应用于新事务,因此在参与活动事务时会被忽略。此外,并非每个事务管理器都支持所有事务定义设置:正确的事务管理器实现应该在遇到不受支持的设置时抛出异常。上述规则的一个例外是只读标志,如果不支持显式只读模式,则应忽略该标志。本质上,只读标志只是潜在优化的提示。
REQUIRES_NEW
,但它不是大多数情况下的正确解决方案。)