190429-SpringBoot的事务管理

Spring Boot事务管理

一、前言

Spring事务管理可以分为两种:编程式以及声明式。

  • 编程式事务就是使用编写代码的方式,进行事务的控制。
  • 声明式事务一般通过切面编程(AOP)的方式,注入到要操作的逻辑的前后,将业务逻辑与事务处理逻辑解耦

由于使用声明式事务可以保证业务代码逻辑不会受到事务逻辑的污染, 所以在实际的工程中使用声明式事务比较多。

对于声明式事务的实现,在Java工程中一般有有两种方式:

  1. 使用配置文件(XML)进行事务规则相关规则的声明
  2. 使用@Transactional注解进行控制

这里我们着重讲解传统工程与Spring Boot进行声明式事务控制的不同。

二、传统工程与Spring Boot对事务的配置处理

传统配置文件xml

在传统的Web工程中,我们通常使用XML配置,利用Spring的AOP切面编程手段,将事务以切面的方式注入到Service的各个数据库操作方法中去:

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
<!-- dataSource数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>

    <!-- 连接池中保留的最大连接数。默认为15 -->
    <property name="maxPoolSize" value="${c3p0.pool.maxPoolSize}"/>
    <!-- 连接池中保留的最小连接数。默认为15 -->
    <property name="minPoolSize" value="${c3p0.pool.minPoolSize}" />
    <!-- 初始化时创建的连接数,应在minPoolSize与maxPoolSize之间取值。默认为3 -->
    <property name="initialPoolSize" value="${c3p0.pool.initialPoolSize}"/>
    <!-- 定义在从数据库获取新连接失败后重复尝试获取的次数,默认为30 -->
    <property name="acquireIncrement" value="${c3p0.pool.acquireIncrement}"/>
</bean>

<!-- 事务管理 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 事务通知(隔离级别、传播行为) -->
<tx:advice id="txAdivce" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="insert*" propagation="REQUIRED"/>
        <tx:method name="update*" propagation="REQUIRED"/>
        <tx:method name="delete*" propagation="REQUIRED"/>
        <tx:method name="save*" propagation="REQUIRED"/>
        <tx:method name="find*" read-only="false"/>
        <tx:method name="get*" read-only="false"/>
        <tx:method name="view*" read-only="false"/>
    </tx:attributes>
</tx:advice>

<!-- 切入事务 -->
<aop:config>
    <aop:pointcut expression="execution(* com.*.service.*.*(..))" id="txPointcut"/>
    <aop:advisor advice-ref="txAdivce" pointcut-ref="txPointcut"/>
</aop:config>

可以看到,针对事务,我们做了如下配置操作:

  1. 首先配置了【数据源】
  2. 然后配置了【事务管理器】
  3. 然后配置了【事务通知】,定义了各种方法的事务操作规范。
  4. 最后将【事务管理器】切入需要进行事务管理的Service方法中。

在Spring Boot 事务处理

在Spring Boot中的推荐操作是,使用@Transactional注解来申明事务,要在要在Spring boot中支持事务,需要做如下几个操作

  1. 首先要导入Spring boot提供的JDBC或JPA依赖
1
2
3
4
5
6
7
8
9
10
11
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-jdbc</artifactId>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
   <scope>test</scope>
</dependency>

当我们使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,Spring Boot会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager

虽然在传统的工程中也可以使用@Transactional注解来申明事务,但是还是需要使用XML来配置事务管理器(DataSourceTransactionManager)。

那么大家可能会有一个疑问,因为传统工程中使用XML配置事务时,需要给DataSourceTransactionManager事务管理器配置数据源DataSource,那么Spring Boot进行自动配置的话,
Spring Boot在注入DataSourceTransactionManager事务管理器时,是如何找到我们配置的DataSource数据源的呢?

答案是Spring Boot会自动到Spring容器中寻找我们配置好的DataSource。也即是之前我们的手动操作,现在使用Spring Boot变成了自动化操作。

三、Spring Boot事务自动配置

Springboot内部提供的事务管理器是根据autoconfigure来进行决定的。

比如当使用jpa的时候,也就是pom中加入了spring-boot-starter-data-jpa这个starter之后(之前我们分析过springboot的自动化配置原理)。

Springboot会构造一个JpaTransactionManager这个事务管理器。

