Jade Dungeon

JDBC事务

事务回滚

Spring中事务实现

在Spring中TransactionInterceptorPlatformTransactionManager这两个类是整个事务模块的核心, TransactionInterceptor负责拦截方法执行,进行判断是否需要提交或者回滚事务。 PlatformTransactionManager是Spring 中的事务管理接口,真正定义了事务如何回滚和提交。 我们重点研究下这两个类的源码。

TransactionInterceptor类中的代码有很多,我简化一下逻辑,方便说明:

//以下代码省略部分内容
public Object invoke(MethodInvocation invocation) throws Throwable {

	//获取事务调用的目标方法
	Class<?> targetClass = invocation.getThis() != null ?
		AopUtils.getTargetClass(invocation.getThis()) : null;
		
	//执行带事务调用
	return invokeWithinTransaction(
			invocation.getMethod(), targetClass, invocation::proceed);
}

invokeWithinTransaction 简化逻辑如下:

//TransactionAspectSupport.class
//省略了部分代码
protected Object invokeWithinTransaction(//
		Method method, 
		@Nullable Class<?> targetClass, 
		final InvocationCallback invocation) throws Throwable 
{
	Object retVal;
	try {
			//调用真正的方法体
			retVal = invocation.proceedWithInvocation();
	} catch (Throwable ex) {
			// 如果出现异常,执行事务异常处理
			completeTransactionAfterThrowing(txInfo, ex);
			throw ex;
	} finally {
			//最后做一下清理工作,主要是缓存和状态等
			cleanupTransactionInfo(txInfo);
	}
	//如果没有异常,直接提交事务。
	commitTransactionAfterReturning(txInfo);
	return retVal;
}

事务出现异常回滚的逻辑completeTransactionAfterThrowing如下:

//省略部分代码
protected void completeTransactionAfterThrowing(
		@Nullable TransactionInfo txInfo, 
		Throwable ex) 
{
	// 判断是否需要回滚,判断的逻辑就是看有没有声明事务属性,
	// 同时判断是不是在目前的这个异常中执行回滚。
	if (txInfo.transactionAttribute != null && 
			txInfo.transactionAttribute.rollbackOn(ex))
	{
		//执行回滚
		txInfo.getTransactionManager().rollback(
				txInfo.getTransactionStatus());
	} else {
		//否则不需要回滚,直接提交即可。
		txInfo.getTransactionManager().commit(
				txInfo.getTransactionStatus());
	}
}

真正执行回滚逻辑的代码中PlatformTransactionManager接口的子类, 我们以JDBC的事务为例,DataSourceTransactionManager就是jdbc的事务管理类。 跟踪上面的代码rollback(txInfo.getTransactionStatus())可以发现最终执行的代码如下:

@Override
protected void doRollback(DefaultTransactionStatus status) {
	DataSourceTransactionObject txObject = 
		(DataSourceTransactionObject) status.getTransaction();
	Connection con = txObject.getConnectionHolder().getConnection();
	
	if (status.isDebug()) {
		logger.debug("Rolling back JDBC transaction on " + 
				"Connection [" + con + "]");
	}

	try {
		con.rollback(); //调用jdbc的 rollback进行回滚事务。
	} catch (SQLException ex) {
		throw new TransactionSystemException(
				"Could not roll back JDBC transaction", ex);
	}
}

自己动手简单实现事务

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

新增一个注解:

/**
 * @description:
 * @author: luozhou 
 * @create: 2020-03-29 17:05
 **/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyTransaction {
    //指定异常回滚
    Class<? extends Throwable>[] rollbackFor() default {};
}

新增连接管理器,该类帮助我们管理连接, 该类的核心功能是把取出的连接对象绑定到线程上, 方便在AOP处理中取出,进行提交或者回滚操作:

@Component
public class DataSourceConnectHolder {

	@Autowired
	DataSource dataSource;
	
	/**
	 * 线程绑定对象
	 */
	ThreadLocal<Connection> resources = //
		new NamedThreadLocal<>("Transactional resources");

