https://blog.csdn.net/bingospunky/article/details/79283104
https://blog.csdn.net/bingospunky/article/details/79541494
这篇博客主要展示mybatis是如何控制事务的。
下面两个例子分别展示一下我们使用/不使用Trancation的例子。
Code 1不使用事务,自动提交
SqlSession session = sqlSessionFactory.openSession(true);
session.insert("insert into table1(id, name) values(1, 'a');");
session.insert("insert into table1(id, name) values(2, 'b');");
1
2
3
Code 2使用事务,不自动提交
SqlSession session = sqlSessionFactory.openSession(false);
try {
session.insert("insert into table1(id, name) values(1, 'a');");
session.insert("insert into table1(id, name) values(2, 'b');");
session.commit();
} catch (Exception e) {
session.rollback();
}
在mybatis api中,控制Trancation是在创建org.apache.ibatis.session.SqlSession的时候设置boolean类型的参数来实现的。调用方法没有差别,支持事务的最后会调用commoit或者rollback。
创建SqlSession
创建org.apache.ibatis.session.SqlSession的过程如下:
Code 3
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
第7行创建了Transaction对象。第8行创建了Executor对象。第9行创建了SqlSession对象。下面第这三个对象做一个简单介绍:
1、org.apache.ibatis.session.SqlSession
用户直接操作的对象,该对象对用户调用的方法进行一些简单的处理,然后调用Executor的方法执行具体的操作。
可以注意一下,该对象维护了一个private boolean dirty;用来标记在上次commit之后有没有执行新的增、删、改操作。该类的commit会根据这个属性来确定会不会执行jdbc代码的commit方法,也可以强制commit而跳过这个属性的限制。
2、org.apache.ibatis.executor.Executor
该对象是具体执行sql的类,它操作的是org.apache.ibatis.mapping.MappedStatement,这个对象在执行真正的sql时,会使用jdbc的java.sql.Connection,但是它不会去生成、维护这个java.sql.Connection,它包含了一个org.apache.ibatis.transaction.Transaction,由Transaction这个对象去维护Transaction。
3、org.apache.ibatis.transaction.Transaction
这是个接口,接口的定义如下:
Code 4
org.apache.ibatis.transaction.Transaction
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
}
它的一个实现类org.apache.ibatis.transaction.jdbc.JdbcTransaction,这个类里维护了DataSource,在需要的时候会创建java.sql.Connection,在创建完java.sql.Connection后,会根据创建org.apache.ibatis.session.SqlSession时传递的参数值,设置java.sql.Connection是否自动提交。该类也提供了对Connection执行commit、rollback的方法,在org.apache.ibatis.session.SqlSession执行commit、rollback方法时,都会传递到这里的方法。
总结
我们使用Jdbc进行事务控制的话,也是调用java.sql.Connection.setAutoCommit设置是否开启事务,调用java.sql.Connection.commit或java.sql.Connection.rollback进行调或回滚。在mybatis代码中,通过org.apache.ibatis.session.SqlSession的创建设置是否支持事务,调用org.apache.ibatis.session.SqlSession.commit、org.apache.ibatis.session.SqlSession.rollback方法会映射到对应Jdbc的方法,这样就足以支持了事务。
————————————————
版权声明:本文为CSDN博主「马彬彬」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/bingospunky/article/details/79283104
spring-tx关键对象
对象包含关系
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原理
首先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());
}
propagation
@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注解的方法,下面分类讨论:
如果B.b的注解为@Transactional(propagation = Propagation.REQUIRES_NEW),则新创建的TransactionStatus的newTransaction属性为true,同时新建一个Connection,begin transaction,在方法执行完成后会commit或者rollback。
如果B.b的注解为@Transactional(propagation = Propagation.REQUIRED),则新创建的TransactionStatus的newTransaction属性为false,不会创建新的Connection,使用之前的事务环境,在方法执行完成后不会进行commit或者rollback,如果发生了异常,给TransactionStatus设置一个状态表示需要回滚,在最外层的方法中会根据这个状态进行回滚。设置这个状态如下:
Code4
org.springframework.jdbc.datasourceDataSourceTransactionManager.doSetRollbackOnly
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
txObject.setRollbackOnly();
}
mybatis配合spring-tx是如何生效的?
万变不离其宗,不论上层添加几个概念,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生效的关键:
1.同一个Connection。
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了。这就对上了。
2.异常类型匹配,默认是RunTimeException。
@Transaction注解默认回滚的是RunTimeException,为了更方便地配合@Transaction注解,Mybatis默认抛出的是RunTimeException,这点是比较符合的。关于异常会在后面section描述。
jdbc proxy or cglib
spring-tx是基于spring aop的,aop对于proxy模式,支持jdbc proxy和cglib。
proxy-target-class=”false”(jdbc proxy)
1.注解只放在interface的方法上,jdbc代理,事务生效。
2.注解只放在实现类的方法上,jdbc代理,事务生效。
proxy-target-class=”true”(cglib)
1.注解只放在interface的方法上,cglib代理,事务不生效。
2.注解只放在实现类的方法上,cglib代理,事务生效。cglib代理,事务生效。
使用时,默认的proxy-target-class=”false”就能满足我们的需求;如果需要使用proxy-target-class=”true”,需要把@Transaction注解加在实现类的方法上,因为注解是不支持继承的。
注意:
如果一个Service类没有接口,又使用事务,需要生成代理,默认使用jdbc proxy。由于这个类没有interface,就会抛出异常。
关于异常
@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。
Using the PlatformTransactionManager
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);
————————————————
版权声明:本文为CSDN博主「马彬彬」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/bingospunky/article/details/79541494