一、什么是事务?

事务是逻辑上的一组操作,要么都执行,要么都不执行。

事务能否生效,数据库引擎是否支持事务是关键。比如常用的 MySQL 数据库默认使用支持事务的 innodb 引擎。但是,如果把数据库引擎变为 myIsam,那么程序也就不再支持事务了。


事务的特性(ACID)

1.原子性(Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用。
2.一致性(Consistency):执行事务前后,数据保持一致。例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的。
3.隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所打扰,各并发事务之间数据库是独立的。
4.持久性(Durability):一个事务被提交之后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说A、I、D是手段,C是目的!
image.png

MySQL 是怎么保证原子性的?

我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚。在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用回滚日志中的信息就数据回滚到修改之前的样子即可!并且,回滚日志会先于数据库持久化道磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚之前未完成的事务。

二、详谈 Spring 对事务的支持

2.1 Spring 支持两种方式的事务管理

编程式事务管理

通过 TransactionTemplate 或者 TransactionManager 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。

使用TransactionTemplate 进行编程式事务管理的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

try {

// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}

}
});
}

使用 TransactionManager 进行编程式事务管理的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {

TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// .... 业务代码
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
声明式事务管理

推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于 @Transactional 的全注解方式使用最多)。
使用 @Transactional 注解进行事务管理的示例代码如下:

1
2
3
4
5
6
7
8
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
B b = new B();
C c = new C();
b.bMethod();
c.cMethod();
}

2.2 Spring 事务管理接口介绍

Spring 框架中,事务管理相关最重要的3个接口如下:

  • PlatformTransactionManager:(平台)事务管理器, Spring 事务策略的核心。
  • TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚原则)。
  • TransactionStatus:事务运行状态。

我们可以把 PlatformTransactionManager 接口看作是事务上层的管理者,而 TransactionDefinition 和 TransactionStatus 这两个接口可以看作是事务的描述。

PlatformTransactionManager 会根据 TransactionDefinition 的定义(比如事务超时时间、隔离级别、传播行为等)来进行事务管理,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态(比如是否新事务、是否可以回滚等等)。

PlatformTransactionManager:事务管理接口

Spring 并不直接管理事务,而是提供了多种事务管理器。Spring 事务管理器的接口是:PlatformTransactionManager。

通过这个接口,Spring 为各个平台如:JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

PlatformTransactionManager 接口的具体实现如下:
image.png

PlatformTransactionManager 接口中定义了三个方法:

1
2
3
4
5
6
7
8
9
10
11
12
package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager {
//获得事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
TransactionDefinition:事务属性

事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition 类,这个类就定义了一些基本的事务属性。

什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性包含了5个方面:

  • 隔离级别
  • 传播行为
  • 回滚规则
  • 是否只读
  • 事务超时

TransactionDefinition 接口定义了5个方法以及一些表示事务属性的常量(比如隔离级别、传播行为等)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package org.springframework.transaction;

import org.springframework.lang.Nullable;

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;
// 返回事务的传播行为,默认值为 REQUIRED。
int getPropagationBehavior();
//返回事务的隔离级别,默认值是 DEFAULT
int getIsolationLevel();
// 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
int getTimeout();
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();

@Nullable
String getName();
}
TransactionStatus:事务状态

TransactionStatus 接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息。
PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。
TransactionStatus 接口内容如下:

1
2
3
4
5
6
7
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事务
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}

2.3 事务属性详解

事务传播行为

事务传播行为是为了解决业务层方法之间相互调用的事务问题。

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

举个例子:我们在 A 类的 aMethod() 方法中调用了 B 类的 bMethod() 方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod() 方法发生异常需要回滚,如何配置事务传播行为才能让 aMethod() 方法也跟着回滚呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.xxx)
public void aMethod {
//do something
b.bMethod();
}
}

@Service
Class B {
@Transactional(propagation = Propagation.xxx)
public void bMethod {
//do something
}
}

TransactionDefinition 定义了如下几个表示传播行为的常量:

1
2
3
4
5
6
7
8
9
10
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;
......
}

不过,为了方便使用,Spring 相应地定义了一个枚举类:Propagation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package org.springframework.transaction.annotation;

import org.springframework.transaction.TransactionDefinition;

public enum Propagation {

REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

NEVER(TransactionDefinition.PROPAGATION_NEVER),

NESTED(TransactionDefinition.PROPAGATION_NESTED);

private final int value;

Propagation(int value) {
this.value = value;
}

public int value() {
return this.value;
}

}

正确的事务传播行为可能的值如下:

  1. TransactionDefinition.PROPAGATION_REQUIRED
    使用的最多的一个事务传播行为,我们平时经常使用的 @Transactional 注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个事务。也就是说:
  • 如果外部方法没有开启事务的话, Propagation.REQUIRED 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不打扰。
  • 如果外部方法开启事务并且被 Propagation.REQUIRED 的话,所有 Propagation.REQUIRED 修饰的内部方法和外部方法均属于同一事务,只要有一个方法回滚,整个事务均回滚。