而当我们使用spring-boot-starter-jdbc的时候,构造的事务管理器则是DataSourceTransactionManager。

这2个事务管理器都实现了spring中提供的PlatformTransactionManager接口,这个接口是spring的事务核心接口。

这个核心接口有以下这几个常用的实现策略:

  • HibernateTransactionManager
  • DataSourceTransactionManager
  • JtaTransactionManager
  • JpaTransactionManager

具体的PlatformTransactionManager继承关系如下:
PlatformTransactionManager

spring-boot-starter-data-jpa这个starter会触发HibernateJpaAutoConfiguration这个自动化配置类,HibernateJpaAutoConfiguration继承了JpaBaseConfiguration基础类。

在JpaBaseConfiguration中构造了事务管理器:

1
2
3
4
5
6
7
8
9
@Bean

@ConditionalOnMissingBean(PlatformTransactionManager.class)

public PlatformTransactionManager transactionManager() {

return new JpaTransactionManager();

}

spring-boot-starter-jdbc会触发DataSourceTransactionManagerAutoConfiguration这个自动化配置类,也会构造事务管理器:

1
2
3
4
5
6
7
8
9
10
11
@Bean

@ConditionalOnMissingBean(PlatformTransactionManager.class)

@ConditionalOnBean(DataSource.class)

public DataSourceTransactionManager transactionManager() {

return new DataSourceTransactionManager(this.dataSource);

}

TransactionManager

