介绍
Spring对事务的支持提供了编程式事务和声明式事务(基于xml和注解)配置方式。Spring最终也是通过jdbc调用了数据库事务,其周期是一个connection。Spring并不直接管理事务,而是提供了多种事务管理器。我们来看一张Spring事务管理器相关图:
我们可以看到核心的平台事务管理器接口PlatformTransactionManager(继承了TransactionManager接口),接口中涉及到事务定义属性TransactionDefinition和事务状态TransactionStatus。抽象平台事务管理器AbstractPlatformTransactionManager实现了PlatformTransactionManager接口,用模版方法限定了这个方法调用执行的流程,子类只需要完成模版回调方法即可。特定ORM框架的事务管理器框架继承了AbstractPlatformTransactionManager进行了自身的实现。
接下来我们先简单介绍下事务,然后对事务定义属性进行详细介绍,特别是事务传播特性。另外我们会介绍spring事务实现的两种方式以及事务失效场景举例。
事务简单介绍
事务就是一组原子性的sql,或者说一个独立的工作单元。如果其中一条语句因崩溃或其它原因无法执行,那么所有语句都不会执行。也就是说,事务内的语句要么全部执行成功,要么全部执行失败。例如转账操作A转账给B, A账户金额减少,B账户金额增加就要放到一个事务中处理。如果A金额减少出现问题B账户金额未增加那是肯定不允许的。
接下来我们简单介绍下事务的特性和事务隔离级别。
事务特性
一个运行良好的事务处理系统,必须具备具有ACID这几个标准的特征。
原子性(atomicity):一个事务必须被视为不可分割的最小工作单元,事务所有操作要么全部提交成功,要么全部回滚失败,对于一个事务来说,不可能只执行其中一部分操作。
一致性(consistency):事务提交后,所有数据状态是一致的,如A给B转账,A账户减少100元后,B账户必定增加100元。
隔离性(isolation):通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。(如果多个事务并发执行,每个事务的修改对其它事务都是隔离的)
持久性(durability):一旦事务提交,则其所做的修改就会永久保存到数据库中。即使系统故障或崩溃,数据也不会丢失。
事务的隔离级别
在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。我们可以根据自己需要选择其中的隔离级别。
READ UNCOMMITTED(未提交读):此级别事务中的修改,即使没有提交,对其它事务也都是可见的。事务可以读取到其它事务未提交的数据,很有可能造成脏读。例如事务A修改了一个记录还未提交被事务B读取到了,但后来事务A发生异常回滚了,修改的记录也就不生效了,而B事务在此期间读取了回滚的无效数据。
READ COMMITTED(提交读):一个事务从开始直到提交之前,所做的任何修改对其它事务都是不可见的。Oracle默认的隔离级别就是提交读。也被称为不可重复读,因为两次执行同样的查询,可能会得到不一样的结果。例如A事务进行中第一次查询某同学账户余额为100,在第二次查询前B事务已经将账户余额减少了20 并提交了事务,则在第二次查询的时候某同学账户余额为80,和第一次查询的信息不一致。
REPEATABLE READ(可重复读):保证同一事务中多次读取同样记录的结果是一致的。Mysql默认采用的就是可重复读的隔离级别。REPEATABLE READ解决了脏读和不可重复读的问题,但无法解决幻读的问题。幻读指的是当某个事务在读取某个范围的记录的时候,另外一个事务又在这个范围内增加了新的记录,当之前的事务在此读取该范围记录时,会产生幻行(Phanton Row)。Mysql的InnoDB存储引擎通过多版本并发控制(MVCC, Multiversion Concurrency Control)解决了幻读的问题。
SERIALIZABLE(可串行化):SERIALIZABLE为最高高隔离级别。通过强制事务串行执行,避免前面所说的脏读,不可重复读,幻读。SERIALIZABLE会在读取的每一行数据加锁,可能导致大量超时和锁争用的问题。实际很少使用该级别。
对四个隔离级别可能产生问题的汇总如下:
| 隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
|---|---|---|---|---|
| READ UNCOMMITTED | YES | YES | YES | NO |
| READ COMMITTED | NO | YES | YES | NO |
| REPEATABLE READ | NO | NO | YES | NO |
| SERIALIZABLE | NO | NO | NO | YES |
我们会发现随着隔离级别越来越严格,效率也由高到低变化。
spring事务定义属性
spring事务定义属性的类如下所示:
public interface TransactionDefinition {
//传播属性
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
//隔离属性
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
//默认事务超时时间
int TIMEOUT_DEFAULT = -1;
default int getPropagationBehavior() {
return 0;
}
default int getIsolationLevel() {
return -1;
}
default int getTimeout() {
return -1;
}
//默认是否只读属性
default boolean isReadOnly() {
return false;
}
//事务名称
@Nullable
default String getName() {
return null;
}
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}
接下来我们依次对其属性进行介绍。
事务名称
指定事务管理器
事务隔离级别
上面事务隔离级别已经做了介绍,下面简单来说下spring配置的事务科隔离级别。
-
ISOLATION_DEFAULT:使用数据库默认隔离级别(PlatformTransactionManager默认的隔离级别)。下面的和数据库隔离级别对应。
-
ISOLATION_READ_UNCOMMITTED:读未提交,上面介绍事务隔离级别已提到,可能会出现脏读,不可重复读和幻读
-
ISOLATION_READ_COMMITTED:读已提交,可阻止脏读,但可能产生不可重复读和幻读。
-
ISOLATION_REPEATABLE_READ:可重复读,可阻止脏读和不可重复读,但是可能产生幻读
-
ISOLATION_SERIALIZABLE:最高的可串行的隔离级别,完全满足ACID特性
事务传播级别
Spring定义了七种事务传播行为,指定了当事务方法被其它事务方法调用,是延续上一个事务里面还是创建一个新的事务。
-
PROPAGATION_REQUIRED:Spring默认事务传播行为。当前方法必须运行在事务当中,如果当前存在一个事务,则运行在当前事务中,如果没有当前事务,则创建一个新的事务
-
PROPAGATION_SUPPORTS:当前方法可不运行在事务中。如果当前存在事务则运行在当前事务,如果不存在则非事务执行
-
PROPAGATION_MANDATORY:方法必须运行在事务中,如果当前存在事务,则运行在当前事务中,如果不存在则会抛出异常。
-
PROPAGATION_REQUIRES_NEW:开启一个新的事务(存在自己的事务的隔离范围)。如果当前事务已经存在,则将当前事务挂起。
-
PROPAGATION_NOT_SUPPORTED:非事务执行。如果当前事务存在则会被刮挂起
-
PROPAGATION_NEVER:非事务执行,如果当前存在事务则会抛出异常
-
PROPAGATION_NESTED:开启一个嵌套事务。如果当前事务存在,则生成一个嵌套的事务。若当前没有事务,则同PROPAGATION_REQUIRED执行。需要注意下面几点:
- 嵌套的内部事务依赖于外层事务。外层事务的回滚可以引起内层嵌套事务的回滚,而内层事务的回滚(抛出异常)并不会导致外层事务的回顾。
- 嵌套事务开始执行会取得一个savepoint,当执行异常会回滚至此savepoint
- 嵌套事务是外层事务的一部分,只有外层事务commit的时候嵌套事务才会提交。
事务只读特性
默认为false(非只读属性),如果该事务方法只对数据库进行读操作,则可以设置该属性为true。通过设置为已读,数据库可能根据这种情况对事务进行一些特定的优化,减轻事务对数据库的压力,因为事务也是要消耗数据库资源的。
事务超时时间
默认TIMEOUT_DEFAULT = -1事务超时设置为-1,表示默认使用数据库事务系统的超时时间(数据库事务系统无超时时间则该事务也没有超时时间)。事务超时时间并不是数据库事务特性(一些数据库并不支持数据库超时)。事务超时就是在特定时间内事务还没执行完成,则不会等待,而是会自动回滚。事务超时是防止事务长期占用数据库资源的一种方式,也是为了系统能够更好的运行。
spring两种实现事务的方式
spring两种实现事务的方式如下:
1.编程式事务的实现,也就是通过通过spring提供的事务的相关的类编码控制事务
2.声明式事务,也就是我们熟悉的@Transaction注解
下面我们分别来介绍下
编程式事务实现
编程式主要有下面两种实现方式:
- 通过PlatformTransactionManager控制事务
- 通过TransactionTemplate控制事务
PlatformTransactionManager控制事务
Spring中的事务管理器为PlatformTransactionManager接口,控制着事务的开启,提交和回滚,我们来看下:
public interface PlatformTransactionManager extends TransactionManager {
//开启事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//事务提交
void commit(TransactionStatus var1) throws TransactionException;
//事务回滚
void rollback(TransactionStatus var1) throws TransactionException;
}
介绍中我们有一张Spring事务管理器的图,最下面我们可以看到PlatformTransactionManager有多个实现类来应对不同的环境。其中我们经常用的是DataSourceTransactionManager(例如jdbc,ibatis等)。
PlatformTransactionManager使用流程,当使用debug日志级别的时候可以看到事务相关日志:
@Autowired
private PlatformTransactionManager transactionManager;
@Override
public void testTransaction() {
TransactionStatus status = null;
try {
//手动开启事务
status = transactionManager.getTransaction(new DefaultTransactionDefinition());
//TODO 业务
//事务提交
transactionManager.commit(status);
} catch (Exception e) {
//事务回滚
if(null != status){
transactionManager.rollback(status);
}
log.error("age upd exception", e);
}
}
TransactionTemplate控制事务
TransactionTemplate继承了DefaultTransactionDefinition,有默认的事务定义,也可自定义隔离级别、传播属性等。TransactionTemplate需要一个PlatformTransactionManager事务管理器执行事务的操作。
我们先来看下TransactionTemplate的源码:
public class TransactionTemplate extends DefaultTransactionDefinition
implements TransactionOperations, InitializingBean {
/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
@Nullable
private PlatformTransactionManager transactionManager;
/**
* Construct a new TransactionTemplate for bean usage.
* <p>Note: The PlatformTransactionManager needs to be set before
* any {@code execute} calls.
* @see #setTransactionManager
*/
public TransactionTemplate() {
}
/**
* Construct a new TransactionTemplate using the given transaction manager.
* @param transactionManager the transaction management strategy to be used
*/
public TransactionTemplate(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* Construct a new TransactionTemplate using the given transaction manager,
* taking its default settings from the given transaction definition.
* @param transactionManager the transaction management strategy to be used
* @param transactionDefinition the transaction definition to copy the
* default settings from. Local properties can still be set to change values.
*/
public TransactionTemplate(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition) {
super(transactionDefinition);
this.transactionManager = transactionManager;
}
/**
* Set the transaction management strategy to be used.
*/
public void setTransactionManager(@Nullable PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* Return the transaction management strategy to be used.
*/
@Nullable
public PlatformTransactionManager getTransactionManager() {
return this.transactionManager;
}
@Override
public void afterPropertiesSet() {
if (this.transactionManager == null) {
throw new IllegalArgumentException("Property 'transactionManager' is required");
}
}
//事务控制管理主要依赖于这个方法
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
/**
* Perform a rollback, handling rollback exceptions properly.
* @param status object representing the transaction
* @param ex the thrown application exception or error
* @throws TransactionException in case of a rollback error
*/
private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
logger.debug("Initiating transaction rollback on application exception", ex);
try {
this.transactionManager.rollback(status);
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
@Override
public boolean equals(@Nullable Object other) {
return (this == other || (super.equals(other) && (!(other instanceof TransactionTemplate) ||
getTransactionManager() == ((TransactionTemplate) other).getTransactionManager())));
}
}
源码中我们可以看到PlatformTransactionManager,可想而知,TransactionTemplate是对PlatformTransactionManager的更进一步的封装。TransactionTemplate主要依赖于源码中的execute方法进行事务管理。这个方法的参数为TransactionCallback:
@FunctionalInterface
public interface TransactionCallback<T> {
/**
* Gets called by {@link TransactionTemplate#execute} within a transactional context.
* Does not need to care about transactions itself, although it can retrieve and
* influence the status of the current transaction via the given status object,
* e.g. setting rollback-only.
* <p>Allows for returning a result object created within the transaction, i.e. a
* domain object or a collection of domain objects. A RuntimeException thrown by the
* callback is treated as application exception that enforces a rollback. Any such
* exception will be propagated to the caller of the template, unless there is a
* problem rolling back, in which case a TransactionException will be thrown.
* @param status associated transaction status
* @return a result object, or {@code null}
* @see TransactionTemplate#execute
* @see CallbackPreferringPlatformTransactionManager#execute
*/
@Nullable
T doInTransaction(TransactionStatus status);
}
其只有一个方法,实现该接口的只有一个抽象类TransactionCallbackWithoutResult:
public abstract class TransactionCallbackWithoutResult implements TransactionCallback<Object> {
@Override
@Nullable
public final Object doInTransaction(TransactionStatus status) {
doInTransactionWithoutResult(status);
return null;
}
/**
* Gets called by {@code TransactionTemplate.execute} within a transactional
* context. Does not need to care about transactions itself, although it can retrieve
* and influence the status of the current transaction via the given status object,
* e.g. setting rollback-only.
* <p>A RuntimeException thrown by the callback is treated as application
* exception that enforces a rollback. An exception gets propagated to the
* caller of the template.
* <p>Note when using JTA: JTA transactions only work with transactional
* JNDI resources, so implementations need to use such resources if they
* want transaction support.
* @param status associated transaction status
* @see TransactionTemplate#execute
*/
protected abstract void doInTransactionWithoutResult(TransactionStatus status);
}
可以看出实现org.springframework.transaction.support.TransactionTemplate#execute的参数有两种:
- 有返回值
@Override
@Nullable
public final Object doInTransaction(TransactionStatus status) {
doInTransactionWithoutResult(status);
return null;
}
- 无返回值
protected abstract void doInTransactionWithoutResult(TransactionStatus status);
我们使用TransactionTemplate进行事务属性设置,如下所示:
//设置传播属性
/**
* Set the propagation behavior. Must be one of the propagation constants
* in the TransactionDefinition interface. Default is PROPAGATION_REQUIRED.
* <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
* {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
* transactions. Consider switching the "validateExistingTransactions" flag to
* "true" on your transaction manager if you'd like isolation level declarations
* to get rejected when participating in an existing transaction with a different
* isolation level.
* <p>Note that a transaction manager that does not support custom isolation levels
* will throw an exception when given any other level than {@link #ISOLATION_DEFAULT}.
* @throws IllegalArgumentException if the supplied value is not one of the
* {@code PROPAGATION_} constants
* @see #PROPAGATION_REQUIRED
*/
public final void setPropagationBehavior(int propagationBehavior) {
if (!constants.getValues(PREFIX_PROPAGATION).contains(propagationBehavior)) {
throw new IllegalArgumentException("Only values of propagation constants allowed");
}
this.propagationBehavior = propagationBehavior;
}
//设置隔离级别
/**
* Set the isolation level. Must be one of the isolation constants
* in the TransactionDefinition interface. Default is ISOLATION_DEFAULT.
* <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
* {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
* transactions. Consider switching the "validateExistingTransactions" flag to
* "true" on your transaction manager if you'd like isolation level declarations
* to get rejected when participating in an existing transaction with a different
* isolation level.
* <p>Note that a transaction manager that does not support custom isolation levels
* will throw an exception when given any other level than {@link #ISOLATION_DEFAULT}.
* @throws IllegalArgumentException if the supplied value is not one of the
* {@code ISOLATION_} constants
* @see #ISOLATION_DEFAULT
*/
public final void setIsolationLevel(int isolationLevel) {
if (!constants.getValues(PREFIX_ISOLATION).contains(isolationLevel)) {
throw new IllegalArgumentException("Only values of isolation constants allowed");
}
this.isolationLevel = isolationLevel;
}
//设置是否只读,默认为false
/**
* Set whether to optimize as read-only transaction.
* Default is "false".
* <p>The read-only flag applies to any transaction context, whether backed
* by an actual resource transaction ({@link #PROPAGATION_REQUIRED}/
* {@link #PROPAGATION_REQUIRES_NEW}) or operating non-transactionally at
* the resource level ({@link #PROPAGATION_SUPPORTS}). In the latter case,
* the flag will only apply to managed resources within the application,
* such as a Hibernate {@code Session}.
* <p>This just serves as a hint for the actual transaction subsystem;
* it will <i>not necessarily</i> cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction.
*/
public final void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
//设置事务执行超时时间
/**
* Set the timeout to apply, as number of seconds.
* Default is TIMEOUT_DEFAULT (-1).
* <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
* {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
* transactions.
* <p>Note that a transaction manager that does not support timeouts will throw
* an exception when given any other timeout than {@link #TIMEOUT_DEFAULT}.
* @see #TIMEOUT_DEFAULT
*/
public final void setTimeout(int timeout) {
if (timeout < TIMEOUT_DEFAULT) {
throw new IllegalArgumentException("Timeout must be a positive integer or TIMEOUT_DEFAULT");
}
this.timeout = timeout;
}
对不同的属性我们只需要对TransactionTemplate进行set设置即可。
通过上面介绍我们可以知道TransactionTemplate可以进行有返回值和无返回值两种方式的事务管理,我们来看下。
- 有返回值的doInTransaction
@Override
public void testTransaction() {
transactionTemplate.execute(status -> {
//TODO 业务方法
return new Object();
});
}
- 无返回值的
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
//TODO 业务方法
}
});
可能你会看到我并没有手动进行回滚和提交,那是因为在模版方法里面已经包含了该逻辑,我们再来看下execute方法:
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
//开启事务
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
//事务执行
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
我们看到事务执行的action.doInTransaction(status)方法已经在try-catch中,从try-catch中catch的异常可以看出不管是运行时异常(RuntimeException)还是Error都进行了roll back,另外还有抛出的未声明的检查异常都进行了回滚。在该方法最后执行无误的情况下会进行commit。
上面我们介绍了两种编程式事务的实现,相对于声明式事务,因为此种方式通过编码的方式实现,所以可以实现更细粒度的事务。但声明式事务更简单方便,也是我们平时使用最多的一种方式。
声明式事务实现
spring默认集成事务,所以无需手动开启@EnableTransactionManagement注解,就可以使用@Transactional(在类或者方法上)注解进行事务管理。Spring对声明式事务是通过AOP代理实现的(自动创建Bean的Porxy实现)。
我们先来看下@Transactional注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* Alias for {@link #transactionManager}.
* @see #transactionManager
*/
@AliasFor("transactionManager")
String value() default "";
/**
* A <em>qualifier</em> value for the specified transaction.
* <p>May be used to determine the target transaction manager,
* matching the qualifier value (or the bean name) of a specific
* {@link org.springframework.transaction.PlatformTransactionManager}
* bean definition.
* @since 4.2
* @see #value
*/
@AliasFor("value")
String transactionManager() default "";
/**
* The transaction propagation type.
* <p>Defaults to {@link Propagation#REQUIRED}.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* The transaction isolation level.
* <p>Defaults to {@link Isolation#DEFAULT}.
* <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
* {@link Propagation#REQUIRES_NEW} since it only applies to newly started
* transactions. Consider switching the "validateExistingTransactions" flag to
* "true" on your transaction manager if you'd like isolation level declarations
* to get rejected when participating in an existing transaction with a different
* isolation level.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getIsolationLevel()
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setValidateExistingTransaction
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* The timeout for this transaction (in seconds).
* <p>Defaults to the default timeout of the underlying transaction system.
* <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
* {@link Propagation#REQUIRES_NEW} since it only applies to newly started
* transactions.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout()
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* A boolean flag that can be set to {@code true} if the transaction is
* effectively read-only, allowing for corresponding optimizations at runtime.
* <p>Defaults to {@code false}.
* <p>This just serves as a hint for the actual transaction subsystem;
* it will <i>not necessarily</i> cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction
* but rather silently ignore the hint.
* @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
* @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
*/
boolean readOnly() default false;
/**
* Defines zero (0) or more exception {@link Class classes}, which must be
* subclasses of {@link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>By default, a transaction will be rolling back on {@link RuntimeException}
* and {@link Error} but not on checked exceptions (business exceptions). See
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
* for a detailed explanation.
* <p>This is the preferred way to construct a rollback rule (in contrast to
* {@link #rollbackForClassName}), matching the exception class and its subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
* @see #rollbackForClassName
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* Defines zero (0) or more exception names (for exceptions which must be a
* subclass of {@link Throwable}), indicating which exception types must cause
* a transaction rollback.
* <p>This can be a substring of a fully qualified class name, with no wildcard
* support at present. For example, a value of {@code "ServletException"} would
* match {@code javax.servlet.ServletException} and its subclasses.
* <p><b>NB:</b> Consider carefully how specific the pattern is and whether
* to include package information (which isn't mandatory). For example,
* {@code "Exception"} will match nearly anything and will probably hide other
* rules. {@code "java.lang.Exception"} would be correct if {@code "Exception"}
* were meant to define a rule for all checked exceptions. With more unusual
* {@link Exception} names such as {@code "BaseBusinessException"} there is no
* need to use a FQN.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(String exceptionName)}.
* @see #rollbackFor
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
String[] rollbackForClassName() default {};
/**
* Defines zero (0) or more exception {@link Class Classes}, which must be
* subclasses of {@link Throwable}, indicating which exception types must
* <b>not</b> cause a transaction rollback.
* <p>This is the preferred way to construct a rollback rule (in contrast
* to {@link #noRollbackForClassName}), matching the exception class and
* its subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(Class clazz)}.
* @see #noRollbackForClassName
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* Defines zero (0) or more exception names (for exceptions which must be a
* subclass of {@link Throwable}) indicating which exception types must <b>not</b>
* cause a transaction rollback.
* <p>See the description of {@link #rollbackForClassName} for further
* information on how the specified names are treated.
* <p>Similar to {@link org.springframework.transaction.interceptor.NoRollbackRuleAttribute#NoRollbackRuleAttribute(String exceptionName)}.
* @see #noRollbackFor
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
String[] noRollbackForClassName() default {};
}
注解中的propagation,isolation,timeout,readOnly属性和编程式事务中所讲一致,在此不再赘述。我们主要来介绍下rollbackFor(rollbackForClassName,noRollbackFor,noRollbackForClassName类似)。 这个属性指明了异常类型进行回滚,默认情况下抛出当程序发生 RuntimeException 和 Error 的这两种异常的时候会进行回滚。但如果抛出检查异常(checkedExcetions)则不会回滚.我们来模拟一个简单的检查异常(非运行时异常)来看下。
- 首先我们简单创建一张表,添加两条记录
- 我们给每个人的age进行加1操作并模拟FileNotFoundException
@Transactional
@Override
public void testTransaction() throws FileNotFoundException {
userMapper.ageAdd(1, 1);
userMapper.ageAdd(2, 1);
//模拟FileNotFoundException异常
BufferedReader br = new BufferedReader(new FileReader("/user/local"));
}
- 运行以后会抛出java.io.FileNotFoundException: /user/local (No such file or directory)异常。我们来看下数据是否会滚,发现数据库数据进行了更新,所以并没回滚。
上面只是简单举了一个例子,声明式事务这点也是值得注意的地方,上面示例如果我们logger的日志级别为debug,我们可以看到在刚进入方法的时候,我们可以看到创建事务的日志:
[2021-12-22 00:30:10:001] DEBUG [http-nio-9092-exec-3] o.s.j.d.DataSourceTransactionManager[370] - Creating new transaction with name [com.wk.manage.biz.service.impl.DemoServiceImpl.testTransaction]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
[2021-12-22 00:30:10:002] DEBUG [http-nio-9092-exec-3] o.s.j.d.DataSourceTransactionManager[265] - Acquired Connection [HikariProxyConnection@1644330035 wrapping com.mysql.cj.jdbc.ConnectionImpl@31a526ca] for JDBC transaction
[2021-12-22 00:30:10:002] DEBUG [http-nio-9092-exec-3] o.s.j.d.DataSourceTransactionManager[283] - Switching JDBC Connection [HikariProxyConnection@1644330035 wrapping com.mysql.cj.jdbc.ConnectionImpl@31a526ca] to manual commit
在执行抛出异常后我们可以看到事务提交的日志:
[2021-12-22 00:31:20:386] DEBUG [http-nio-9092-exec-3] o.s.j.d.DataSourceTransactionManager[741] - Initiating transaction commit
[2021-12-22 00:31:20:387] DEBUG [http-nio-9092-exec-3] o.s.j.d.DataSourceTransactionManager[328] - Committing JDBC transaction on Connection [HikariProxyConnection@1644330035 wrapping com.mysql.cj.jdbc.ConnectionImpl@31a526ca]
[2021-12-22 00:31:20:389] DEBUG [http-nio-9092-exec-3] o.s.j.d.DataSourceTransactionManager[387] - Releasing JDBC Connection [HikariProxyConnection@1644330035 wrapping com.mysql.cj.jdbc.ConnectionImpl@31a526ca] after transaction
有人可能会问了,这个在源码中应该可以找到逻辑依据,我们再来看下这个属性:
/**
* Defines zero (0) or more exception {@link Class classes}, which must be
* subclasses of {@link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>By default, a transaction will be rolling back on {@link RuntimeException}
* and {@link Error} but not on checked exceptions (business exceptions). See
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
* for a detailed explanation.
* <p>This is the preferred way to construct a rollback rule (in contrast to
* {@link #rollbackForClassName}), matching the exception class and its subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
* @see #rollbackForClassName
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
Class<? extends Throwable>[] rollbackFor() default {};
上面文字描述已经告诉我们默认什么异常情况会进行回滚并指出了判断的具体地方:
org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
那么我们来看下这个方法:
/**
* The default behavior is as with EJB: rollback on unchecked exception
* ({@link RuntimeException}), assuming an unexpected outcome outside of any
* business rules. Additionally, we also attempt to rollback on {@link Error} which
* is clearly an unexpected outcome as well. By contrast, a checked exception is
* considered a business exception and therefore a regular expected outcome of the
* transactional business method, i.e. a kind of alternative return value which
* still allows for regular completion of resource operations.
* <p>This is largely consistent with TransactionTemplate's default behavior,
* except that TransactionTemplate also rolls back on undeclared checked exceptions
* (a corner case). For declarative transactions, we expect checked exceptions to be
* intentionally declared as business exceptions, leading to a commit by default.
* @see org.springframework.transaction.support.TransactionTemplate#execute
*/
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
现在我们就很清楚声明式事务为什么非运行时异常为什么不能回滚了。前面我们也介绍了编程式事务TransactionTemplate不仅会对RuntimeException和Error进行回滚,对未声明的检查异常也会进行回滚。可见TransactionTemplate编程事务对回滚异常覆盖范围更广,我们无需关心异常类型。但在声明式事务中,通常我们需要指定rollbackFor,如下所示:
@Transactional(rollbackFor = Exception.class)
当然如果我们的异常体系都是从RuntimeException派生的话,我们也可以不必关注rollbackFor。
声明式事务我们就介绍到这,具体源码的话有兴趣的话可以自行研究下。
事务边界
通常我们事务的开始和结束如下所示:
@Transactional(rollbackFor = Exception.class)
@Override
public void testTransaction() {//事务开始
}//事务结束
但是这只是最简单的情况,如果内部调用了其它的service的方法,这个方法需不需要新生成一个事务,或者它内部异常的话外层事务是不是需要回滚?这块就涉及到事务的传播特性了,在上面我们已经介绍,就不再进行赘述了。
事务失效场景
我们来简单看几个事务失效的场景,在平时开发中需要注意下。我们以声明式事务为例。
- @Transactional需要放在public方法上才会生效,如果放在其它的(private、default、protect)的方法上则不会生效。
在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法我们可以看到非公共方法的校验。
/**
* Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
* {@link #getTransactionAttribute} is effectively a caching decorator for this method.
* <p>As of 4.1.8, this method can be overridden.
* @since 4.1.8
* @see #getTransactionAttribute
*/
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
//不允许使用非公共方法
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
2.service方法内部未声明事务注解方法调用该service内部有@Transactional注解的方法事务不会生效。如下所示:
public class DemoService {
public void testTransaction() {
//调用内部有事务注解的test方法
test();
}
@Transactional
public void test() {
}
}
@Transactional的原理是Spring Aop生成代理对象,则同一个类中无事务注解方法调用有事务注解方法的话是不会生效的
3.多线程调用的时候,主线程和新创建的线程不是在同一线程中。主线程带有注解@Transactional开启一个线程调用另外一个带有@Transactional的注解的方法时候,两个线程中数据库连接不同(开启两个事务)。如下示例所示:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private JobService jobService;
@Transactional
public void addUser(){
//业务
new Thread(()->{
jobService.addJob();
}).start();
}
}
@Service
public class JobService {
@Autowired
private UserMapper userMapper;
@Transactional
public void addJob(){
//业务
}
}
两个线程数据库连接不同,事务也就不同,则新创建线程中异常回滚的话,主线程的操作并不会回滚。
for update
这里再简单说下for update,一般我们使用for update也是为了锁定数据库中的某些记录,对于锁定整张表谨慎使用。for update的使用需要包含在事务中。另外需要注意for update可能造成资源互相等待死锁的问题,对于oracle来说这种情况会自动检测死锁对其中一个事务报出ORA-00060来使程序继续运行下去。关于死锁的详细介绍可以参考:Deadlocks
总结
我们需要注意try-catch的使用时导致异常不能抛出而使事务无法回滚的情况。对于@Transactional的使用需要注意默认的是RuntimeException和Error异常会进行回滚。对于检查异常例如FileNotFoundException是不会进行回滚的。一般我们通过指定rollbackFor属性来定义可回滚异常。
另外对于声明式事务和编程式事务来说的话,声明式事务可能会造成大事务的问题(可能事务的范围比较大),还可能出现误用导致失效,但其使用确实简单方便,也是我们经常使用的。编程式事务进行编码一般不会造成事务失效问题,而且可以缩小事务的范围,但需要多一些编程。
参考书籍:《高性能MySQL》