1.概述
对于一个系统应用而言,使用数据库进行数据存储是必然的,意味着开发过程中事务的使用及控制也是必不可少的,当然事务是数据库层面的知识点并不是Spring
框架所提出的。使用JDBC开发时,我们使用connnection
对事务进行控制,使用MyBatis
时,我们使用SqlSession
对事务进行控制,缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化,所以Spring
就在这些技术基础上,提供了统一的控制事务的接口。Spring的事务分为:编程式事务控制和声明式事务控制。
- 编程式事务控制:Spring提供了事务控制的类和方法,使用编码的方式对业务代码进行事务控制,事务控制代码和业务操作代码耦合到了一起,开发中几乎不使用
- 声明式事务控制: Spring将事务控制的代码封装,对外提供了Xml和注解配置方式,通过配置的方式完成事务的控制,可以达到事务控制与业务操作代码解耦合,开发中推荐使用
2.Spring事务管理和封装
2.1 原生事务控制
在没有框架对事务进行封装之前,我们都是使用底层的原生api来进行事务控制,如JDBC操作数据库控制事务
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取mysql数据库连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "root");
conn.setAutoCommit(false);
// 获取statement
statement = conn.createStatement();
// 执行sql,返回结果集
resultSet = statement.executeQuery("xxxx");
// 提交
conn.commit();
// 回滚
// conn.rollback();
这就是原生操作事务的流程,在我们使用Spring
框架开发业务系统时也是离不了这样的事务操作的,如果每与数据库交互都需要按上面步骤进行操作,就会显得十分臃肿、重复编码,所以Spring
对此进行了封装来提高编程的效率,让事务控制这一过程自动化、透明化,从而做到让开发者专注业务逻辑编码,无需关注事务控制,由Spring
框架AOP切面完成即可。
2.2 Spring提供的事务API
Spring基于模版方法设计模式实现了事务控制的封装,核心API和模板类如下:
核心类 | 解释 |
---|---|
平台事务管理器PlatformTransactionManager | 是一个接口标准,实现类都具备事务提交、回滚和获得事务对象的功能,不同持久层框架可能会有不同实现方案 |
事务定义TransactionDefinition | 封装事务的隔离级别、传播行为、过期时间等属性信息 |
事务状态TransactionStatus | 存储当前事务的状态信息,如果事务是否提交、是否回滚、是否有回滚点等 |
事务管理器—PlatformTransactionManager
PlatformTransactionManager
是事务管理器的顶层接口,只规定了事务的基本操作:创建事务,提交事物和回滚事务。
public interface PlatformTransactionManager extends TransactionManager {
// 打开事务
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
Spring使用模板方法模式提供了一个抽象类AbstractPlatformTransactionManager
,规定了事务管理器的基本框架,仅将依赖于具体平台的特性作为抽象方法留给子类实现,如mybatis
框架的事务管理器是DatasourceTransactionManager
,hibernate
框架的事务管理器是HibernateTransactionManager
,我曾经见过一个项目服务里的ORM
框架同时使用了mybatis,hibernate
两个框架,至于为啥?大概是想从hibernate
转为mybatis
吧....然后有这么一个问题,一个逻辑方法有两个数据库操作,一个是用mybatis
实现的,一个是用hibernate
实现,这个逻辑方法使用了@Transactional(rollbackFor = Exception.class)
,但是事务竟然没控制住~前面就是问题的原因所在,mybatis
和hibernate
的事务管理器都不是同一个,肯定控制不住事务的。
事务状态—TransactionStatus
存储当前事务的状态信息,如果事务是否提交、是否回滚、是否有回滚点等。
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
/**
* 是否有Savepoint Savepoint是当事务回滚时需要恢复的状态
*/
boolean hasSavepoint();
/**
* flush()操作和底层数据源有关,并非强制所有数据源都要支持
*/
@Override
void flush();
}
还从父接口TransactionExecution,SavepointManager
中继承了其他方法
/**
* 是否是新事务(或是其他事务的一部分)
*/
boolean isNewTransaction();
/**
* 设置rollback-only 表示之后需要回滚
*/
void setRollbackOnly();
/**
* 是否rollback-only
*/
boolean isRollbackOnly();
/**
* 判断该事务已经完成
*/
boolean isCompleted();
/**
* 创建一个Savepoint
*/
Object createSavepoint() throws TransactionException;
/**
* 回滚到指定Savepoint
*/
void rollbackToSavepoint(Object savepoint) throws TransactionException;
/**
* 释放Savepoint 当事务完成后,事务管理器基本上自动释放该事务所有的savepoint
*/
void releaseSavepoint(Object savepoint) throws TransactionException;
事务属性的定义—TransactionDefinition
TransactionDefinition
封装事务的隔离级别、传播行为、过期时间等属性信息
/**
* 返回事务的传播级别
*/
default int getPropagationBehavior() {
return PROPAGATION_REQUIRED;
}
/**
* 返回事务的隔离级别
*/
default int getIsolationLevel() {
return ISOLATION_DEFAULT;
}
/**
* 事务超时时间
*/
default int getTimeout() {
return TIMEOUT_DEFAULT;
}
/**
* 是否为只读事务(只读事务在处理上能有一些优化)
*/
default boolean isReadOnly() {
return false;
}
/**
* 返回事务的名称
*/
@Nullable
default String getName() {
return null;
}
/**
* 默认的事务配置
*/
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
2.3 Spring编程式事务实现
基于上面底层的API,开发者可以在代码中手动的管理事务的开启、提交、回滚等操作来完成编程式事务控制。在spring项目中可以使用TransactionTemplate
和TransactionCallback
进行实现手动控制事务。
//设置事务的各种属性;可以猜测TransactionTemplate应该是实现了TransactionDefinition
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transactionTemplate.setTimeout(30000);
//执行事务 将业务逻辑封装在TransactionCallback中
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
//.... 业务代码
}
});
但是我们在开发过程中一般不使用编程式事务控制,因为比较繁琐不够优雅,一般是使用声明式进行事务控制,所以接下来就重点讲讲声明式事务。
项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用
Github地址:github.com/plasticene/…
Gitee地址:gitee.com/plasticene3…
微信公众号:Shepherd进阶笔记
交流探讨qun:Shepherd_126
3.声明式事务
3.1 示例
@Transactional
是Spring中声明式事务管理的注解配置方式,相信这个注解的作用大家都很清楚,直接来看看我们添加用户和角色的示例代码:
@Transactional(rollbackFor = Exception.class)
public void addUser(UserParam param) {
String username = param.getUsername();
checkUsernameUnique(username);
User user = PtcBeanUtils.copy(param, User.class);
userDAO.insert(user);
if (!CollectionUtils.isEmpty(param.getRoleIds())) {
userRoleService.addUserRole(user.getId(), param.getRoleIds());
}
}
可以看出在Spring中进行事务管理非常简单,只需要在方法上加上注解@Transactional
,Spring就可以自动帮我们进行事务的开启、提交、回滚操作。
3.2 实现原理
从@EnableTransactionManagement
说起,该注解开启注解声明式事务,所以我们就先来看看其定义:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
/**
* 用来表示默认使用JDK Dynamic Proxy还是CGLIB Proxy
*/
boolean proxyTargetClass() default false;
/**
* 表示以Proxy-based方式实现AOP还是以Weaving-based方式实现AOP
*/
AdviceMode mode() default AdviceMode.PROXY;
/**
* 顺序
*/
int order() default Ordered.LOWEST_PRECEDENCE;
}
可以看出,和注解@EnableAspectJAutoProxy
开启aop代理差不多,核心逻辑:@Import(TransactionManagementConfigurationSelector.class)
,TransactionManangementConfigurationSelector
主要是往Spring容器中注入相关bean,核心逻辑如下:
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector {
/**
* Returns {@link ProxyTransactionManagementConfiguration} or
* {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY}
* and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()},
* respectively.
*/
@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {determineTransactionAspectClass()};
default:
return null;
}
}
}
3.2.1 如何生成代理类
AutoProxyRegistrar
通过调用AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
向容器中注册AbstractAdvisorAutoProxyCreator
,这个类在之前总结的 Spring切面编程实现原理一文中重点解析过,是生成AOP代理类的核心实现所在。
UserService
实现类中使用了@Transational
来进行数据库事务控制,AuthService
中不涉及到数据库事务处理,从图中可知UserService
是被CGLIB动态代理生成的代理类,而AuthService
是原生类,这就是AbstractAdvisorAutoProxyCreator
实现的,
#getAdvicesAndAdvisorsForBean()
会判断bean是否有advisor,有的话就通过动态代理生成代理对象注入到Spring容器中,这就是前面UserService
代理对象的由来。
protected Object[] getAdvicesAndAdvisorsForBean(
Class beanClass, String beanName, @Nullable TargetSource targetSource) {
List advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
#findEligibleAdvisors()
顾名思义就是找到符合条件的advisor
protected List findEligibleAdvisors(Class beanClass, String beanName) {
List candidateAdvisors = findCandidateAdvisors();
List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
#findCandidateAdvisors()
查找所有候选的advisor
@Override
protected List findCandidateAdvisors() {
// Add all the Spring advisors found according to superclass rules.
List advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
super.findCandidateAdvisors()
就是获取spring内部规则的advisor,比如说事务控制的advisor:BeanFactoryTransactionAttributeSourceAdvisor
this.aspectJAdvisorsBuilder.buildAspectJAdvisors()
是解析使用了@Aspect
的切面类,根据切点表达式生成advisor。
#findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName)
调用AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
筛选出能匹配当前bean的advisor。
AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass)
会调用#canApply()
方法:
public static boolean canApply(Advisor advisor, Class targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
这里判断如果是PointcutAdvisor
类型,就会调用canApply(pca.getPointcut(), targetClass, hasIntroductions);
,上面提到的事务advisor:BeanFactoryTransactionAttributeSourceAdvisor
正好符合。执行BeanFactoryTransactionAttributeSourceAdvisor
的TransactionAttributeSourcePointcut
对象的matches()
方法来进行是否匹配判断,然后根据当前bean的所有method遍历执行判断使用有@Transational
注解,来到AbstractFallbackTransactionAttributeSource
的computeTransactionAttribute()
方法
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
最终来到SpringTransactionAnnotationParser#parseTransactionAnnotation()
@Override
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
//这里就是分析Method是否被@Transactional注解标注,有的话,不用说BeanFactoryTransactionAttributeSourceAdvisor适配当前bean,进行代理,并且注入切点
//BeanFactoryTransactionAttributeSourceAdvisor
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ae, Transactional.class);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
上面就是判断是否需要根据@Transactiona
l进行代理对象创建的判断过程。@Transactional
的作用就是标识方法需要被代理,同时携带事务管理需要的属性信息。
3.2.2 如何进行事务控制
在之前Spring切面编程实现原理一文中我碍于文章篇幅只分析了JDK代理的实现方式,但在 SpringBoot 2.x AOP中会默认使用Cglib来实现,所以今天就来分析一下CGLIB这种方式。
Spring的CGLIB方式生存代理对象是靠ObjenesisCglibAopProxy
完成的,ObjenesisCglibAopProxy
继承自CglibAopProxy
,调用方法#createProxyClassAndInstance()
得到基于Cglib动态代理的对象。最终的代理对象的代理方法DynamicAdvisedInterceptor
的#intercept()
方法
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
target = targetSource.getTarget();
Class targetClass = (target != null ? target.getClass() : null);
List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
try {
retVal = methodProxy.invoke(target, argsToUse);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
}
else {
// We need to create a method invocation...
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
这和以JDK实现的动态代理JdkDynamicAopProxy
实现了InvocationHandler
执行invoke()
来进行逻辑增强套路是一样的。
通过分析 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)
返回的是TransactionInterceptor
,然后来到new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed()
,最终调用TransactionInterceptor
的#invoke()
方法
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
#invokeWithinTransaction()
就是通过切面实现事务控制的核心逻辑所在:
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class targetClass,
final InvocationCallback invocation) throws Throwable {
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);
//省略部分代码
//获取事物管理器
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// 打开事务(内部就是getTransactionStatus的过程)
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 执行业务逻辑 invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 异常回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//省略部分代码
//提交事物
commitTransactionAfterReturning(txInfo);
return retVal;
}
4.总结
行文至此,Spring基于AOP自动完成事务控制的逻辑分析就完结了。Spring的声明式事务注解开发非常简单,只需要在方法上加上注解@Transactional
,Spring就可以自动帮我们进行事务的开启、提交、回滚操作。但是在日常开发中却经常出现事务失效的情况,所以了解Spring事务控制实现还是很有必要的,同时还可以加深对Spring AOP应用的认识。