Spring的事务管理器PlatformTransactionManager接口中定义了3个方法:

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

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager {

// 基于事务的传播特性,返回一个已经存在的事务或者创建一个新的事务
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

// 提交事务
void commit(TransactionStatus status) throws TransactionException

// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}

TransactionDefinition

其中TransactionDefinition接口表示跟spring兼容的事务属性,比如传播行为、隔离级别、超时时间、是否只读等属性。

TransactionStatus接口

TransactionStatus接口表示事务的状态,比如事务是否是一个刚构造的事务、事务是否已经完成等状态。

下面这段代码就是传统事务的常见写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
transaction.begin();

try {

...

transaction.commit();

catch(Exception e) {

...

transaction.rollback();

finally {

}

由于spring的事务操作被封装到了PlatformTransactionManager接口中,commit和rollback方法对应接口中的方法,begin方法在getTransaction方法中会被调用。

细心的读者发现文章前面构造事务管理器的时候都会加上这段注解:

1
@ConditionalOnMissingBean(PlatformTransactionManager.class)

也就是说如果我们手动配置了事务管理器,Springboot就不会再为我们自动配置事务管理器。

多事务管理器

如果要使用多个事务管理器的话,那么需要手动配置多个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration

public class DatabaseConfiguration {

@Bean

public PlatformTransactionManager transactionManager1(EntityManagerFactory entityManagerFactory) {

return new JpaTransactionManager(entityManagerFactory);

}

@Bean

public PlatformTransactionManager transactionManager2(DataSource dataSource) {

return new DataSourceTransactionManager(dataSource);

}

}

然后使用Transactional注解的时候需要声明是哪个事务管理器:

1
2
3
4
5
6
7
@Transactional(value="transactionManager1")

public void save() {

doSave();

}

Spring给我们提供了一个TransactionManagementConfigurer接口,该接口只有一个方法返回PlatformTransactionManager。其中返回的PlatformTransactionManager就表示这是默认的事务处理器,这样在Transactional注解上就不需要声明是使用哪个事务管理器了。

四、@Transactional的使用

@Transactional不仅可以注解在方法上,也可以注解在类上。当注解在类上的时候意味着此类的所有public方法都是开启事务的。如果类级别和方法级别同时使用了@Transactional注解,则
使用在类级别的注解会重载方法级别的注解。

使用@Transactional注解进行事务控制时,可以在其中添加有关“隔离级别”和“传播行为”的指定:

隔离级别

隔离级别 解释
DEFAULT 这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是: READ_COMMITTED
READ_UNCOMMITTED 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别
READ_COMMITTED 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值
REPEATABLE_READ 该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读
SERIALIZABLE 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别

指定方法:通过使用 isolation 属性设置,例如:
@Transactional(isolation = Isolation.DEFAULT)

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

事务的并发问题

  1. 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
  2. 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
  3. 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

传播行为

| 传播行为 | 解释 |
| REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务|
| SUPPORTS | SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行|
| MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 | REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起|
| NOT_SUPPORTED| 以非事务方式运行,如果当前存在事务,则把当前事务挂起|
| NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常 |
| NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED |

指定方法:通过使用 propagation 属性设置,例如:
@Transactional(propagation = Propagation.REQUIRED)

Spring Boot开启事务支持

  1. 在Spring Boot中使用@Transactional注解,只需要在启动类上添加@EnableTransactionManagement注解开启事务支持:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    @EnableTransactionManagement //开启声明式事务
    @SpringBootApplication
    //Sprnig Boot项目的核心注解,主要目的是开启自动配置
    public class MainApplication {
        //该main方法作为项目启动的入口
        public static void main(String[] args) {
            SpringApplication.run(MainApplication.class, args);
        }
    }
  2. 然后在访问数据库的Service方法上添加注解@Transactional注解即可:

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
        import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import cn.com.springboot.mapper.UserMapper;
    import cn.com.springboot.pojo.User;
    import cn.com.springboot.service.UserService;

    @Service("userService")
    public class UserServiceImpl implements UserService{

        @Autowired
        private UserMapper userMapper;

        @Override
        @Transactional
        public User findUserById(int id) {
            return userMapper.findUserById(id);
        }
    }

    3. 当然,如果我们想要使用自定义的事务管理器,可以在配置类中设置自定义事务管理器,并以@Bean暴露给Spring容器:

    ```java
    import javax.annotation.Resource;
    import javax.persistence.EntityManagerFactory;
    import javax.sql.DataSource;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.orm.jpa.JpaTransactionManager;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.annotation.TransactionManagementConfigurer;

    @Configuration
    public class TransactionalConfiguration implements TransactionManagementConfigurer{

        @Resource(name="txManager1")
        private PlatformTransactionManager txManager1;

        // 创建事务管理器1
        @Bean(name = "txManager1")
        public PlatformTransactionManager txManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }

        // 创建事务管理器2
        @Bean(name = "txManager2")
        public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
            return new JpaTransactionManager(factory);
        }

        @Override
        public PlatformTransactionManager annotationDrivenTransactionManager() {
            return txManager1;
        }
    }

    这里配置类实现了TransactionManagementConfigurer接口,其必须实现annotationDrivenTransactionManager()方法,该方法的返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器。

  3. 在@Transactional注解中使用value指定需要的事务管理器的名称即可(不指定的话默认使用annotationDrivenTransactionManager()方法的返回值):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Override
    // 使用value具体指定使用哪个事务管理器
    @Transactional(value="txManager1")
    public User findUserById(int id) {
        return userMapper.findUserById(id);
    }

    @Override
    // 没有指定value,则默认使用方法 annotationDrivenTransactionManager() 返回的事务管理器
    @Transactional
    public User findUserById2(int id) {
        return userMapper.findUserById(id);
    }

五、@Transactional注解实现原理剖析

使用@Transactional注解对某目标方法进行标注时,Spring会使用AOP代理,生成一个代理对象,该对象会根据@Transactional注解的属性配置信息,来决定是否使用TransactionInterceptor拦截器来进行
拦截。

如果该方法需要使用事务控制,则需要使用TransactionInterceptor事务拦截器,对该方法进行拦截,在该目标方法执行之前创建并开启事务,然后执行目标方法

最后在目标方法执行完毕后,根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager操作数据源DataSource提交或回滚事务:

work

Spring AOP代理方式

  1. CglibAopProxy
    上图就是以CglibAopProxy为例,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法来进行代理。

  2. JdkDynamicAopProxy
    JDK提供的代理类。
    需要调用其 invoke 方法来进行代理。

    事务管理的框架是由抽象事务管理器AbstractPlatformTransactionManager来提供的,而具体的底层事务处理实现,由PlatformTransactionManager的具体实现类来实现,如事务管理器 DataSourceTransactionManager。不同的事务管理器管理不同的数据资源 DataSource,

    比如 DataSourceTransactionManager 管理 JDBC 的 Connection。
    PlatformTransactionManager,AbstractPlatformTransactionManager 及具体实现类关系下图所示:

    PlatformTransactionManagerImpl

参考资料

本文参考链接
本文参考链接

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×