Spring事务详解

2023/12/26 Spring框架

# 1、事务

# 1.1、什么是事务?

事务是指数据库中一组被绑定在一起的操作,这些操作可以被看作是一个逻辑上的单一工作单元。在一个事务中,要么所有的操作都被成功执行,要么所有的操作都被回滚,即使中间出现了错误。

一组SQL语句的集合,集合有多条SQL,要么都不执行,要么都执行,这些SQL语句的执行是一致的,作为一个整体执行。

当操作涉及到多个表或者多条增删改SQL语句时,需要保证这些SQL语句要么都成功,要么都失败,这时就需要使用事务。

案例:银行转账

  • A向B转账1000元,成功情况是:A账户余额减少1000元,B账户余额增加1000元。
  • 如果转账失败,A账户和B账户的余额都不变。

上述案例就要保证账户余额的一致性,这就需要使用事务。

# 1.2、事务的特性(ACID)

事务具有以下四个特性:

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

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

事务的特性图解

# 1.3、以往项目处理事务

对于以往的项目常用的有以下几种事务处理方案:

  • JDBC方式:开始事务,执行SQL,提交/回滚(conn.commit/conn.rollback),老项目用的比较多。
  • Mybatis方式:通过SqlSession.commit()SqlSession.rollback()进行提交/回滚,SSM项目中比较常用。
  • Hibernat方式:通过Session.commit()Session.rollback()进行提交/回滚,SSH项目中比较常用。

# 1.4、以往项目处理事务缺点

  • 不同的数据库访问技术,处理事务的对象,方法不同
  • 需要了解不同的数据库访问技使用事务的原理
  • 需要掌握多种数据库的处理逻辑。什么时候提交/回滚

多种数据库的访问技术,有不同的事务处理的机制,对象,方法。无论是学习还是使用成本都高。

# 1.5、Spring解决事务问题

spring提供一种处理事务的统一模型,能使用统一步骤,方式完成对多种不同数据库访问技术的事务处理。

# 2、Spring事务管理两种方式

是否支持事务还要取决与数据库引擎。比如MySQL,如果是 innodb 引擎是可以支持事务的。但如果是myisam引擎,则不支持事务。 可以参考:MySQL架构及存储引擎

  • 编程式事务管理

编程式事务管理是通过在代码中显式地编写事务控制逻辑来管理事务。Spring提供了TransactionTemplatePlatformTransactionManager来支持编程式事务管理。开发者可以使用TransactionTemplate进行事务的开始、提交和回滚操作,还可以通过PlatformTransactionManager来获取和管理底层的事务资源。

  • 声明式事务管理

声明式事务管理是通过在配置文件中声明事务的管理规则来管理事务。开发者只需在需要进行事务管理的方法上使用@Transactional注解,Spring框架会自动为带有@Transactional注解的方法添加事务管理功能。通过声明式事务管理,开发者可以将事务的关注点从业务逻辑中分离出来,使代码更加清晰简洁。

# 2.1、编程式事务管理

编程式事务管理案例,在实际开发中用的比较少,但可以帮助理解Spring事务管理。

  • TransactionTemplate 案例
@Autowired
private TransactionTemplate transactionTemplate;

public void testTransaction() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    // ....  业务代码
                } catch (Exception e){
                    //回滚
                    transactionStatus.setRollbackOnly();
                }

            }
        });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • TransactionManager 案例
