1. The transactional annotation opens the transaction
value
transactionManager specifies the transaction manager
propagation transaction propagation behavior
isolation isolation level dirty read phantom read non-repeatable read serialization
timeout transaction timeout
The transaction cannot run for too long, because the transaction may involve locking the database, which will occupy resources and cause a deadlock. If the code is not executed within a certain period of time, the transaction will automatically roll back without waiting for its end
readOnly is read-only
If all operations on the database in a transaction are read-only, that is to say, these operations only read database data and do not perform update operations. At this time, we implement the read-only attribute for the transaction, which can help the database engine optimize the transaction , so as to improve the efficiency of database read and write
rollbackFor executes under specified abnormal conditions. By default, if not specified, other exceptions under Exception except RuntimeException and its subclasses) will not roll back
rollbackForClassName rollbackFor, only the class name is needed here
noRollbackFor no rollback exception
noRollbackForClassName The name of the exception that does not roll back
2. The scenario where the Transactional transaction does not take effect
Class internal access : the a1 method of class A is not marked with @Transactional, the a2 method is marked with @Transactional, and a2 is called in a1;
Private method : mark the @Transactional annotation on the non-public method;
Exception mismatch : @Transactional does not set the rollbackFor attribute, and the method returns exceptions such as Exception;
Multi-threading : the main thread and the sub-thread call, and the thread throws an exception.
3. Transactional overview
The essence is to use jdbc transactions for transaction control
Mechanism implementation of dynamic proxy based on spring
4. Transaction workflow
第一块是后置处理,我们在创建 Louzai Bean 的后置处理器中,里面会做两件事情:
获取 Louzai 的切面方法:首先会拿到所有的切面信息,和 Louzai 的所有方法进行匹配,然后找到 Louzai 所有需要进行事务处理的方法,匹配成功的方法,还需要将事务属性保存到缓存 attributeCache 中。
创建 AOP 代理对象:结合 Louzai 需要进行 AOP 的方法,选择 Cglib 或 JDK,创建 AOP 代理对象。
第二块是事务执行,整个逻辑比较复杂,我只选取 4 块最核心的逻辑,分别为从缓存拿到事务属性、创建并开启事务、执行业务逻辑、提交或者回滚事务。
5.源码解析
5.1代码入口
5.2创建代理对象
先获取 louzai 类的所有切面列表;
创建一个 AOP 的代理对象。
5.2.1获取切面列表
这里有 2 个重要的方法,先执行 findCandidateAdvisors(),待会我们还会再返回 findEligibleAdvisors()。
依次返回,重新来到 findEligibleAdvisors()。
进入 canApply(),开始匹配 louzai 的切面。
这里只会匹配到 Louzai.testSuccess() 方法,我们直接进入匹配逻辑。
如果匹配成功,还会把事务的属性配置信息放入 attributeCache 缓存。
我们依次返回到 getTransactionAttribute(),再看看放入缓存中的数据。
再回到该小节开头,我们拿到 louzai 的切面信息,去创建 AOP 代理对象。
5.2.2创建aop代理对象
5.3事务执行
回到业务逻辑,通过 louzai 的 AOP 代理对象,开始执行主方法。
因为代理对象是 Cglib 方式创建,所以通过 Cglib 来执行。
下面的代码是事务执行的核心逻辑 invokeWithinTransaction()。
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 PlatformTransactionManager tm = determineTransactionManager(txAttr);
//从tx属性对象中获取出标注了@Transactionl的方法描述符
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
//处理声明式事务
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
//有没有必要创建事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
//调用钩子函数进行回调目标方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
//抛出异常进行回滚处理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//清空我们的线程变量中transactionInfo的值
cleanupTransactionInfo(txInfo);
}
//提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
//编程式事务
else {
// 这里不是我们的重点,省略...
}
}
5.3.1获取事务属性
在 invokeWithinTransaction() 中,我们找到获取事务属性的入口。
从 attributeCache 获取事务的缓存数据,缓存数据是在 “2.2.1 获取切面列表” 中保存的。
5.3.2创建事务
通过 doGetTransaction() 获取事务。
protected Object doGetTransaction() {
//创建一个数据源事务对象
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
//是否允许当前事务设置保持点
txObject.setSavepointAllowed(isNestedTransactionAllowed());
/**
* TransactionSynchronizationManager 事务同步管理器对象(该类中都是局部线程变量)
* 用来保存当前事务的信息,我们第一次从这里去线程变量中获取 事务连接持有器对象 通过数据源为key去获取
* 由于第一次进来开始事务 我们的事务同步管理器中没有被存放.所以此时获取出来的conHolder为null
*/
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
//返回事务对象
return txObject;
}
通过 startTransaction() 开启事务。
下面是开启事务的详细逻辑,了解一下即可。
protected void doBegin(Object transaction, TransactionDefinition definition) {
//强制转化事务对象
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
//判断事务对象没有数据库连接持有器
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//通过数据源获取一个数据库连接对象
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
//把我们的数据库连接包装成一个ConnectionHolder对象 然后设置到我们的txObject对象中去
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
//标记当前的连接是一个同步事务
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
//为当前的事务设置隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
//关闭自动提交
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
//判断事务为只读事务
prepareTransactionalConnection(con, definition);
//设置事务激活
txObject.getConnectionHolder().setTransactionActive(true);
//设置事务超时时间
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// 绑定我们的数据源和连接到我们的同步管理器上 把数据源作为key,数据库连接作为value 设置到线程变量中
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
//释放数据库连接
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
最后返回到 invokeWithinTransaction(),得到 txInfo 对象。
5.3.3执行逻辑分析
还是在 invokeWithinTransaction() 中,开始执行业务逻辑。
进入到真正的业务逻辑。
执行完毕后抛出异常,依次返回,走后续的回滚事务逻辑。
5.3.4 回滚事务
还是在 invokeWithinTransaction() 中,进入回滚事务的逻辑。
。
执行回滚逻辑很简单,我们只看如何判断是否回滚。
如果抛出的异常类型,和事务定义的异常类型匹配,证明该异常需要捕获。
之所以用递归,不仅需要判断抛出异常的本身,还需要判断它继承的父类异常,满足任意一个即可捕获。
到这里,所有的流程结束。