<dependency>
<groupId>c3p0groupId>
<artifactId>c3p0artifactId>
<version>0.9.1.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.44version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>4.3.12.RELEASEversion>
dependency>
向IOC容器中注册一个c3p0数据源。
package com.meimeixia.tx;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
@EnableTransactionManagement // 它是来开启基于注解的事务管理功能的
@ComponentScan("com.meimeixia.tx")
@Configuration
public class TxConfig {
// 注册c3p0数据源
@Bean
public DataSource dataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("liayun");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
return dataSource;
}
// 向IOC容器中注册一个JdbcTemplate组件,它是spring提供的一个简化数据库操作的工具。
```java
@Bean
public JdbcTemplate jdbcTemplate() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
// 注册事务管理器在容器中
@Bean
public PlatformTransactionManager platformTransactionManager() throws Exception {
return new DataSourceTransactionManager(dataSource());
}
}
注意,这个事务管理器有一个特别重要的地方,就是它要管理数据源,也就是说事务管理器一定要把数据源控制住。这样的话,它才会控制住数据源里面的每个连接,这时该连接上的回滚以及事务的开启等操作,都会有这个事务管理器来做。
写sql相关操作类
package com.meimeixia.tx;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insert() {
String sql = "insert into `tbl_user`(username, age) values(?, ?)";
String username = UUID.randomUUID().toString().substring(0, 5);
jdbcTemplate.update(sql, username, 19); // 增删改都来调用这个方法
}
}
package com.meimeixia.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void insertUser() {
userDao.insert();
// otherDao.other(); // 该方法中的业务逻辑势必不会像现在这么简单,肯定还会调用其他dao的方法
System.out.println("插入完成...");
int i = 10 / 0;
}
}
要想指定声明式事务的原理,只需要搞清楚@EnableTransactionManagement注解给容器中注册了什么组件,以及这些组织工作时候的功能是什么就行了。一旦把这个研究透了,声明式事务的原理就清楚了。
之前研究AOP的原理的时候,是从@EnableAspectJAutoProxy注解开始入手研究的,因此研究声明式事务的原理,我们也应该从@EnableTransactionManagement注解开始入手研究。
其实,当研究完声明式事务的原理,就会发现这一过程与研究AOP原理的过程是非常相似的,也可以说着俩原理几乎一摸一样。
在配置类上添加@EnableTransactionManagement注解,便能够开启基于注解的事务管理功能。看看下面的源码。
从源码中可以看出,@EnableTransactionManagement注解使用@Import注解给容器中引入了TranactionManagementConfigurationSelector组件。其实TranactionManagementConfigurationSelector就是一个ImportSelector。
我们可以点到TransactionManagementConfigurationSelector类中一看究竟,如下图所示,发现它继承了一个类,叫AdviceModeImportSelector。
然后再次点到AdviceModeImportSelector类中,如下图所示,发现它实现了一个接口,叫ImportSelector。
说到底,其实它就是用于给容器中快速导入一些组件的,到底要导入哪些组件,就看它会返回哪些要导入到容器中的组件的全类名。
看源码就会发现里面会做一个switch判断,如果adviceMode是PROXY,就会返回一个String[],该String数组如下所示:
new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};
这说明会向容器中导入AutoProxyRegistrar和ProxyTransactionManagementConfiguration这两个组件。
AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,说明AutoProxyRegistrar组件是来向容器中注册bean的,最终会调用该组件的registerBeanDefinitions()方法来向容器中注册bean。
我们来仔细的看下AutoProxyRegistrar类中的registerBeanDefinitions()方法。
在该方法中先是通过如下一行代码来获取各种注解类型,这儿需要特别注意的是,这里是拿到所有的注解类型,而不是只拿@EnableAspectJAutoProxy这个类型的。因为mode、proxyTargetClass等属性会直接影响到代理的方式,而拥有这些属性的注解至少有@EnableTransactionManagement、@EnableAsync以及@EnableCaching等等,甚至还有启用AOP的注解,即@EnableAspectJAutoProxy,它也能设置proxyTargetClass这个属性的值,因此也会产生关联影响。
Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
然后是拿到注解里的mode、proxyTargetClass这两个属性的值。
注意,如果这儿的注解是@Configuration或者别的其他注解的话,那么获取到的这俩属性的值就是null了。
接着做一个判断,如果存在mode、proxyTargetClass这两个属性,并且这两个属性的class类型也都是对的,那么便会进入到if判断语句中,这样,其余注解就相当于都被挡在外面了。
要是真进入到了if判断语句中,是不是意味着找到了候选的注解(例如@EnableTransactionManagement)呢?你仔细想一下,是不是这回事。找到了候选的注解之后,就将candidateFound标识置为true。
紧接着会再做一个判断,即判断找到的候选注解中的mode属性的值是否为AdviceMode.PROXY,若是则会调用我们熟悉的AopConfigUtils工具类的registerAutoProxyCreatorIfNecessary方法。相信大家也很熟悉这个方法了,它主要是来向容器中注册一个InfrastructureAdvisorAutoProxyCreator组件的。
我们继续往下看AutoProxyRegistrar类的registerBeanDefinitions()方法。这时,又会做一个判断,要是找到的候选注解设置了proxyTargetClass这个属性的值,并且值为true,那么便会进入到下面的if判断语句中,看要不要强制使用CGLIB的方式。
如果此时找到的候选注解是@EnableTransactionManagement,想一想会发生什么事情?查看该注解的源码,你会发现它里面就拥有一个proxyTargetClass属性,并且其默认值是false。所以此时压根就不会进入到if判断语句中,而只会调用我们熟悉的AopConfigUtils工具类的registerAutoProxyCreatorIfNecessary方法。
继续追踪源码我们就发现注册的是一个InfrastructureAdvisorAutoProxyCreator.class了。
现在我们得出一个结论:导入的第一个组件(AutoProxyRegistrar)向容器中注入了一个自动代理创建器,即InfrastructureAdvisorAutoProxyCreator。
我们继续追踪源码发现InfrastructureAdvisorAutoProxyCreator它其实是一个后置处理器。
其实InfrastructureAdvisorAutoProxyCreator做的事情和之前研究的AOP的AnnotationAwareAspectJAutoProxyCreator组件所有的事情基本一样。就是利用后置处理器机制在对象创建以后进行包装,然后返回一个代理对象,并且该代理对象里面会有所有的事务增强器。最后代理对象执行目标方法,在执行的目标方法的过程过程中会利用拦截器的链式机制,依次进入每个拦截器中进行执行。
源码中我们发现它是一个配置类,它会利用@Bean注解向容器中注册各种组件,而且注册的第一个组件就是BeanFactoryTransactionAttributeSourceAdvisor,这个Advisor是事务的核心内容,称之为事务增强器。
事务增强器是什么呢?从上面的配置类可以看出,在向容器中注册事务增强器时,它会需要一个TransactionAttributeSource,翻译过来应该是事务属性源。
你会发现所需的TransactionAttributeSource又是容器中的一个bean,它是一个AnnotationTransactionAttributeSource对象。这个是重点,它是基于注解驱动的事务管理的事务属性源,和@Transaction注解相关,也是现在使用最多的方式,其基本作用遇上比如@Transaction注解标准的方法是,此类会分析此事务注解。
继续追踪源码会发现有一个事务解析器接口。
相关属性就是我们在@Transaction注解里面的写的。
事务增强器要用到事务注解的信息,它会使用AnnotationTransactionAttributeSource类来解析方法上标注的@Transtaction注解。
接下来,我们再来看看向容器中注册事务增强器时,还得做些什么。回到ProxyTransactionManagementConfiguration类中,发现在向容器中注册事务增强器时,除了需要事务注解信息,还需要一个事务的拦截器,看到那个transactionInterceptor方法没,它就是表示事务增强器还要用到一个事务的拦截器。
仔细查看上面的transactionInterceptor方法,你会看到在里面创建了一个TransactionInterceptor对象,创建完毕之后,不但会将事务属性源设置进去,而且还会将事务管理器(txManager)设置进去。也就是说,事务拦截器里面不仅保存了事务属性信息,还保存了事务管理器。
我们点进去TransactionInterceptor类里面去看一下,发现该类实现了一个MethodInterceptor接口,如下图所示。
看到它,会不会倍感亲切,因为在研究AOP原理的时候,就认识它了。AOP里面的一个知识点:切面类里面的通知方法最终都会被整成增强器,而增强器又会被转成MethodInterceptor。所以,这个事务拦截器的本质上还是一个MethodInterceptor(方法拦截器)。
所谓方法拦截器就是会在容器中放一个代理对象,代理对象要执行目标方法,那么方法拦截器就会进行工作。
其实,跟我妈之前研究AOP的原理一摸一样,在代理对象执行目标方法的时候,它便会来执行拦截器链,而现在这个拦截器链,只有一个TransactionInterceptor,它正是这个拦截器。
仔细翻阅TransactionInterceptor类的源码,你会发现它里面有一个invoke方法,而且还会看到在该方法里面又调用了一个invokeWithinTransaction方法,如下图所示。
从invokeWithinTransaction方法的第一行代码,即:
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
接着往下看invokeWithinTransaction方法,可以看到它的第二行代码是这样写的:
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
这就是来获取PlatformTransactionManager的,还记得我们之前就已经向容器中注册了一个吗,现在就是来获取它的。那到底又是怎么来获取的呢?我们不妨点进去determineTransactionManager方法里面去看一下。
首次获取肯定就为null,但没关系,因为最终会从容器中按照类型来获取,这可以从下面这行代码中看出来。
defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
所以,我们只需要给容器中注入一个PlatformTransactionManager,正如我们前面写的这样:
// 注册事务管理器在容器中
@Bean
public PlatformTransactionManager platformTransactionManager() throws Exception {
return new DataSourceTransactionManager(dataSource());
}
如果事先没有添加任何TransactionManager,那么最终会从容器中按照类型来获取一个PlatformTransactionManager。
接下来,继续往下看invokeWithinTransaction方法,来看它接下去又做了些什么。其实,很容易就能看出来,获取到事务管理器之后,然后便要来执行目标方法了,而且如果目标方法执行时一切正常,那么还能拿到一个返回值,如下图所示。
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
面这个方法翻译成中文,就是如果是必须的话,那么得先创建一个Transaction。说人话,就是如果目标方法是一个事务,那么便开启事务。
如果目标方法执行时一切正常,那么接下来该怎么办呢?这时,会调用一个叫commitTransactionAfterReturning的方法,如下图所示。
我们可以点进去commitTransactionAfterReturning方法里面去看一看,发现它是先获取到事务管理器,然后再利用事务管理器提交事务,如下图所示。
如果执行目标方法时出现异常,那么又该怎么办呢?这时,会调用一个叫completeTransactionAfterThrowing的方法,如下图所示。
我们可以点进去completeTransactionAfterThrowing方法里面去看一看,发现它是先获取到事务管理器,然后再利用事务管理器回滚这次操作,如下图所示。
也就是说,真正的回滚与提交事务的操作都是由事务管理器来做的,而TransactionInterceptor只是用来拦截目标方法的。
以上就是我们通过简单地来分析源码,粗略地了解了一下整个事务控制的原理。
首先,使用AutoProxyRegistrar向Spring容器里面注册了一个后置处理器,这个后置处理器会负责给我们包装代理对象。然后使用ProxyTransactionManagementConfiguration再向容器中注册一个事务增强器,此时需要用的事务拦截器。最后代理对象执行目标方法,在执行目标方法的过程中便会执行代理类里面的拦截器链,而且每次在执行目标方法异常时,便会利用事务管理器进行事务回滚,如果执行一切正常,那么就好使用事务管理器提交事务。
Spring注解驱动开发第34讲——你了解基于注解版的声明式事务吗?
Spring注解驱动开发第35讲——声明式事务原理的源码分析