事务是与连接对象紧密相关的,事务属性用来控制事务流转。
Spring事务的传播属性有以下几种:
- Propagation.REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,则加入到这个事务中。默认属性,也是最常使用。
- Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与Propagation.REQUIRED类似的操作。
- Propagation.REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
- Propagation.SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
- Propagation.MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
- Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
前面三种使用居多。
本篇看REQUIRED传播属性。
通过案例代码进入本篇内容,有如下事务方法:
@Transactional
@Override
public void transation(ConsultConfigArea area, ZgGoods zgGoods) {
areaService.addArea(area);
goodsService.addGoods(zgGoods);
}
transation()方法事务执行的大致流程就是下面,为了便于理解,用伪代码来表示:
// transation()方法执行
// 开启事务
createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
try {
// 执行被代理方法
invocation.proceedWithInvocation();
// 相当于执行下面方法:
@Transactional
public void transation(ConsultConfigArea area, ZgGoods zgGoods) throws JamesException {
areaService.addArea(area);
goodsService.addGoods(zgGoods);
}
} catch (Exception e) {
// 事务回滚
completeTransactionAfterThrowing(txInfo, ex);
}
// 事务提交
commitTransactionAfterReturning(txInfo);
// transation()方法结束
调用的业务方法同样也有@Transactional注解:
@Transactional
@Override
public int addArea(ConsultConfigArea area) {
int i = commonMapper.addArea(area);
return i;
}
@Transactional
@Override
public void addGoods(ZgGoods zgGoods) throws JamesException {
commonMapper.addGood(zgGoods);
}
那么,伪代码就会变成这样:
// transation()方法执行
// 开启事务
createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
try {
// 执行被代理方法
invocation.proceedWithInvocation();
// 相当于执行下面方法:
// 1.areaService.addArea(area)开始执行
// 开启事务
createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
try {
// 执行被代理方法
invocation.proceedWithInvocation();
// 相当于执行下面方法:
commonMapper.addArea(area);
} catch (Exception e) {
// 事务回滚
completeTransactionAfterThrowing(txInfo, ex);
throw new Exception(e);
}
// 事务提交
commitTransactionAfterReturning(txInfo);
// areaService.addArea(area)执行结束
————————————————————————————————————————————————————————————
// 2.goodsService.addGoods(zgGoods)开始执行
// 开启事务
createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
try {
// 执行被代理方法
invocation.proceedWithInvocation();
// 相当于执行下面方法:
commonMapper.addGood(zgGoods);
} catch (Exception e) {
// 事务回滚
completeTransactionAfterThrowing(txInfo, ex);
throw new Exception(e);
}
// 事务提交
commitTransactionAfterReturning(txInfo);
// goodsService.addGoods(zgGoods)执行结束
} catch (Exception e) {
// 事务回滚
completeTransactionAfterThrowing(txInfo, ex);
}
// 事务提交
commitTransactionAfterReturning(txInfo);
// transation()方法结束
如果是PROPAGATION_REQUIRED属性,示例代理中的三个方法使用的是同一个事务。
@Autowired
private DataSource dataSource;
ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
这样,就可以拿到连接对象。上面代码执行得到的连接对象为同一个:
分析一下执行过程,三个方法transation(ConsultConfigArea area, ZgGoods zgGoods)、 areaService.addArea(area)、goodsService.addGoods(zgGoods)是通过不同的代理对象去执行,那么TransactionInterceptor#invoke()会执行三次,在AbstractPlatformTransactionManager#getTransaction()方法:
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
// 得到一个DataSourcetranctionObject类(就是事务对象)****
Object transaction = doGetTransaction();
// 判断之前是否已存在事务对象(第一次进来为空)
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
// 如果不是第一次进来, 则走这里,表示之前已开启事务,则为现有事务创建TransactionStatus。
return handleExistingTransaction(def, transaction, debugEnabled);
}
----省略无关代码----
获取连接对象doGetTransaction():
所属类org.springframework.jdbc.datasource.DataSourceTransactionManager
protected Object doGetTransaction() {
// 创建一个事务对象
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
// 是否创建回滚点,默认为true
txObject.setSavepointAllowed(isNestedTransactionAllowed());
// 根据DataSource对象去ThreadLocal中拿连接对象ConnectionHolder,第一次进来拿的时候是空的
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
Spring获取连接对象就是从ThreadLocal中获取的,第一次进来为空,那么就会进入后面的逻辑, 在startTransaction()中的doBegin()方法中创建连接对象:
所属类:org.springframework.jdbc.datasource.DataSourceTransactionManager
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
// 检查事务中是否有连接对象(DataSource)
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// 没有的话通过数据源管理器拿DataSource对象,再从DataSource中获取连接对象
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();
// 根据@Transactional注解中的配置的隔离级别,设置Connection的readonly与隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
// 关闭自动提交,提交由Spring控制
con.setAutoCommit(false);
}
// 判断事务为只读事务
prepareTransactionalConnection(con, definition);
// 设置事务状态(是否为活跃的)为true
txObject.getConnectionHolder().setTransactionActive(true);
// 设置事务(数据库连接)超时时间
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the connection holder to the thread.
// 把连接对象和当前线程进行绑定,把数据源和连接的绑定关系设置到ThreadLocal<Map<数据源,连接对象>>,为threadId和Map的映射关系
// 后面就可以通过threadId拿到Map,进而通过相同的数据源对象拿到数据库连接
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
把连接对象包装成ConnectionHolder,赋值给DataSourceTransactionObject事务对象,然后关闭自动提交,最后数据源对象和连接对象建立绑定关系,放到ThreadLocal中:
所属类:org.springframework.transaction.support.TransactionSynchronizationManager
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
Thread.currentThread().getName() + "]");
}
}
这就是绑定过程,resources就是ThreadLocal:
// key为DataSource对象,value为ConnectionHolder对象
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
上面实例代码会执行三次事务的方法,总结一下:
第一次:执行外层transation() 方法:
AbstractPlatformTransactionManager.getTransaction()——>
DataSourceTransactionManager.doGetTransaction()返回null(ThreadLocal中没有)——>
AbstractPlatformTransactionManager.startTransaction()中的doBegin()方法从DataSource获取连接对象,包装成ConnectionHolder(newTransaction属性为true),放到事务对象DataSourceTransactionObject中,建立绑定关系,放到ThreadLocal中;
第二次:执行addArea()方法:
AbstractPlatformTransactionManager.getTransaction()——>
DataSourceTransactionManager.doGetTransaction()通过ThreadId拿到连接对象,但newTransaction属性为false(因为是之前的连接对象,不是新建的)——>
存在事务对象进入AbstractPlatformTransactionManager.handleExistingTransaction()执行最后一行,创建DefaultTransactionStatus事务状态对象(newTransaction属性为false)——>
执行被代理方法addArea()——>
TransactionAspectSupport.commitTransactionAfterReturning()事务提交——>
AbstractPlatformTransactionManager.processCommit()通过status.isNewTransaction()判断是否为新事务,是则提交,此时为false,所以不提交。
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
----省略无关代码----
try {
// 如果是新事务(前面提交的newTransaction属性),则提交
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
// 事务提交
doCommit(status);
}
----省略无关代码----
第三次:执行addGoods()方法:
流程和第二次进来一样,也没有提交事务;
事务提交是最外层的transation() 方法执行完被代理方法之后进行,此时事务状态TransactionStatus的newTransaction属性为true:
接着进入AbstractPlatformTransactionManager.processCommit(),进行事务提交:
所属类:org.springframework.jdbc.datasource.DataSourceTransactionManager
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
// 调用JDK的事务提交方法
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
异常情况处理:
1. 前面执行的addArea()方法出现异常:
@Transactional()
public int addArea(ConsultConfigArea area) {
ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
int i = commonMapper.addArea(area);
if (true) throw new RuntimeException("ssf");
return i;
}
那么就会被TransactionAspectSupport.invokeWithinTransaction()的try/catch捕获,并将异常向上抛出;后面的addGoods()方法就不再执行,抛出的异常被最外层的transation() 方法捕获,执行回滚操作,所以数据库的两张表都没有数据。
2. 后面执行的addGoods()方法出现异常:
@Transactional
public void addGoods(ZgGoods zgGoods) {
ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
System.out.println(resource.getConnection());
int i = commonMapper.addGood(zgGoods);
if (true) throw new RuntimeException("ssf");
}
虽然前面的addArea()执行成功,但事务状态newTransaction属性为false,不会提交事务;addGoods()出现异常,被外层的transation() 方法捕获,执行回滚操作,所以数据库的两张表也没有数据。
3. 内部方法addGoods()出现异常,外层transation() try/catch异常,未向上抛:
@Transactional
@Override
public void transation(ConsultConfigArea area, ZgGoods zgGoods) throws JamesException {
try {
areaService.addArea(area);
goodsService.addGoods(zgGoods);
} catch (Exception e) {
e.printStackTrace();
}
}
这种情况,addArea()执行成功,但事务状态newTransaction属性为false,不会提交事务;addGoods()出现异常,不会执行commitTransactionAfterReturning()事务提交;
异常被自己的try/catch捕获,但未做任何处理;
继续执行外层的transation() 方法的commitTransactionAfterReturning()事务提交,进入commit()方法,在这里执行了回滚操作:
所属类:org.springframework.transaction.support.AbstractPlatformTransactionManager
commit()重执行回滚操作???数据库没有插入数据!和我们的预期不一样!什么鬼?
因为addGoods()方法出现异常,会执行completeTransactionAfterThrowing()回滚操作:
所属类:org.springframework.transaction.interceptor.TransactionAspectSupport
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
// transactionAttribute的实现类为RuledBasedTransactionAttribute,父类为DefaultTransactionAttribute
// 把异常传进来,执行RuledBasedTransactionAttribute.rollbackOn()方法
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
// 执行回滚逻辑
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
进入rollback(TransactionStatus status) 方法:
所属类:org.springframework.transaction.support.AbstractPlatformTransactionManager
public final void rollback(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
// 回滚方法
processRollback(defStatus, false);
}
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
// 是否存在回滚点,如mysql的savepoint
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
// 回滚到上一个savepoint位置
status.rollbackToHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
// 如果当前执行的方法是新开了一个事务,则直接回滚
doRollback(status);
}
else {
// Participating in larger transaction
// 如果当前执行的方法,共用了之前已存在的事务,而当前方法抛出异常,则判断整个事务是否要回滚
if (status.hasTransaction()) {
// 如果一个事务有两个方法,第二个抛异常了,则第二个方法执行失败进行回滚
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
// 直接将rollbackOnly设置到ConnectionHolder中取,表示整个事务都要回滚
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status);
}
}
在doSetRollbackOnly(DefaultTransactionStatus status)设置事务对象的setRollbackOnly()属性为true。
这里需要注意的是, 如果当前执行的方法,已存在事务,而当前方法抛出异常,则判断整个事务是否要回滚,如果一个事务有两个方法,第二个抛异常了,则第二个方法执行失败进行回滚,会将rollbackOnly设置到ConnectionHolder中,表示整个事务都要回滚,看doSetRollbackOnly(status)方法:
所属类:org.springframework.jdbc.datasource.DataSourceTransactionManager
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
"] rollback-only");
}
txObject.setRollbackOnly();
}
进入本类的setRollbackOnly()方法:
public void setRollbackOnly() {
getConnectionHolder().setRollbackOnly();
}
拿到连接对象ConnectionHolder,并设置rollbackOnly属性为true,所以虽然是事务提交,但是前面将TransactionStatus的rollbackOnly属性设置为true,所以在这里进行了回滚操作:
public final void commit(TransactionStatus status) throws TransactionException {
----省略无关代码----
// 判断此事务在此之前是否设置了回滚,并且isGlobalRollbackOnly()为true
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
// 提交
processCommit(defStatus);
}
所以外层的transation() 方法不论是否将异常向上抛,都进行了回滚,导致数据库没数据。
4. 内部方法addGoods()方法try/catch异常,未向上抛:
@Transactional
public void addGoods(ZgGoods zgGoods) {
try {
commonMapper.addGood(zgGoods);
if (true) throw new RuntimeException("ssf");
} catch (Exception e) {
e.printStackTrace();
}
}
此时数据库却有数据。因为虽然有异常,但是却吞掉了,没有抛出,所以外层的transation() 方法未捕捉到异常,执行正常的事务提交了。
总结:内部方法出现异常被吞掉,不会回滚;外部方法异常抛出或吞掉,都会回滚。