Spring@Transactional 只读传播

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

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

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

@Transactional
public Command handle(Command cmd) throws CommandException

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

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

197143 次浏览

First of all, since Spring doesn't do persistence itself, it cannot specify what readOnly should exactly mean. This attribute is only a hint to the provider, the behavior depends on, in this case, Hibernate.

If you specify readOnly as true, the flush mode will be set as FlushMode.NEVER in the current Hibernate Session preventing the session from committing the transaction.

Furthermore, setReadOnly(true) will be called on the JDBC Connection, which is also a hint to the underlying database. If your database supports it (most likely it does), this has basically the same effect as FlushMode.NEVER, but it's stronger since you cannot even flush manually.

Now let's see how transaction propagation works.

If you don't explicitly set readOnly to true, you will have read/write transactions. Depending on the transaction attributes (like REQUIRES_NEW), sometimes your transaction is suspended at some point, a new one is started and eventually committed, and after that the first transaction is resumed.

OK, we're almost there. Let's see what brings readOnly into this scenario.

If a method in a read/write transaction calls a method that requires a readOnly transaction, the first one should be suspended, because otherwise a flush/commit would happen at the end of the second method.

Conversely, if you call a method from within a readOnly transaction that requires read/write, again, the first one will be suspended, since it cannot be flushed/committed, and the second method needs that.

In the readOnly-to-readOnly, and the read/write-to-read/write cases the outer transaction doesn't need to be suspended (unless you specify propagation otherwise, obviously).

By default transaction propagation is REQUIRED, meaning that the same transaction will propagate from a transactional caller to transactional callee. In this case also the read-only status will propagate. E.g. if a read-only transaction will call a read-write transaction, the whole transaction will be read-only.

Could you use the Open Session in View pattern to allow lazy loading? That way your handle method does not need to be transactional at all.

Calling readOnly=false from readOnly=true doesn't work since the previous transaction continues.

In your example, the handle() method on your service layer is starting a new read-write transaction. If the handle method in turn calls service methods that annotated read-only, the read-only will take no effect as they will participate in the existing read-write transaction instead.

If it is essential for those methods to be read-only, then you can annotate them with Propagation.REQUIRES_NEW, and they will then start a new read-only transaction rather than participate in the existing read-write transaction.

Here is a worked example, CircuitStateRepository is a spring-data JPA repository.

BeanS calls a transactional=read-only Bean1, which does a lookup and calls transactional=read-write Bean2 which saves a new object.

  • Bean1 starts a read-only tx.

31 09:39:44.199 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Creating new transaction with name [nz.co.vodafone.wcim.business.Bean1.startSomething]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly; ''

  • Bean 2 pariticipates in it.

    31 09:39:44.230 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Participating in existing transaction

    Nothing is committed to the database.

Now change Bean2 @Transactional annotation to add propagation=Propagation.REQUIRES_NEW

  • Bean1 starts a read-only tx.

    31 09:31:36.418 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Creating new transaction with name [nz.co.vodafone.wcim.business.Bean1.startSomething]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly; ''

  • Bean2 starts a new read-write tx

    31 09:31:36.449 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager - Suspending current transaction, creating new transaction with name [nz.co.vodafone.wcim.business.Bean2.createSomething]

And the changes made by Bean2 are now committed to the database.

Here's the example, tested with spring-data, hibernate and 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);
}
}

It seem to ignore the settings for the current active transaction, it only apply settings to a new transaction:

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.