@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {

    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

    try {
        // ....  业务代码
        transactionManager.commit(status);
    } catch (Exception e) {
        transactionManager.rollback(status);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 2.2、声明式事务管理

推荐使用基于@Transactional的全注解方式来实现声明式事务管理,这种方式代码侵入性最小。

@Transactional(propagation = Propagation.REQUIRED)
public void xyMethod {
  // 业务代码
  A a = new A();
  B b = new B();
  a.bMethod();
  b.bMethod();

  // 其他业务...
}
1
2
3
4
5
6
7
8
9
10

# 3、Spring事务管理接口

Spring框架中三个重要事务管理相关接口:

  • PlatformTransactionManager:Spring的事务管理器、事务策略的核心。
  • TransactionDefinition:定义了事务的属性(事务隔离级别、传播行为、超时、只读、回滚规则)。
  • TransactionStatus:事务的状态。

PlatformTransactionManager负责事务的执行和管理,TransactionDefinition定义事务的属性和行为,而TransactionStatus则提供了事务状态的查询和控制。通过这三个接口,Spring框架实现了灵活的事务管理功能,以满足不同的业务需求。

# 3.1、事务接口

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

PlatformTransactionManager接口源码

public interface PlatformTransactionManager {
    //获得事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //提交事务
    void commit(TransactionStatus var1) throws TransactionException;
    //回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}
1
2
3
4
5
6
7
8

PlatformTransactionManager接口提供了三个方法,分别用于获取事务、提交事务和回滚事务。

通过PlatformTransactionManager接口,Spring为部分平台提供对应的事务管理器:

  • DataSourceTransactionManager:JDBC事务管理器。
  • HibernateTransactionManager:Hibernate事务管理器。
  • JpaTransactionManager:JPA事务管理器。

PlatformTransactionManager接口实现

对于其他平台,因为不同的平台(如关系型数据库、消息队列等)有各自的事务管理机制和规范。因此,Spring为这些平台提供特定的事务管理器接口PlatformTransactionManager,以确保与这些平台的兼容性和正确的事务管理。

# 3.2、事务属性

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

事务属性描述了事务的特性或行为,事务属性是事务管理的一部分,用于控制事务的行为和执行环境。

事务属性包含了5个方面:

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

TransactionDefinition 接口源码

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();
}
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

# 3.3、事务状态

事务状态(TransactionStatus)是指事务在执行过程中的状态。

TransactionManager案例中,返回一个TransactionStatus对象。

@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
1
2
3
4
5
6

TransactionStatus 接口源码

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

# 4、事务属性详解

在实际开发中,用的比较多的就是@Transactional注解来开启事务,在@Transactional注解上使用的参数就是配置事务属性。

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, 
                timeout = -1, readOnly = false,rollbackFor = RuntimeException.class)
public void xyMethod {
    // 业务代码
}
1
2
3
4
5

  • propagation:事务传播行为
  • isolation:事务隔离级别
  • timeout:事务超时时间(以秒为单位)
  • readOnly:事务是否只读
  • rollbackFor:事务回滚规则

对于这些参数是什么意思,我们需要对事务属性进行详细的了解。

# 4.1、事务隔离级别

事务隔离级别是数据库事务处理过程中,一个事务所能看到其他事务中修改的数据的程度。在Spring框架中,事务隔离级别决定了事务之间的可见性和并发控制。

Spring提供了对应枚举类:Isolation源码及解释

public enum Isolation {

    /**
     * 使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 
     * Oracle 默认采用的 READ_COMMITTED 隔离级别.
     */
    DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),

    /**
     * 最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,
     * 可能会导致脏读、幻读或不可重复读
     */
    READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),

    // 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
    READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),

    /**
     * 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,
     * 可以阻止脏读和不可重复读,但幻读仍有可能发生。
     */
    REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),

    /**
     * 最高的隔离级别,完全服从 ACID 的隔离级别。
     * 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,
     * 也就是说,该级别可以防止脏读、不可重复读以及幻读。
     * 但是这将严重影响程序的性能。通常情况下也不会用到该级别。
     */
    SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

    private final int value;

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

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

}
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
33
34
35
36
37
38
39
40
41
42

对于事务的隔离级别的脏读、幻读、不可重复读不清楚的,可以参考:TCL【事务控制语言】- 事务的隔离级别

# 4.2、事务传播行为

事务传播行为是指当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。

案例说明

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

