我正在尝试使用命令模式来允许我的Web层在单个事务的上下文中使用Hibernate实体(从而避免延迟加载异常)。但是,我现在对如何处理交易感到困惑。
我的命令调用使用@Transactional
注释注释的服务层方法。这些服务层方法中的一些是只读的 - 例如, @Transactional(readOnly=true)
- 有些是读/写。
我的服务层公开了一个命令处理程序,它执行代表Web层传递给它的命令。
@Transactional
public Command handle( Command cmd ) throws CommandException
我认为我在使命令处理程序的handle()
方法事务处理方面是正确的。这就是混乱的来源。如果命令的实现调用多个服务层方法,命令处理程序无法知道命令中调用的操作是只读,读/写还是组合两个。
我不明白传播在这个例子中是如何工作的。如果我要创建handle()
方法readOnly=true
,那么如果该命令调用使用@Transactional(realOnly=false)
注释的服务层方法会发生什么?
我很高兴能够更好地理解这一点并欢迎你的评论......
安德鲁
答案 0 :(得分:64)
首先,由于Spring本身不执行持久性,因此无法指定readOnly
应该具有什么意义。这个属性只是对提供者的一个提示,行为取决于,在这种情况下,Hibernate。
如果您将readOnly
指定为true
,则当前Hibernate会话中的刷新模式将设置为 FlushMode.NEVER
,从而阻止会话提交事务。
此外,将在JDBC Connection上调用 setReadOnly(true),这也是底层数据库的提示。如果你的数据库支持它(很可能它),它与FlushMode.NEVER
的效果基本相同,但它更强大,因为你甚至无法手动刷新。
现在让我们看看事务传播是如何工作的。
如果未明确将readOnly
设置为true
,则会有读/写事务。根据事务属性(如REQUIRES_NEW
),有时您的事务会在某个时刻暂停,新的事务将被启动并最终提交,之后将恢复第一个事务。
readOnly
进入这种情况。
如果读/写事务中的方法调用需要 readOnly 事务的方法,则应暂停第一个事务,否则将发生刷新/提交在第二种方法结束时。
相反,如果您在 readOnly 事务中调用一个需要读/写的方法,那么第一个方法将被暂停,因为它无法刷新/承诺,第二种方法需要。
在 readOnly-to-readOnly 和读/写 - 读/写情况下,外部交易不需要暂停(除非你否则,明确指定传播。
答案 1 :(得分:18)
自上次事务继续以来,从readOnly = true调用readOnly = false不起作用。
在您的示例中,服务层上的handle()方法正在启动一个新的读写事务。如果handle方法依次调用带注释的只读服务方法,则只读将不起作用,因为它们将参与现有的读写事务。
如果这些方法必须是只读的,那么您可以使用Propagation.REQUIRES_NEW对它们进行注释,然后它们将启动一个新的只读事务,而不是参与现有的读写事务。
这是一个有效的例子,CircuitStateRepository是一个spring-data JPA存储库。
BeanS调用transactional =只读Bean1,它执行查找并调用transactional = read-write Bean2,它保存一个新对象。
31 09:39:44.199 [pool-1-thread-1] DEBUG 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] DEBUG o.s.orm.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] 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);
}
}
答案 2 :(得分:11)
默认情况下,事务传播是必需的,这意味着同一事务将从事务调用者传播到事务调用者。在这种情况下,只读状态也会传播。例如。如果只读事务将调用读写事务,则整个事务将是只读的。
您是否可以使用View模式中的Open Session来允许延迟加载?这样你的句柄方法根本不需要是事务性的。
答案 3 :(得分:5)
它似乎忽略了当前活动事务的设置,它只将设置应用于新事务:
org.springframework.transaction.PlatformTransactionManager TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException Return a currently active transaction or create a new one, according to the specified propagation behavior. Note that parameters like isolation level or timeout will only be applied to new transactions, and thus be ignored when participating in active ones. Furthermore, not all transaction definition settings will be supported by every transaction manager: A proper transaction manager implementation should throw an exception when unsupported settings are encountered. An exception to the above rule is the read-only flag, which should be ignored if no explicit read-only mode is supported. Essentially, the read-only flag is just a hint for potential optimization.