mybatis源码解析(一)-开篇
mybatis源码解析(二)-加载过程
mybatis源码解析(三)-SqlSession.selectOne类似方法调用过程
mybatis源码解析(四)-Mapper方法调用过程
mybatis源码解析(五)-mybatis如何实现的事务控制
mybatis源码解析(六)-配合spring-tx实现事务的原理
mybatis源码解析(七)-当mybatis一级缓存遇上spring
转载请标明出处:
http://blog.csdn.net/bingospunky/article/details/79541494
本文出自马彬彬的博客
TransactionAspectSupport.TransactionInfo包含TransactionStatus对象,TransactionStatus对象包含DataSourceTransactionManager.DataSourceTransactionObject(简称Transaction对象),DataSourceTransactionManager.DataSourceTransactionObject包含ConnectionHolder对象,ConnectionHolder包含Connection。
创建ConnectionHolder需要DataSource,所以DataSourceTransactionManager创建ConnectionHolder。
其他对象基本是在TransactionAspectSupport和DataSourceTransactionManager这两个类中。
这些对象基本都是在before advice过程中创建的。
TransactionAspectSupport里的ThreadLocal维护TransactionAspectSupport.TransactionInfo。 TransactionSynchronizationManager里的ThreadLocal维和线程相关的TransactionStatus。
其他对象在引用它的对象中。
首先spring-tx是基于aop的,既然aop,关键就在advice里。增强后的代码是这个样子的:
Code1
org.springframework.transaction.interceptor.TransactionAspectSupport中
TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
retVal = invocation.proceedWithInvocation();
} catch (Throwable var15) {
this.completeTransactionAfterThrowing(txInfo, var15);
throw var15;
} finally {
this.cleanupTransactionInfo(txInfo);
}
this.commitTransactionAfterReturning(txInfo);
return retVal;
Code1第1行,会创建一个新的TransactionStatus,并放在适当的ThreadLocal里,创建TransactionStatus的过程处理的情况比较多,比如现在是否是在Transaction中、新的propagation是怎样的。我们设置的propagation的值也是在这里生效的,这部分代码会根据propagation的不同做一些不同的操作(比如是否新建Connection,是否开启新的Transaction)。
创建TransactionStatus的代码如下:
Code2
org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
Object transaction = this.doGetTransaction();
boolean debugEnabled = this.logger.isDebugEnabled();
if (definition == null) {
definition = new DefaultTransactionDefinition();
}
if (this.isExistingTransaction(transaction)) {
return this.handleExistingTransaction((TransactionDefinition)definition, transaction, debugEnabled);
} else if (((TransactionDefinition)definition).getTimeout() < -1) {
throw new InvalidTimeoutException("Invalid transaction timeout", ((TransactionDefinition)definition).getTimeout());
} else if (((TransactionDefinition)definition).getPropagationBehavior() == 2) {
throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
} else if (((TransactionDefinition)definition).getPropagationBehavior() != 0 && ((TransactionDefinition)definition).getPropagationBehavior() != 3 && ((TransactionDefinition)definition).getPropagationBehavior() != 6) {
if (((TransactionDefinition)definition).getIsolationLevel() != -1 && this.logger.isWarnEnabled()) {
this.logger.warn("Custom isolation level specified but no actual transaction initiated; isolation level will effectively be ignored: " + definition);
}
boolean newSynchronization = this.getTransactionSynchronization() == 0;
return this.prepareTransactionStatus((TransactionDefinition)definition, (Object)null, true, newSynchronization, debugEnabled, (Object)null);
} else {
AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);
if (debugEnabled) {
this.logger.debug("Creating new transaction with name [" + ((TransactionDefinition)definition).getName() + "]: " + definition);
}
try {
boolean newSynchronization = this.getTransactionSynchronization() != 2;
DefaultTransactionStatus status = this.newTransactionStatus((TransactionDefinition)definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
this.doBegin(transaction, (TransactionDefinition)definition);
this.prepareSynchronization(status, (TransactionDefinition)definition);
return status;
} catch (RuntimeException var7) {
this.resume((Object)null, suspendedResources);
throw var7;
} catch (Error var8) {
this.resume((Object)null, suspendedResources);
throw var8;
}
}
}
Code1第4行就是调用被代理对象的方法。
Code1第6行是处理异常的,如果抛出的异常是@Transaction回滚的,则进行适当的回滚操作(包括真正的回滚或者标记回滚,后面会解释);如果抛出的异常不在回滚范围,那么这里依然会执行commit操作,所以我们一定要注意异常类型,尤其是spring-tx配合别的数据框orm框架使用时(后面也会煮个栗子)。
Code1第9行,清理掉ThreadLocal里的TransactionAspectSupport.TransactionInfo,需要把TransactionAspectSupport.TransactionInfo设置成前一个TransactionAspectSupport.TransactionInfo,这里有一个链式的用法有一点巧妙。事情是这个样子的:调用A.a方法时,设置TransactionAspectSupport.TransactionInfo对象tia在ThreadLocal,在A.a方法中又调用了B.b方法,这个方法也是事务的,这时,需要构造一个TransactionAspectSupport.TransactionInfo对象tib放在ThreadLocal里。且tib有一个属性叫oldTransactionInfo指向了tia。当B.b方法执行完了,需要把tib在ThreadLocal里清除,把tia放进ThreadLocal里,这样通过tib的oldTransactionInfo属性可以找到tia。以此类推,链式形成。
Code1第11行,当业务代码执行完成时,根据情况进行commit或者rollback,这里和Code1第6行的代码是相似的,只是那个是有异常发生时。这里真正处理是commit还是rollback的代码在TransactionManager里的,调用代码如下:
Code3
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning
if (txInfo != null && txInfo.hasTransaction()) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
@Transaction里有几个参数可以设置,比较关键的一个就是propagation,就是传播规则。
有下面这几种:
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
具体含义可以查询相关api,我在这里就叙述一下REQUIRED和REQUIRES_NEW是如何生效的。
还是刚才那个例子:
已经调用了A.a方法,A.a是被@Transaction注解的方法,在A.a中调用了B.b方法,B.b也是@Transaction注解的方法,下面分类讨论:
Code4
org.springframework.jdbc.datasourceDataSourceTransactionManager.doSetRollbackOnly
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
txObject.setRollbackOnly();
}
万变不离其宗,不论上层添加几个概念,java的单机事务都是以jdbc的api为基础的,关键就是同一个Connection的begin/commit/rollback。
spring-tx的原理上面已经描述过了,这里做一个简化:
在before advice中,先获取TransactionManager,TransactionManager中包含DataSource,再根据当前的事务环境和@Transaction.propagation构造出Connection,放在ThreadLocal里的Map里,Map的key是DataSource。然后执行业务代码。在return advice或exception advice中,先获取之前生成的Connection,然后根据异常情况以及@Transaction.propagation(并不是直接使用这个属性,而是使用它转化后的信息,比如newTransaction的值,这个属性在before advice已经转化到TransactionStatus里了)进行commit或者rollback(或等价操作,设置标记,等外层commit或者rollback)。
spring-tx生效的关键:
Connection放在ThreadLocal中的Map里,Map的key为DataSource。所以确保同一个Connection,需要在同一个线程中,这个没有问题,需要使用同一个DataSource去Map里取。我们自己去写这几行代码是比较麻烦的,Spring-tx给我们提供了一个工具类来做这个事情,public static Connection org.springframework.jdbc.datasourc.DataSourceUtils.getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException,通过这个方法,只要线程和DataSource正确,就可以获取到正确的Connection。我们使用Connection进行增删改查,且不执行与Transaction相关的方法,advice去完成Transaction。
我们使用Mybatis-spring这个框架集成一下mybatis和spring,mybatis在@Transaction注解标记的方法内是正常的支持事务的。那么mybatis是如何获取到正确的Connection的呢?
在我的mybatis源码解析(五)-mybatis如何实现的事务控制这篇文章中可以获取如下的信息:用户操作org.apache.ibatis.session.SqlSession这个类,org.apache.ibatis.session.SqlSession操作org.apache.ibatis.executor.Executor去执行sql,org.apache.ibatis.executor.Executor只是使用Connection,但是不去维护Connection,它维护了org.apache.ibatis.transaction.Transaction,由org.apache.ibatis.transaction.Transaction去创建、维护、回收Connection。
在mybatis-spring初始化的过程会执行如下的代码:
Code5
org.mybatis.spring.SqlSessionFactoryBean
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
Code5给Configuration设置了SpringManagedTransactionFactory,SpringManagedTransactionFactory就是用来生成Transaction的,它生成的类为org.mybatis.spring.transaction.SpringManagedTransaction,org.mybatis.spring.transaction.SpringManagedTransaction生成Connection的代码如下:
Code6
org.mybatis.spring.transaction.SpringManagedTransaction
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
if (logger.isDebugEnabled()) {
logger.debug("JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
}
}
Code6第2行,就是使用的spring的方法去获取到对应的Connection了。这就对上了。
@Transaction注解默认回滚的是RunTimeException,为了更方便地配合@Transaction注解,Mybatis默认抛出的是RunTimeException,这点是比较符合的。关于异常会在后面section描述。
spring-tx是基于spring aop的,aop对于proxy模式,支持jdbc proxy和cglib。
使用时,默认的proxy-target-class=”false”就能满足我们的需求;如果需要使用proxy-target-class=”true”,需要把@Transaction注解加在实现类的方法上,因为注解是不支持继承的。
注意:
@Transaction默认的回滚是RunTimeException,使用JdbcTemplate或者Mybatis产生的都是RunTimeException。如果是下面这种代码:
Code7
@Transactional
public void transactionInJdbcApi2() throws Exception{
Connection connection = DataSourceUtils.getConnection(dataSource);
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO foo(id, name) values(1, 'aaa')");
preparedStatement.executeUpdate();
PreparedStatement preparedStatement2 = connection.prepareStatement("INSERT INTO foo(id, name) values(2, 'xxx')");
preparedStatement2.executeUpdate();
}
Code7代码是不能支持事务的,如果在第二个插入语句出现主键重复异常时,抛出的是check exception,不能回滚。
为了解决上面的问题,我们让配置的回滚规则和出现的异常对应起来就行了。比如,1:配置@Transaction针对所有异常进行回滚。2.对Exception进行处理,使其抛出RunTimeException。
You can also use the org.springframework.transaction.PlatformTransactionManager directly to manage your transaction. Simply pass the implementation of the PlatformTransactionManager you are using to your bean through a bean reference. Then, using the TransactionDefinition and TransactionStatus objects you can initiate transactions, roll back, and commit.
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);