Spring Boot事务控制
了Spring Boot与MyBatis的结合开发,并成功操作了数据库。众所周知,保证数据库一致性的操作,就是事务的控制。
而Spring事务管理可以分为两种:编程式以及声明式。
其中编程式事务就是使用编写代码的方式,进行事务的控制。而声明式事务一般通过切面编程(AOP)的方式,注入到要操作的逻辑的前后,将业务逻辑与事务处理逻辑解耦。
由于使用声明式事务可以保证业务代码逻辑不会受到事务逻辑的污染, 所以在实际的工程中使用声明式事务比较多。
对于声明式事务的实现,在Java工程中一般有有两种方式:
(1)使用配置文件(XML)进行事务规则相关规则的声明
(2)使用@Transactional注解进行控制
这里我们着重讲解传统工程与Spring Boot进行声明式事务控制的不同。
一、传统工程与Spring Boot对事务的配置处理
在传统的Web工程中,我们通常使用XML配置,利用Spring的AOP切面编程手段,将事务以切面的方式注入到Service的各个数据库操作方法中去:
<!-- 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>
可以看到,针对事务,我们首先配置了【数据源】,然后配置了【事务管理器】,然后配置了【事务通知】,定义了各种方法的事务操作规范。最后将【事务管理器】切入需要进行事务管理的Service方法中。而在Spring Boot中的推荐操作是,使用@Transactional注解来申明事务。
要在Spring boot中支持事务,首先要导入Spring boot提供的JDBC或JPA依赖:
<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注解进行事务的使用。
虽然在传统的工程中也可以使用@Transactional注解来申明事务,但是还是需要使用XML来配置事务管理器(DataSourceTransactionManager)。
那么大家可能会有一个疑问,因为传统工程中使用XML配置事务时,需要给DataSourceTransactionManager事务管理器配置数据源DataSource,那么Spring Boot进行自动配置的话,
Spring Boot在注入DataSourceTransactionManager事务管理器时,是如何找到我们配置的DataSource数据源的呢?
答案是Spring Boot会自动到Spring容器中寻找我们配置好的DataSource。也即是之前我们的手动操作,现在使用Spring Boot变成了自动化操作。
二、@Transactional的使用
@Transactional不仅可以注解在方法上,也可以注解在类上。当注解在类上的时候意味着此类的所有public方法都是开启事务的。如果类级别和方法级别同时使用了@Transactional注解,则
使用在方法级别的注解会重载类级别的注解。
使用@Transactional注解进行事务控制时,可以在其中添加有关“隔离级别”和“传播行为”的指定:
(1)隔离级别
DEFAULT :这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是: READ_COMMITTED 。
READ_UNCOMMITTED :该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
READ_COMMITTED :该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
REPEATABLE_READ :该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
SERIALIZABLE :所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
指定方法:通过使用 isolation 属性设置,例如:
@Transactional(isolation = Isolation.DEFAULT)
(2)传播行为
REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED 。
指定方法:通过使用 propagation 属性设置,例如:
@Transactional(propagation = Propagation.REQUIRED)
在Spring Boot中使用@Transactional注解,只需要在启动类上添加@EnableTransactionManagement注解开启事务支持:
package cn.com.springboot;
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);
}
}
然后在访问数据库的Service方法上添加注解@Transactional注解即可:
package cn.com.springboot.service.impl;
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);
}
}
当然,如果我们想要使用自定义的事务管理器,可以在配置类中设置自定义事务管理器,并以@Bean暴露给Spring容器:
package cn.com.springboot.data;
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()方法,该方法的返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器。
然后在@Transactional注解中使用value指定需要的事务管理器的名称即可(不指定的话默认使用annotationDrivenTransactionManager()方法的返回值):
@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提交或回滚事务:
Spring AOP代理类有两种:
(1)CglibAopProxy
垃圾回收类库提供的代理类。
上图就是以CglibAopProxy为例,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法来进行代理。
(2)JdkDynamicAopProxy
JDK提供的代理类。
需要调用其 invoke 方法来进行代理。
事务管理的框架是由抽象事务管理器AbstractPlatformTransactionManager来提供的,而具体的底层事务处理实现,由PlatformTransactionManager的具体实现类来实现,如事务管理器 DataSourceTransactionManager。
不同的事务管理器管理不同的数据资源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。
PlatformTransactionManager,AbstractPlatformTransactionManager 及具体实现类关系下图所示: