这篇源码解析,和 Spring AOP 中的知识有很多重合的地 ,但是 AOP 要稍微简单 些。
下 我会简单介绍 下 Spring 事务的基础知识,以及使 法,然后直接对源码进 拆解。
目录:
下 是 DB 数据和 DB 操作接 :
Id | uname | usex |
1 | 小王 | 男 |
2 | 小李 | 女 |
1 | 小赵 | 男 |
@Data
public class MyUser {
private int id;
private String uname;
private String usex;
}
复制代码
public interface UserDao {
// select * from user_test where id = "#{id}"
MyUser selectUserById(Integer uid);
// update user_test set uname =#{uname},usex = #{usex} where id = #{id}
int updateUser(MyUser user);
}
复制代码
基础测试代码,testSuccess() 是事务 效的情况:
@Service
public class Model {
@Autowired
private UserDao userDao;
public void update(Integer id) {
MyUser user = new MyUser();
user.setId(id);
user.setUname("张三-testing");
user.setUsex(" ");
userDao.updateUser(user);
}
public MyUser query(Integer id) {
MyUser user = userDao.selectUserById(id);
return user;
}
// 正常情况
@Transactional(rollbackFor = Exception.class)
public void testSuccess() throws Exception {
Integer id = 1;
MyUser user = query(id);
System.out.println("原记录:" + user);
update(id);
throw new Exception("事务 效");
}
}
复制代码
执 :
public class SpringMyBatisTest {
public static void main(String[] args) throws Exception {
String xmlPath = "applicationContext.xml";
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext(xmlPath);
Model uc = (Model) applicationContext.getBean("model");
uc.testSuccess();
}
}
复制代码
输出:
为了 便 家能更好看懂后 的源码,我先整体介绍 下源码的执 流程,让 家有 个整体的认识,否则容易被 绕进去。
整个 Spring 事务源码,其实分为 2 块,我们会结合上 的示例,给 家进 讲解。
第 块是后置处理,我们在创建 Model Bean 的后置处理器中, 会做两件事情:
获取 Model 的切 法: 先会拿到所有的切 信息,和 Model 的所有 法进 匹配,然后找到 Model 所有需 要进 事务处理的 法,匹配成功的 法,还需要将事务属性保存到缓存 attributeCache 中。
创建 AOP 代理对象:结合 Model 需要进 AOP 的 法,选择 Cglib 或 JDK,创建 AOP 代理对象。
第 块是事务执 ,整个逻辑 较复杂,我只选取 4 块最核 的逻辑,分别为从缓存拿到事务属性、创建并开启事 务、执 业务逻辑、提交或者回滚事务。
注意:Spring 的版本是 5.2.15.RELEASE,否则和我的代码不 样!!!
上 的知识都不难,下 才是我们的重头戏,让我们一起 遍代码流程。
这 需要多跑 次,把前 的 beanName 跳过去,只看 model。
进 doGetBean(),进 创建 Bean 的逻辑。
进 createBean(),调 doCreateBean()。
进 doCreateBean(),调 initializeBean()。
如果看过我前 期系列源码的同学,对这个 应该会 常熟悉,其实就是 来创建代理对象。
这 是重点!敲 板!!!
先获取 model 类的所有切 列表; 创建 个 AOP 的代理对象。
这 有 2 个重要的 法,先执 findCandidateAdvisors(),待会我们还会再返回 findEligibleAdvisors()。
依次返回,重新来到 findEligibleAdvisors()。
进 canApply(),开始匹配 model 的切 。
这 是重点!敲 板!!! 这 只会匹配到 Model.testSuccess() 法,我们直接进 匹配逻辑。
如果匹配成功,还会把事务的属性配置信息放 attributeCache 缓存。
我们依次返回到 getTransactionAttribute(),再看看放 缓存中的数据。
再回到该 节开头,我们拿到 mdoel 的切 信息,去创建 AOP 代理对象。
创建 AOP 代理对象的逻辑,在上 篇 章【Spring源码解析-Spring AOP】讲解过,我是通过 Cglib 创建,感兴趣的同学可以翻 下我的历史 章。
回到业务逻辑,通过 model 的 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 {
// 这 不是我们的重点,省略...
}
}
复制代码
在 invokeWithinTransaction() 中,我们找到获取事务属性的 。
从 attributeCache 获取事务的缓存数据,缓存数据是在 “3.2.1 获取切 列表” 中保存的。
通过 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);
最后返回到 invokeWithinTransaction(),得到 txInfo 对象。
//关闭 动提交
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
}
}
复制代码
最后返回到 invokeWithinTransaction(),得到 txInfo 对象。
还是在 invokeWithinTransaction() 中,开始执 业务逻辑。
进 到真正的业务逻辑。
执 完毕后抛出异常,依次返回, 后续的回滚事务逻辑。
还是在 invokeWithinTransaction() 中,进 回滚事务的逻辑。
执 回滚逻辑很简单,我们只看如何判断是否回滚。
如果抛出的异常类型,和事务定义的异常类型匹配,证明该异常需要捕获。
之所以 递归,不仅需要判断抛出异常的本身,还需要判断它继承的 类异常,满 任意 个即可捕获。
到这 ,所有的流程结束。
我们再 节 下, 章先介绍了事务的使 示例,以及事务的执 流程。
之后再剖析了事务的源码,分为 2 块:
先匹配出 model 对象所有关于事务的切 列表,并将匹配成功的事务属性保存到缓存; 从缓存取出事务属性,然后创建、启动事务,执 业务逻辑,最后提交或者回滚事务。
页面更新:2024-05-11
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号