举个例子:如果我们上面的 aMethod() 和bMethod() 使用的都是 PROPAGATION_REQUIRED 传播行为的话,两者使用的就是同一个事务,只要有一个方法回滚,整个事务均回滚。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.REQUIRED)
public void bMethod {
//do something
}
}
  1. TransactionDefinition.PROPAGATION_REQUIRES_NEW
    创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不打扰。

举个例子:如果我们上面的 bMethod() 使用 PROPAGATION_REQUIRES_NEW 事务传播行为修饰,aMethod() 还是用 PROPAGATION_REQUIRED 修饰的话。如果 aMethod() 发生异常回滚,bMethod()不会跟着回滚,因为 bMethod() 开启了独立的事务。但是,如果 bMethod() 抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,aMethod() 同样也会回滚,因为这个异常被 aMethod() 的事务管理机制检测到了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}

@Service
Class B {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void bMethod {
//do something
}
}

  1. TransactionDefinition.PROPAGATION_NESTED
    如果当前存在事务,就在嵌套事务内执行;如果当前没有事务,就执行与 TransactionDefinition.PROPAGATION_REQUIRED 类似的操作。也就是说:
  • 在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在。
  • 如果外部方法无事务,则单独开启一个事务,与 TransactionDefinition.PROPAGATION_REQUIRED类似。
    这里还是举个简单例子:如果 bMethod() 回滚的话,aMethod() 不会回滚。如果 aMethod() 回滚的话,bMethod() 会回滚。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Service
    Class A {
    @Autowired
    B b;
    @Transactional(propagation = Propagation.REQUIRED)
    public void aMethod {
    //do something
    b.bMethod();
    }
    }

    @Service
    Class B {
    @Transactional(propagation = Propagation.NESTED)
    public void bMethod {
    //do something
    }
    }
  1. TransactionDefinition.PROPAGATION_MANDATORY
    如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
    这个使用的很少,就不举例子来说了。

若是错误的配置以下3种事务传播行为,事务将不会发生回滚,这里就不对照案例讲解了,使用的很少。

  • TransactionDefinition.PROPAGATION_SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
事务隔离级别

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

1
2
3
4
5
6
7
8
9
public interface TransactionDefinition {
......
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
......
}

和事务传播行为一样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public enum Isolation {

DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),

READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),

READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),

REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),

SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

private final int value;

Isolation(int value) {
this.value = value;
}

public int value() {
return this.value;
}

}

下面我依次对每一种事务隔离级别进行介绍:

  • TransactionDefinition.ISOLATION_DEFAULT:使用后段数据库默认的隔离级别,MySQL 默认采用 REPEATABLE_READ 隔离级别,Oracle 默认采用的是 READ_COMMITTED 隔离级别。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,使用这个隔离级别的很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复度。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能,通常情况下也不会用到该级别。
事务超时属性

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为 -1,这表示事务的超时时间取决于底层事务系统或者没有超时时间。

事务只读属性
1
2
3
4
5
6
7
8
9
10
package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface TransactionDefinition {
......
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();

}

对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。

事务回滚规则

这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。
image.png

如果你想要回滚你定义的特定的异常类型的话,可以这样:

1
@Transactional(rollbackFor= MyException.class)
@Transactional 事务注解原理

我们知道,@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现接口,会使用 Cglib 动态代理。

createAopProxy() 方法决定了是使用 JDK 还是 Cglib 来做动态代理,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
.......
}

如果一个类或者一个类中的 public 方法上被标注 @Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被 @Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke() 方法,这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。

Spring AOP 自调用问题

当一个方法被标记了 @Transactional 注解的时候,Spring 事务管理器只会在被其他类方法调用的时候生效,而不会在一个类中方法调用生效。

这是因为 Spring AOP 工作原理决定的。因为 Spring AOP 使用动态代理来实现事务的管理,它会在运行的时候为带有 @Transactional 注解的方法生成代理对象,并在方法调用的前后应用事务逻辑。如果该方法被其他类调用,我们的代理对象就会拦截方法调用并处理事务。但是在一个类中的其他方法内部调用的时候,我们的代理对象就无法拦截到这个内部调用,因此事务也就失效了。

MyService 类中的method1()调用method2()就会导致method2()的事务失效。

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class MyService {

private void method1() {
method2();
//......
}
@Transactional
public void method2() {
//......
}
}

解决办法就是避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理。

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class MyService {

private void method1() {
((MyService)AopContext.currentProxy()).method2(); // 先获取该类的代理对象,然后通过代理对象调用method2。
//......
}
@Transactional
public void method2() {
//......
}
}

上面的代码确实可以在自调用的时候开启事务,但是这是因为使用了 AopContext.currentProxy() 方法来获取当前类的代理对象,然后通过代理对象调用 method2()。这样就相当于从外部调用了 method2(),所以事务注解才会生效。

@Transactional 的使用注意事项总结
  • @Transactional 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;
  • 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效;
  • 正确的设置 @Transactional 的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败;
  • 被 @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
  • 底层使用的数据库必须支持事务机制,否则不生效。

参考