通常我们在日常开发中在配置类上增加@EnableTransactionManagement注解,在业务开发时,只要在业务类上或者方法上加@Transactional注解,spring就会帮我们做好创建数据库连接,设置相关的数据隔离级别,超时,提交或者回滚等操作,而我们只要专注业务开发,做好配置即可。那么spring是如何做到处理各种复杂情况呢?
首先我们来看看这两个注解为我们做了哪些事情:
@EnableTransactionManagement
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
....
}
该注解导入了TransactionManagementConfigurationSelector类,这个实现了ImportSelector接口,spring在启动的时候就会执行selectImports()方法,并将返回的AutoProxyRegistrar和ProxyTransactionManagementConfiguration类进行解析并注册到spring容器中去;
而AutoProxyRegistrar这个类又实现ImportBeanDefinitionRegistrar类,故spring又会执行registerBeanDefinitions()方法,这里会进行一步尤为重要的操作,向spring注册InfrastructureAdvisorAutoProxyCreator这个BeanPostProcessor,Bean才可以进行AOP;
另外ProxyTransactionManagementConfiguration类中定义了三个bean:
BeanFactoryTransactionAttributeSourceAdvisor:advisor
AnnotationTransactionAttributeSource:定义了一个pointcut(判断某个类或者某个方法上是否加了@Transactional注解),存在就解析@Transactional注解的信息并封装为RuleBasedTransactionAttribute对象,并且会被设置到advisor里
TransactionInterceptor:定义了advice,即代理逻辑(事务创建 提交 回滚等等操作)
spring bean在初始化后,会先从beanFactory中找到所有的advisor,然后遍历所有的advisor,判断是否属于PointcutAdvisor类,再通过TransactionAttributeSourcePointcut判断目标类上是否存在@Transactional以及方法上是否存在@Transactional,只要存在就将advisor缓存到集合中并返回,并为目标类创建代理对象。
1、test()调用abc()用的是同一个数据库连接,此时sql执行要么全部提交,要么全部失败
如果设置transactionManager.setGlobalRollbackOnParticipationFailure(false),abc()中sql执行错误,并且test()方法中捕获异常;那么此时就会出现test()方法中sql执行成功提交,abc()中执行失败回滚
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserService userService;
@Transactional(rollbackFor = Exception.class)
public void test() {
jdbcTemplate.execute("insert into goods(id, stock, name, detail) values('2', '1', 'apple', '200')");
userService.abc("test");
}
@Transactional
public void abc(String str) {
jdbcTemplate.execute("insert into goods(id, stock, name, detail) values('3', '3', 'xiaomi', '100')");
throw new NullPointerException();
}
}
2、abc()方法设置@Transactional(propagation = Propagation.REQUIRES_NEW),就会新创建一个数据库连接;此时abc()抛出异常,如果test()没有捕获,则都会回滚,否则,test()提交,abc()回滚。
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void abc(String str) {
jdbcTemplate.execute("insert into goods(id, stock, name, detail) values('3', '3', 'xiaomi', '100')");
throw new NullPointerException();
}
那么spring是如何新建数据库连接,在test()调用abc()执行完成以后如何拿到之前的连接的呢?
当调用代理类的方法时,就会执行TransactionInterceptor#invoke()方法:
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 执行创建数据库连接,设置隔离级别,回滚或提交等操作
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
// 执行后续的Interceptor,以及被代理的方法
return invocation.proceed();
}
@Override
public Object getTarget() {
return invocation.getThis();
}
@Override
public Object[] getArguments() {
return invocation.getArguments();
}
});
1、拿到@Transactional注解上所有属性信息(已经封装为一个对象)
2、拿到spring容器中设置好的spring事务管理器(TransactionManager)
3、拿到当前正在执行的被代理类的方法名称
4、创建事务:test():先获取是否已经存在一个事务,而且propagation = Propagation.REQUIRES则返回当前事务,否则先挂起当前线程的资源(缓存到一个资源对象中,但此时为空),再去创建事务,并设置相关属性信息,设置autoCommit=false,把当前事务存到ThreadLocal(DataSource对象为key,数据库连接对象为value)中
5、执行abc(),假设此时propagation = Propagation.REQUIRES_NEW,先挂起当前线程的资源并缓存到挂起资源对象中,再去创建一个新事物(属性设置等等);当执行完abc()方法后,无论是提交还是回滚,都会关闭新建的数据库连接,并恢复现场,把挂起资源对象属性重新设置到当前线程的ThreadLocal中去。
6、最后test()方法执行完成以后,也会关闭数据连接等操作
注意:如果程序员设置了TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()表示本来可以提交的事物,强制回滚。NOT_SUPPORT,使用jdbc自己的连接。
spring事务面试题记录:
被问到spring事务默认对哪些异常进行回滚? 特此记录:
开启事务后TransactionInterceptor.invoke()方法会执行下面的核心方法
进入invokeWithinTransaction():
进入completeTransactionAfterThrowing():
可以看出抛出了三中类型异常:TransactionSystemException|RuntimeException|Error,而TransactionSystemException也是RuntimeException的子类。