	public Connection getConnection() {
		Connection con = resources.get();
		if (con != null) { return con; }
		try {
			con = dataSource.getConnection();
			//为了体现事务,全部设置为手动提交事务
			con.setAutoCommit(false);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		resources.set(con);
		return con;
	}

	public void cleanHolder() {
		Connection con = resources.get();
		if (con != null) {
			try {
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		resources.remove();
	}
	
}

新增一个切面,这部分是事务处理的核心,先获取注解上的异常类, 然后捕获住执行的异常,判断异常是不是注解上的异常或者其子类, 如果是就回滚,否则就提交。

@Aspect
@Component
public class MyTransactionAopHandler {

	@Autowired
	DataSourceConnectHolder connectHolder;
	
	Class<? extends Throwable>[] es;

	//拦截所有MyTransaction注解的方法
	@org.aspectj.lang.annotation.Pointcut(
			"@annotation(luozhou.top.annotion.MyTransaction)")
	public void Transaction() { 
	}

	@Around("Transaction()")
	public Object TransactionProceed(ProceedingJoinPoint proceed) 
		throws Throwable 
	{
		Object result = null;
		Signature signature = proceed.getSignature();
		MethodSignature methodSignature = (MethodSignature) signature;
		Method method = methodSignature.getMethod();
		
		if (method == null) { return result; }
		
		MyTransaction transaction = 
			method.getAnnotation(MyTransaction.class);

		if (transaction != null) {
			es = transaction.rollbackFor();
		}
		try {
			result = proceed.proceed();
		} catch (Throwable throwable) {
			//异常处理
			completeTransactionAfterThrowing(throwable);
			throw throwable;
		}
		doCommit();           //直接提交
		return result;
	}
	
	/* 执行回滚,最后关闭连接和清理线程绑定 */
	private void doRollBack() {
		try {
			connectHolder.getConnection().rollback();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			connectHolder.cleanHolder();
		}
	}
	
	// 执行提交,最后关闭连接和清理线程绑定
	private void doCommit() {
		try {
			connectHolder.getConnection().commit();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			connectHolder.cleanHolder();
		}
	}
	
	// 异常处理,捕获的异常是目标异常或者其子类,
	// 就进行回滚,否则就提交事务
	private void completeTransactionAfterThrowing(Throwable throwable) {
		if (es != null && es.length > 0) {
			for (Class<? extends Throwable> e : es) {
				if (e.isAssignableFrom(throwable.getClass())) {
					doRollBack();
				}
			}
		}
		doCommit();
	}
	
}

测试验证创建一个tb_test表,表结构如下:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_test
-- ----------------------------
DROP TABLE IF EXISTS `tb_test`;
CREATE TABLE `tb_test` (
  `id` int(11) NOT NULL,
  `email` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

SET FOREIGN_KEY_CHECKS = 1;

编写一个Service。saveTest()方法调用了2个插入语句, 同时声明了@MyTransaction事务注解,遇到NullPointerException就进行回滚, 最后我们执行了除以0操作,会抛出ArithmeticException。 我们用单元测试看看数据是否会回滚。

@Service
public class MyTransactionTest implements TestService {

	@Autowired
	DataSourceConnectHolder holder;
	
	//一个事务中执行两个sql插入
	@MyTransaction(rollbackFor = NullPointerException.class)
	@Override
	public void saveTest(int id) {
		saveWitharamters(id, "luozhou@gmail.com");
		saveWitharamters(id + 10, "luozhou@gmail.com");
		int aa = id / 0;
	}
	
	//执行sql
	private void saveWitharamters(int id, String email) {
		String sql = "insert into tb_test values(?,?)";
		Connection connection = holder.getConnection();
		PreparedStatement stmt = null;
		try {
			stmt = connection.prepareStatement(sql);
			stmt.setInt(1, id);
			stmt.setString(2, email);
			stmt.executeUpdate();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

}

单元测试

@SpringBootTest
@RunWith(SpringRunner.class)
class SpringTransactionApplicationTests {
	@Autowired
	private TestService service;

	@Test
	void contextLoads() throws SQLException {
		service.saveTest(1);
	}

}

图代码声明了事务对NullPointerException异常进行回滚, 运行中遇到了ArithmeticException异常,所以是不会回滚的, 我们在右边的数据库中刷新发现数据正常插入成功了,说明并没有回滚。

我们把回滚的异常类改为ArithmeticException,把原数据清空再执行一次, 出现了ArithmeticException异常,这个时候查看数据库是没有记录新增成功了, 这说明事物进行回滚了,表明我们的注解起作用了。