前言
最近看SpringBoot源码的时候突然想起之前的事务机制及其传播,于是认真的去看了下相关源码,特给大家分享,并手动实现一个简单版本的,主要观光流程如下。
基本思想
SpringBoot事务传播的机制主要是将连接和事务管理器绑定
起来,以及新事务出现的时候需要将之前的事务挂起,也就是解绑
。当REQUIRES_NEW
的时候是会创建一个新的ConnectionHolder
。ConnectionHolder
就是当前连接Connection
的一个拥有者,用来获取当前连接。
源码解析
首先让我们看下拦截器入口
public Object invoke(MethodInvocation invocation) throws Throwable {
//加载目标类
Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
//加载目标类的方法
Method var10001 = invocation.getMethod();
invocation.getClass();
//进行AOP对事务的一些处理(核心)
return this.invokeWithinTransaction(var10001, targetClass, invocation::proceed);
}
然后进入invokeWithinTransaction
方法,这时候我们不走if (txAttr != null && tm instanceof CallbackPreferringPlatformTransactionManager) {
这个条件,因为我们的事务管理不是CallbackPreferringPlatformTransactionManager
,所以我们进入else
//根据字面意思就是根据情况是否创建新事务,如果创建了返回新事务,否则老的
TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
try {
//进行拦截目标方法的调用
result = invocation.proceedWithInvocation();
} catch (Throwable var17) {
//出现异常之后的一些处理,比如回滚
this.completeTransactionAfterThrowing(txInfo, var17);
throw var17;
} finally {
//清除当前事务相关信息,将之前的事务加入
this.cleanupTransactionInfo(txInfo);
}
//提交当前事务
this.commitTransactionAfterReturning(txInfo);
return result;
这里我们主要看下createTransactionIfNecessary
if (txAttr != null && ((TransactionAttribute)txAttr).getName() == null) {
//如果当前事务相关信息为空,就给个默认的
txAttr = new DelegatingTransactionAttribute((TransactionAttribute)txAttr) {
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
//获取事务对象
status = tm.getTransaction((TransactionDefinition)txAttr);
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured");
}
}
return this.prepareTransactionInfo(tm, (TransactionAttribute)txAttr, joinpointIdentification, status);
让我们进入getTransaction
方法一探究竟,因为平时基本就用REQUIRED
和REQUIRED_NEW
,就拿这两个讲啦,其他的类似。
//判断是否之前就有事务,如果有进行一些处理
if (this.isExistingTransaction(transaction)) {
return this.handleExistingTransaction((TransactionDefinition)definition, transaction, debugEnabled);
//这里就是对REQUIRES_NEW的相关处理啦
if (definition.getPropagationBehavior() == 3) {
if (debugEnabled) {
this.logger.debug("Suspending current transaction, creating new transaction with name [" + definition.getName() + "]");
}
//挂起当前事务
suspendedResources = this.suspend(transaction);
try {
newSynchronization = this.getTransactionSynchronization() != 2;
//新建一个事务状态
DefaultTransactionStatus status = this.newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
//开始事务(核心)
this.doBegin(transaction, definition);
//进行事务管理的一些属性设置和同步设置
this.prepareSynchronization(status, definition);
return status;
} catch (Error | RuntimeException var7) {
//恢复之前挂起的事务
this.resumeAfterBeginException(transaction, suspendedResources, var7);
throw var7;
}
让我们看看事务是如何挂起的,进去suspend
方法,点击进去再进入doSuspend
,可以看到DataSourceTransactionManager
中
protected Object doSuspend(Object transaction) {
//得到当前线程的事务对象
DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
//将当前对象的连接清除,因为要挂起事务,放下下一个事务
txObject.setConnectionHolder((ConnectionHolder)null);
//之前的事务解绑
return TransactionSynchronizationManager.unbindResource(this.obtainDataSource());
}
这时候之前的事务就被挂起啦,接下来就是对新事务,连接的建立和绑定流程啦。
//判断当前线程是否已经有了连接了,因为刚刚对这个是清空了的,就是解绑那里,这里再验证一遍
if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//这里获取一个新的连接
Connection newCon = this.obtainDataSource().getConnection();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
//将新连接和当前线程的事务对象绑定
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
//获取连接Holder
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
//设置当前连接是否可读
txObject.setPreviousIsolationLevel(previousIsolationLevel);
//关闭自动提交
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
//准备事务,设置是否只读
this.prepareTransactionalConnection(con, definition);
//设置事务生效
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = this.determineTimeout(definition);
if (timeout != -1) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
//当前是新的连接则进行绑定,因为如果不是新的,当前是已经绑定,再绑定会报错
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
}
其实到这里就能看出核心了,为什么REQUIRE_NEW
发生回滚的时候不会影响父方法,因为它们是两个不同的连接,总的来说,整体思想就是,如果当前需要创建新事务,也就是REQUIRE_NEW
状态,则用新连接去绑定当前的事务管理,也就是我们当前使用哪个连接去操作,这时候子方法是REQUIRE_NEW
状态,出现异常我们进行回滚不会影响到父方法,因为我们绑定的是当前子方法的连接。
进行手写一个
连接相关操作工具类
/**
* @author chujun
* @version 1.0
* @date 2021-03-06 22:42
*/
@Component
public class ConnectionHolderUtil {
private static DataSource dataSource;
private static final Logger log = LoggerFactory.getLogger(ConnectionHolderUtil.class);
@Autowired
public void setDataSource(DataSource dataSource) {
ConnectionHolderUtil.dataSource = dataSource;
}
private static ThreadLocal<ConnectionHolder> connectionHolderThreadLocal = new ThreadLocal<>();
/**
* 获取数据库连接
* @return Connection
*/
public static ConnectionHolder getConnectionHolder(boolean isNew){
ConnectionHolder connectionHolder = connectionHolderThreadLocal.get();
//如果有连接,并不需要生成新的直接返回
if(connectionHolder != null && !isNew){
return connectionHolder;
}
try {
//获取新连接
Connection connection = dataSource.getConnection();
//关闭自动提交
connection.setAutoCommit(false);
connectionHolder = new ConnectionHolder(connection);
connectionHolderThreadLocal.set(connectionHolder);
//绑定连接
TransactionSynchronizationManager.bindResource(dataSource,connectionHolder);
return connectionHolder;
} catch (SQLException e) {
log.error("数据库连接获取失败",e);
return null;
}
}
/**
* 提交事务
*/
public static void commit(){
ConnectionHolder connectionHolder = connectionHolderThreadLocal.get();
if(connectionHolder == null){
return;
}
try {
connectionHolder.getConnection().commit();
} catch (SQLException e) {
log.error("提交失败",e);
}
}
/**
* 事务回滚
*/
public static void rollback(){
ConnectionHolder connectionHolder = connectionHolderThreadLocal.get();
if(connectionHolder == null){
return;
}
try {
connectionHolder.getConnection().rollback();
} catch (SQLException e) {
log.error("回滚失败",e);
}
}
/**
* 关闭连接
*/
public static void close(){
ConnectionHolder connectionHolder = connectionHolderThreadLocal.get();
if(connectionHolder == null){
return;
}
Connection connection = connectionHolder.getConnection();
try {
connection.close();
} catch (SQLException e) {
log.error("数据库连接关闭失败",e);
}
}
/**
* 恢复挂起的事务
*/
public static void resume(Object susPend){
TransactionSynchronizationManager.unbindResource(dataSource);
TransactionSynchronizationManager.bindResource(dataSource,susPend);
connectionHolderThreadLocal.set((ConnectionHolder) susPend);
}
/**
* 挂起当前事务
*/
public static Object hangTrasaction(){
return TransactionSynchronizationManager.unbindResource(dataSource);
}
/**
* 判断当前连接是否已经关闭
* @return
*/
public static boolean isClose(){
if(connectionHolderThreadLocal.get() == null){
return true;
}
try {
return connectionHolderThreadLocal.get().getConnection().isClosed();
} catch (SQLException e) {
log.error("获取连接状态失败");
}
return true;
}
}
AOP切面拦截
/**
* @author chujun
* @version 1.0
* @date 2021-02-28 15:31
*/
@Aspect
@Component
public class TransactionAspect{
private static final Logger log = LoggerFactory.getLogger(TransactionAspect.class);
@Pointcut("@annotation(com.cj.annotion.Transactional)")
public void point(){}
@Around("point()")
public Object transactionHandle(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Transactional transactional = methodSignature.getMethod().getAnnotation(Transactional.class);
//方法执行结果
Object result = null;
//被挂起的事务
Object supSend = null;
boolean isNew = false;
//如果事务类型是 require require_new
if(transactional.propagation() == Propagation.REQUIRED || transactional.propagation() == Propagation.REQUIRES_NEW)
{
//如果事务类型是 require
if(transactional.propagation() == Propagation.REQUIRED){
//有事务则用原来的事务
ConnectionHolderUtil.getConnectionHolder(false);
}
//如果事务类型是 require_new
else {
//挂起当前事务
//新建一个连接事务,然后绑定到事务同步管理器
//绑定新连接
supSend = ConnectionHolderUtil.hangTrasaction();
//获取一个新连接
isNew = true;
ConnectionHolderUtil.getConnectionHolder(true);
}
try {
//执行拦截的方法
result = joinPoint.proceed();
//提交事务
if(!ConnectionHolderUtil.isClose()) {
ConnectionHolderUtil.commit();
}
}catch (Exception e){
//回滚事务
if(!ConnectionHolderUtil.isClose()) {
ConnectionHolderUtil.rollback();
}
}finally {
//关闭连接
if(isNew) {
ConnectionHolderUtil.close();
}
//恢复之前挂起的事务
if(supSend != null) {
ConnectionHolderUtil.resume(supSend);
}
}
}
//否则
else {
log.info("事务类型暂时不支持");
}
log.info("切面执行完成");
return result;
}
}
事务注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
Propagation propagation() default Propagation.REQUIRED;
Class <? extends Throwable>[] rollBackFor() default {};
}
传播方式
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
具体调用客户端
@Service
public class BlogService {
@Autowired
private BlogDao blogDao;
@Transactional(propagation = Propagation.REQUIRED)
public void testMyTransaction(Blog blog){
blogDao.insertBlog(blog);
BlogService blogService = (BlogService) AopContext.currentProxy();
try {
blogService.testMyTransaction2(blog);
}catch (Exception e){
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testMyTransaction2(Blog blog){
blogDao.insertBlog(blog);
int a = 1/0;
}
}
如上图,这时候如果testMyTransaction2
出现异常,是不会影响到testMyTransaction
的,因为它们是不同事务。
总结
Spring事务管理确实也用到,我也是接触这块没多久,但是之前只会用,经过这几天面试,我发现学习东西一定要深入的去了解,只会用可不行。不知道大家有没有发现存ConnectionHolder的时候用的ThreadLocal
,这样是为了多个客户端同时请求的时候互不干扰,线程隔离的,如果不清楚ThreadLocal
的可以了解一下ThreadLocal使用和原理,面试有被问到。
最后的最后,感谢大家浏览,如有任何错误欢迎指出。