一、Spring事务
事务依托于数据库,在生产开发中Spring帮我们更好的管理事务。标准的JDBC也可以控制事务,但是需要写大量的模板代码,繁复、冗杂、不方便,Spring事务正好解决了这些问题。Spring事务需要生成一个代理来增强方法,添加了@Transactional注解的方法,会被识别到并进行AOP代理。如果对SpringAOP不熟,可以先看上一篇AOP主流程
1.1 事务的隔离级别
Spring定义了五个事务隔离级别常量,这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
- DEFAULT :采 用 DB 默 认 的 事 务 隔 离 级 别 。 MySql 的默认为REPEATABLE_READ; Oracle 默认为 READ_COMMITTED
- READ_UNCOMMITTED:读未提交,未解决任何并发问题。
- READ_COMMITTED:读已提交,解决脏读,存在不可重复读与幻读。
- REPEATABLE_READ:可重复读,解决脏读、不可重复读,存在幻读
- SERIALIZABLE:串行化,不存在并发问题,性能最差。
1.2 事务的传播特性
Spring定义了七个事务传播行为,所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。事务传播行为常量都是以 PROPAGATION_ 开头,形如PROPAGATION_XXX。
- PROPAGATION_REQUIRED 指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中,Spring 默认的事务传播行为
- PROPAGATION_REQUIRES_NEW 总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
- PROPAGATION_SUPPORTS 指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
- PROPAGATION_MANDATORY 支持当前事务,如果没有事务就抛出异常
- PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作
- PROPAGATION_NEVER 不使用事务,如果存在事务就报错
- PROPAGATION_NOT_SUPPORTED 以非事务方式执行,如果当前存在事务则将当前事务挂起
二、源码跟踪
2.1 环境准备
AppConfig
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@ComponentScan("com.spring.study")
//@EnableAspectJAutoProxy
@EnableTransactionManagement //开启事务管理,Spring事务的源头就在这里
@Configuration
public class AppConfig {
@Bean
JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
TransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
DataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariConfig.setJdbcUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=Asia/Shanghai");
hikariConfig.setUsername("test");
hikariConfig.setPassword("123456");
return new HikariDataSource(hikariConfig);
}
}
UserService
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class UserService implements IUserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void run() {
System.out.println("run run run quickly!");
}
@Transactional
public String selectOne() {
return jdbcTemplate.queryForObject("select 1 from dual;", String.class);
}
}
TestSpring
import com.spring.study.service.IUserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestSpring {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
IUserService o = (IUserService) context.getBean("userService");
System.out.println(o.getClass());
o.run();
System.out.println("selectOne :"+o.selectOne());
}
}
2.2 源码实现
Spring的事务需要AOP代理来增强方法,先看AOP部分,再看事务部分
2.2.1 生成代理对象
2.2.1.1 EnableTransactionManagement
EnableTransactionManagement是入口,它引入了TransactionManagementConfigurationSelector,mode默认是AdviceMode.PROXY
2.2.1.2 TransactionManagementConfigurationSelector
TransactionManagementConfigurationSelector实现了ImportSelector接口,那么selectImports返回的String数组就是会被Spring管理的bean的名字,注意这里包含了包名。adviceMode来源于父类,从注解EnableTransactionManagement中获取的,因此AutoProxyRegistrar、ProxyTransactionManagementConfiguration将被加入到容器。
2.2.1.3 AutoProxyRegistrar
AutoProxyRegistrar的registerBeanDefinitions方法,在容器启动阶段,处理BeanFactoryPostProcesser的子类ConfigurationClassPostProcessor时触发执行。
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);–>registerAutoProxyCreatorIfNecessary(registry, null);–>registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
InfrastructureAdvisorAutoProxyCreator被添加到容器中,InfrastructureAdvisorAutoProxyCreator继承了AbstractAutoProxyCreator(Spring AOP在这里进行),因此具备了产生代理对象的能力。上一篇讲到使用@EnableAspectJAutoProxy注解会向容器添加AnnotationAwareAspectJAutoProxyCreator,两者的作用都是为了进行AOP代理
2.2.1.4 ProxyTransactionManagementConfiguration
ProxyTransactionManagementConfiguration产生了一个Advisor(BeanFactoryTransactionAttributeSourceAdvisor),在上一篇SpringAOP里面讲到过,Bean匹配到Advisor之后就会被动态代理
2.2.1.5 Bean与Advisor匹配
在AbstractAutoProxyCreator.wrapIfNecessary方法中,可以看到UserService匹配到了BeanFactoryTransactionAttributeSourceAdvisor,那么它将被代理。在执行事务方法时会进入JdkDynamicAopProxy的invoke方法,而Advisor也会被添加到JdkDynamicAopProxy里面使用。
注意看this=InfrastructureAdvisorAutoProxyCreator,所以加了@Transactional注解的方法或对象是在InfrastructureAdvisorAutoProxyCreator里面进行的AOP代理
2.2.2 事务执行
2.2.2.1 JdkDynamicAopProxy
得到代理对象之后,执行代理对象的方法,进入JdkDynamicAopProxy的invoke方法
run是普通方法,得到的拦截器链个数为0
selectOne方法的拦截器有一个TransactionInterceptor,接着往下
retVal = invocation.proceed();–>((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
进入到TransactionInterceptor.invoke
2.2.2.2 TransactionInterceptor开启事务
进入invokeWithinTransaction方法
这里开启了事务管理,传播机制是PROPAGATION_REQUIRED,,隔离级别是ISOLATION_DEFAULT
2.2.2.3 获取Connection
还是在invokeWithinTransaction里面执行到createTransactionIfNecessary,看下注释:使用getTransaction进行标准事务划分,然调用后提交或回滚。createTransactionIfNecessary就进行了设置手动提交事务、获取连接,跟进去看看
createTransactionIfNecessary–>status = tm.getTransaction(txAttr);–>startTransaction(def, transaction, debugEnabled, suspendedResources);–>doBegin(transaction, definition);–>Connection newCon = obtainDataSource().getConnection();
Connection是从数据源持有的连接池里面获取的,然后将其添加到ConnectionHolder,再让txObject持有ConnectionHolder。后面还有一个重要的方法bindResource
意思是将Connection和当前线程绑定。这里面就不细讲了,简单说下原理:使用了ThreadLocal<Map<Object, Object>> ,Map的key值就是数据源对象,value就是数据库连接。
作用:
- 同一个线程,相同的数据源,对于事务的处理使用同一个连接
- 同一个线程,不相同的数据源,使用不同的连接
- 不同的线程,相同的数据源,使用不同的连接
- 不同的线程,不同的数据源,使用不同的连接
2.2.2.4 设置手动提交事务
继续往下,可以看到将con设置为非自动提交
往下走,结束该方法返回到createTransactionIfNecessary
TransactionStatus持有了本次事务,事务下有了本次执行SQL的Connection,再返回
txInfo里面包含了本次事务的所有信息,后面提交/回滚会用上
2.2.2.5 执行事务方法
invocation.proceedWithInvocation();将会执行目标对象的方法,看下调用链
selectOne–>queryForObject–>query(sql, rowMapper);–>execute(new QueryStatementCallback());
这里获取连接之后进行数据库的操作,返回结果,我们看下是如何获取数据库连接的
getConnection–>doGetConnection(dataSource);–>TransactionSynchronizationManager.getResource(dataSource)–>Object value = doGetResource(actualKey);
从resources拿出的Connection就是前面bindResource放进去的,因为他们是在同一个线程进行的
2.2.2.6 提交事务
返回到proceedWithInvocation,得到返回值“1”
继续往下
commitTransactionAfterReturning(txInfo);–>txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());–>processCommit(defStatus);–>doCommit(status);
最终事务提交。事务的传播特性,这里没有具体分析,有兴趣的伙伴可以自行研讨。
三、事务失效情况
3.1 目标对象执行事务方法
test加了事务注解,不允许有事务,有就报错
正常输出,没报错,这是因为test方法在目标对象调用,没有经过动态代理增强。
改成这样,事务就生效了
3.2 子线程执行事务方法
子线程执行test(),未报错,事务不生效。