@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
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Propagation枚举源码加解释

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),
    /**
     * 如果当前存在事务,则嵌套事务作为一个子事务运行;如果当前没有事务,则该属性表现得像Propagation.REQUIRED。
     * 这意味着如果当前有一个活动的事务,它将继续执行并允许嵌套事务;如果没有活动事务,它将开始一个新的事务。
     */
    NESTED(TransactionDefinition.PROPAGATION_NESTED);

    private final int value;

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

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

}
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 4.3、事务回滚规则

事务回滚规则是指在事务执行过程中出现异常时,系统如何撤销事务中的操作以保证数据的一致性和完整性。Spring框架中的事务回滚规则主要基于异常类型和事务的隔离级别。

默认情况下,只有当方法抛出运行时异常(即继承自RuntimeException的异常)时,Spring才会回滚事务。这意味着如果程序中发生了未处理的异常,整个事务将被回滚。

@Transactional
public void doSomething(){  
    // 业务逻辑代码
}
1
2
3
4

除了默认的事务回滚规则之外,我们也可以指定特定的异常回滚事务。

// 指定当方法抛出Exception异常时回滚事务。
@Transactional(rollbackFor=Exception.class)  
public void doSomething() throws Exception {  
    // 业务逻辑代码
}


// 指定多种异常
@Transactional(rollbackFor = {RuntimeException.class, Exception.class})  
public void doSomething() throws Exception {  
    // 这里写你的业务逻辑  
}
1
2
3
4
5
6
7
8
9
10
11
12

# 4.4、事务是否只读

只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。

TransactionDefinition只读源码

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

事务只读属性解读

  • 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性。
  • 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

# 4.5、事务超时

事务超时是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。

在Spring框架中,可以通过@Transactional注解的timeout属性来设置事务的超时时间,默认值为-1。timeout属性的值以秒为单位,表示事务的最大允许执行时间。如果事务的执行时间超过了设定的超时时间,则Spring会自动回滚该事务。

需要注意的是,事务超时时间的设置应该根据具体的业务需求和系统性能要求进行权衡。如果将超时时间设置得太短,可能会导致事务无法完成正常的业务逻辑;而如果将超时时间设置得太长,可能会导致数据库资源的浪费和系统性能的下降。因此,在实际应用中,需要根据具体情况进行合适的设置。

# 5、@Transactional详解

# 5.1、作用范围

先看下@Transactional注解的源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

	@AliasFor("transactionManager")
	String value() default "";

	@AliasFor("value")
	String transactionManager() default "";

	Propagation propagation() default Propagation.REQUIRED;

	Isolation isolation() default Isolation.DEFAULT;

	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

	boolean readOnly() default false;

	Class<? extends Throwable>[] rollbackFor() default {};

	String[] rollbackForClassName() default {};

	Class<? extends Throwable>[] noRollbackFor() default {};

	String[] noRollbackForClassName() default {};

}
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

@Transactional 的作用范围

  • 方法:该注解只能应用到 public 方法上,否则不生效。
  • :如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
  • 接口:不推荐,效果同类一样。

# 5.2、参数使用

配置案例及参数说明,上面有解释过了

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, 
                timeout = -1, readOnly = false,rollbackFor = RuntimeException.class)
public void xyMethod {
    // 业务代码
}
1
2
3
4
5

  • propagation:事务传播行为,默认:REQUIRED
  • isolation:事务隔离级别,默认:DEFAULT
  • timeout:事务超时时间(以秒为单位),默认:-1
  • readOnly:事务是否只读,默认:false
  • rollbackFor:事务回滚规则,默认:RuntimeException及其子类异常

# 5.3、工作机制

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

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

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

案例

@Service
public class MyService {

    private void method1() {
         method2();
         //......
    }
    @Transactional
     public void method2() {
         //......
      }
    }
1
2
3
4
5
6
7
8
9
10
11
12

解决方案

避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理。 开发中也常用的是AspectJ代理方式。

// 开启AspectJ代理
@EnableAspectJAutoProxy
1
2

可以参考:AOP面向切面编程-spring基于aspectj注解配置方式

# 5.4、注意事项

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