在上一篇 Spring Boot事务管理(上)的基础上介绍Spring Boot事务属性和事务回滚规则 。
4 Spring Boot事务属性
什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示,它们定义于TransactionDefinition接口类
1、 事务隔离级别
隔离级别是指若干个并发事务之间的隔离程度。
Spring Boot的隔离级别被封装在枚举类Isolation,枚举值取自接口TransactionDefinition 定义,该接口中定义了五个表示隔离级别的常量:
隔离级别 |
含义 |
ISOLATION_DEFAULT |
默认值,使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED |
表示一个事务可以读取另一个事务修改但还没有提交的数据。可能导致脏读,不可重复读和幻读,因此很少使用该隔离级别。最低的隔离级别。 |
ISOLATION_READ_COMMITTED |
表示一个事务只能读取另一个事务已经提交的数据。可以防止脏读,但是幻读或不可重复读仍有可能发生,这也是大多数情况下的推荐值。 |
ISOLATION_REPEATABLE_READ |
表示一个事务在整个过程中可以多次重复执行某个查询并且每次返回的记录都相同。可以防止脏读和不可重复读,但幻读仍有可能发生。MySQL默认隔离级别。 |
ISOLATION_SERIALIZABLE |
所有的事务依次逐个执行,事务之间完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能,因为它通常是通过完全锁定事务相关的数据库表来实现的。 |
2、 事务传播规则
事务传播行为(propagation behavior)用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时候,事务如何传播。
传播行为定义了事务范围,何时触发事务,是否暂停现有事务,或者在调用方法是如果没有事务则失败等等。
Spring Boot的事务传播行为常量被封装在枚举类Propagation,枚举值取自接口TransactionDefinition,在接口中定义了如下七个表示传播行为的常量:
传播行为 |
含义 |
PROPAGATION_REQUIRED |
如果当前存在事务,则加入该事务;否则,新建一个事务。这是默认值 |
PROPAGATION_REQUIRES_NEW |
新建事务,如果当前存在事务,则挂起当前事务 |
PROPAGATION_SUPPORTS |
如果当前存在事务,则加入该事务;否则,以非事务的方式继续运行 |
PROPAGATION_NOT_SUPPORTED |
以非事务方式运行,如果当前存在事务,则挂起当前事务 |
PROPAGATION_NEVER |
以非事务方式运行,如果当前存在事务,则抛异常 |
PROPAGATION_MANDATORY |
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
PROPAGATION_NESTED |
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED |
下面贴出TransactionDefinition中关于传播行为的源码。
/**
* Support a current transaction; create a new one if none exists.
* Analogous to the EJB transaction attribute of the same name.
* <p>This is typically the default setting of a transaction definition,
* and typically defines a transaction synchronization scope.
*/
int PROPAGATION_REQUIRED = 0; /**
* Support a current transaction; execute non-transactionally if none exists.
* Analogous to the EJB transaction attribute of the same name.
* <p><b>NOTE:</b> For transaction managers with transaction synchronization,
* {@code PROPAGATION_SUPPORTS} is slightly different from no transaction
* at all, as it defines a transaction scope that synchronization might apply to.
* As a consequence, the same resources (a JDBC {@code Connection}, a
* Hibernate {@code Session}, etc) will be shared for the entire specified
* scope. Note that the exact behavior depends on the actual synchronization
* configuration of the transaction manager!
* <p>In general, use {@code PROPAGATION_SUPPORTS} with care! In particular, do
* not rely on {@code PROPAGATION_REQUIRED} or {@code PROPAGATION_REQUIRES_NEW}
* <i>within</i> a {@code PROPAGATION_SUPPORTS} scope (which may lead to
* synchronization conflicts at runtime). If such nesting is unavoidable, make sure
* to configure your transaction manager appropriately (typically switching to
* "synchronization on actual transaction").
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#SYNCHRONIZATION_ON_ACTUAL_TRANSACTION
*/
int PROPAGATION_SUPPORTS = 1; /**
* Support a current transaction; throw an exception if no current transaction
* exists. Analogous to the EJB transaction attribute of the same name.
* <p>Note that transaction synchronization within a {@code PROPAGATION_MANDATORY}
* scope will always be driven by the surrounding transaction.
*/
int PROPAGATION_MANDATORY = 2; /**
* Create a new transaction, suspending the current transaction if one exists.
* Analogous to the EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code javax.transaction.TransactionManager} to be
* made available it to it (which is server-specific in standard Java EE).
* <p>A {@code PROPAGATION_REQUIRES_NEW} scope always defines its own
* transaction synchronizations. Existing synchronizations will be suspended
* and resumed appropriately.
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
int PROPAGATION_REQUIRES_NEW = 3; /**
* Do not support a current transaction; rather always execute non-transactionally.
* Analogous to the EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code javax.transaction.TransactionManager} to be
* made available it to it (which is server-specific in standard Java EE).
* <p>Note that transaction synchronization is <i>not</i> available within a
* {@code PROPAGATION_NOT_SUPPORTED} scope. Existing synchronizations
* will be suspended and resumed appropriately.
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
int PROPAGATION_NOT_SUPPORTED = 4; /**
* Do not support a current transaction; throw an exception if a current transaction
* exists. Analogous to the EJB transaction attribute of the same name.
* <p>Note that transaction synchronization is <i>not</i> available within a
* {@code PROPAGATION_NEVER} scope.
*/
int PROPAGATION_NEVER = 5; /**
* Execute within a nested transaction if a current transaction exists,
* behave like {@link #PROPAGATION_REQUIRED} else. There is no analogous
* feature in EJB.
* <p><b>NOTE:</b> Actual creation of a nested transaction will only work on
* specific transaction managers. Out of the box, this only applies to the JDBC
* {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
* when working on a JDBC 3.0 driver. Some JTA providers might support
* nested transactions as well.
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
*/
int PROPAGATION_NESTED = 6;
理解PROPAGATION_NESTED的关键是savepoint。他与REQUIRES_NEW的区别REQUIRES_NEW另起一个事务,将会与它的父事务相互独立,而Nested的事务和父事务是相依的,它的提交是要等待和父事务一块提交的。也就是说,如果父事务最后回滚,它也要回滚的;如果子事务回滚而且异常被捕获,父事务无感知,则父事务不回滚。
public class TransactionalDemo {
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRES_NEW)
public void doSth() {
// do something
}
}
3、 事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
/**
* Return the transaction timeout.
* <p>Must return a number of seconds, or {@link #TIMEOUT_DEFAULT}.
* <p>Only makes sense in combination with {@link #PROPAGATION_REQUIRED}
* or {@link #PROPAGATION_REQUIRES_NEW}.
* <p>Note that a transaction manager that does not support timeouts will throw
* an exception when given any other timeout than {@link #TIMEOUT_DEFAULT}.
* @return the transaction timeout
*/
int getTimeout();
4、 事务只读属性
只读事务用于只读取但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。默认为读写事务。
//@return {@code true} if the transaction is to be optimized as read-only
boolean isReadOnly();
5、 事务回滚规则
指示Spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出哪些异常时回滚事务,包括checked异常。也可以明确定义哪些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
Spring的事务边界是在调用业务方法之前开始的,通过调用connection.setAutoCommit(false)激活事务,业务方法执行完毕之后来执行commit or rollback(Spring默认取决于是否抛出runtime异常)以结束事务。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。一般不需要在业务方法中catch异常,如果非要catch,在做完你想做的工作后(比如关闭文件等)一定要抛出runtime exception;否则,Spring会将你的操作commit,这样就会产生脏数据,所以你的catch代码是画蛇添足。如:
try {
// bisiness logic code
} catch(Exception e) {
// handle the exception
}
由此可以推知,在Spring中如果某个业务方法被一个try catch整个包裹起来,那么这个业务方法也就等于脱离了Spring事务的管理,因为没有任何异常会从业务方法中抛出!全被捕获并吞掉,导致Spring异常抛出触发事务回滚策略失效。不过,如果在catch代码块中采用页面硬编码的方式使事务显式的回滚,这样写也未尝不可。
预知后事如何,请听下回分解——Spring Boot事务管理(下)