写在最前
这些天看了一些关于Spring事务的文章,发现写的都很浅,基本上说一下事务特性和Spring事务传播行为就完事了,但是实际上事务不仅有这些,事务的来龙去脉没有讲清楚,具体原理也没有说。
现在最新Spring版本已经到了5.2.0,翻了下源码,事务这块核心代码和4.x版本变化不大,所以本文的源码使用了Spring-tx:4.2.5.RELEASE。
什么是事务
事务存在的目的
在日常开发中,我们在数据库中保存数据,在对数据库操作的过程中,为了使数据始终保持“正确”的状态,我们要对访问操作进行一些限制,以保证数据的完整性。
事务就是以可控的方式对数据进行访问的一组操作,为了保证事务执行前后,数据库中数据始终处于“正确”的状态,事务本身有4个限定属性,也就是事务常说的ACID属性。
-
原子性(Atomicity)
原子性指事务中所包含的全部操作是一个不可分割的整体,要不全部提交成功,只要有一个失败,要么就全部失败。
-
一致性(Consistency)
一致性指的是事务中所包含的操作不能违反数据库的一致性检查,数据在事务执行之前处于某个数据的一致性状态,那么在数据执行之后也要保持数据间的一致性状态。
最常用的例子就是银行转账,A给B转账,转账之前和转账之后两个账户的总额是不变的。
-
隔离性(Isolation)
事务的隔离性主要规定了各个事务之间相互影响的程度。隔离性主要面对数据的并发访问,并兼顾影响事务的一致性。当多个事务同时访问同一个数据时,不同的隔离级别决定了各个事务对数据访问的不同行为。一般事务的隔离级别有四种,从弱到强分别是Read Uncommitted,Read Committed,Repeatable Read和Serializable。
-
Read Uncommitted
Read Uncommitted是最低级别的隔离,直接效果就是一个事务可以读取另外一个事务没有提交的更新结果。Read Uncommitted是以较低的隔离来寻求较高的性能,其本身无法回避几下几个问题:
- 脏读(Dirty Read)。如果一个事务对数据进行了更新,但事务还有提交,另一个事务就可以看到该事务没有提交的更新结果。这样造成的问题就是,如果第一个事务发生回滚,那么第二个事务在此之前看到的数据就是一笔脏数据。
- 不可重复读取(Non-Repeatable Read)。如果同一个事务在整个事务过程中对同一笔数据数据进行读取,每次读取结果都不同。如果事务1在事务2的更新造作之前读取一次数据,在事务2的更新造作之后对同一笔数据又读取一次,两次结果是不同的。
- 幻读(Phantom Read)。幻读是指同样一个查询,在整个事务多次执行后,查询到的结果集不一样。如果事务1在事务2的插入或删除之前读取一次数据,在事务2的插入或删除造作之后对同一笔数据又读取一次,两次结果集是不同的。
-
Read Committed
Read Committed是大部分数据库默认的隔离级别, 在该隔离级别下,一个事务的更新操作提交之后,其他事务才可以读到该事务的更新结果。所以Read Committed可以避免脏读问题,但是无法避免不可重复读取和幻读。
-
Repeatable Read
Repeatable Read可以保证在整个事务的过程中,对同一笔数据的读取结果是相同的,不管其他事务是否同时在对同一笔数据进行更新,也不管其他事务对同一笔数据的更新提交与否。Repeatable Read避免了脏读和不可重复读,但是无法避免幻读。
-
Serializable
Serializable是最严格的隔离级别。所有事务操作都必须依次进行,可以避免所有问题,是最安全的隔离级别,但也是性能最差的隔离级别。
-
-
持久性(Durability)
事务的持久性是指,事务一旦提交,就会被持久的记录下来。
Java事务处理
在Java的事务场景中,事务处理操作会随着数据访问技术的不同而各异,我们不是使用专用的事务API来管理事务,而是通过当前数据访问技术提供的基于connection的API来管理事务,比如JDBC时Java平台访问关系型数据库最基础的API(伪代码):
Connection connection = null;
try{
connection.setAutoCommit(false);
connection = dataSource.getConnection;
...
connection.commit();
}finally{
...
connection.close();
}
再比如我们使用Hibernate进行数据访问,就要使用Hibernate的Session进行数据访问时的事务管理。(伪代码)
Session session = null;
Transaction transaction = null;
try{
sessionFactory = sessionFactory.openSession();
transaction = session.beginTransaction();
transaction.commit();
}finally{
session.close():
}
由于不同的数据访问技术提供了不同的数据访问API来管理事务,这样会产生一些问题:
- 各种数据访问方式只提供了简单的事务API,但没有更高层次的抽象来帮助我们隔离事务与数据访问的过紧耦合。
- 事务处理过程中的异常应该都是不可恢复的,所以应该抛出
unchecked exception
,并且有一个统一的父类便于客户端处理。但现在没有一个统一的事务相关异常体系,我们需要捕捉特定API异常并处理。 - 对于开发人员,所谓事务的管理,最多也就是界定一下事务的边界,规定什么时候开始,什么时候结束。但没有一个统一的方式对事务进行管理。
Spring事务架构
Spring的事务框架理念的基本原则是:让事务管理的关注点与数据访问关注点相分离。
- 当业务层使用的抽象API进行事务界定的时候,不需要关心操作的事务资源是什么,对不同的事务资源管理由相应的框架核心来负责。
- 对数据访问层对数据进行访问时,只需要使用数据访问API进行数据访问,不需要关心当前事务资源如何参与事务或者如何参与事务,同样由框架负责。
以上两点清晰的分离出来之后,我们只要关心通过抽象后的事务管理API对当前事务进行界定。不管数据访问方式如何切换,事务管理的方式都不需要改变。
我们一般将事务管理放在Service层,而不是DAO层,是为了提高数据访问逻辑的可重用性,也可以再Service层根据相应逻辑决定提交事务或回滚,一般在业务方法中会调用多个数据访问的方法。
Spring事务接口
Spring事务抽象一共有3个主要接口,他们都在org.springframework.transaction
包下。 PlatformTransactionManager
是Spring事务框架的核心接口,它为应用程序提供事务边界的统一方式,它根据TransactionDefinition
的定义来开启相关事务。TransactionDefinition
负责定义事务相关属性,包括隔离级别、传播行为等。事务开启期间的事务状态由TransactionStatus
负责,也可以通过TransactionStatus
对事务进行有限的控制。下面对三个接口分别做介绍之前,先试着实现自定义的PlatformTransactionManager。
自定义PlatformTransactionManager
我们一般将事务管理放在Service层,而不是DAO层,是为了提高数据访问逻辑的可重用性,也可以再Service层根据相应逻辑决定提交事务或回滚,一般在业务方法中会调用多个数据访问的方法。以java.sql.Connection为例,在一个service方法中,不管调用几个Dao层方法,使用的都应该是同一个Connection,我们可以采用传递connection的方式。
public void serviceMethod(){
Object txObject = transactionManager.beginTransaction();
Connection conn = (Connection)txObject;
dao1.doDataAccess(conn);
dao2.doDataAccess(conn);
transactionManager.commit(txObject);
}
但这样有一个问题,还是没有摆脱Connection,如果要换成Hibernate,就需要转成Session。传递Connection的理念是对的,更好的做法是在事务开始之前获取一个Connection,然后将这个Connection绑定到当前线程。在需要使用Connection的时候,从当前线程获取,事务提交后,解除和当前线程的绑定。
public class TransactionResourceManager {
private static ThreadLocal resources = new ThreadLocal();
public static Object getResources() {
return resources.get();
}
public static void bindResources(Object resource){
resources.set(resource);
}
public static Object unbindResource(){
Object res = getResources();
resources.set(null);
return res;
}
}
public class JdbcTransactionManager implements PlatformTransactionManager {
private DataSource dataSource;
public JdbcTransactionManager(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
Connection conn;
try{
conn = dataSource.getConnection();
TransactionResourceManager.bindResources(conn);
return new DefaultTransactionStatus(conn,true,true,false,true,null);
}catch (Exception e){
throw new CannotCreateTransactionException("不能创建事务"+ e );
}
}
@Override
public void commit(TransactionStatus status) throws TransactionException {
Connection conn = (Connection)TransactionResourceManager.unbindResource();
try {
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public void rollback(TransactionStatus status) throws TransactionException {
Connection conn = (Connection)TransactionResourceManager.unbindResource();
try {
conn.rollback();
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
这样,在开始和结束时期间,Connection都可以通过TransactionResourceManager获得,Dao层也可以通过这种方式获取Connection进行数据访问了。
TransactionDefinition
事务属性
TransactionDefinition主要定义了有哪些事务属性可以指定,包括
事务的隔离级别
在TransactionDefinition
定义了5个常量用于表示事务的隔离级别
//数据库默认的隔离级别
int ISOLATION_DEFAULT = -1;
//以下4中分别对应数据库中隔离级别
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
事务的传播行为
事务的传播行为表示事务处理过程中,所跨越的业务对象将以什么样的行为参与事务。在TransactionDefinition
中定义了7种事务传播行为
/*
默认的事务传播行为,如果存在当前事务,则加入,如果不存在事务,则创建一个新事务。
*/
int PROPAGATION_REQUIRED = 0;
/*
如果当前存在一个事务,则加入当前事务,如果不存在,则直接执行。
对于一些查询方法来说,PROPAGATION_SUPPORTS比较适合,如果当前方法被其他方法调用,而其他方法启动了一个事务,此行为可以保证当前方法加入当前事务,并洞察当前事务对数据资源所做的更新。其他传播方式则看不到更新,因为当前事务没有提交。
*/
int PROPAGATION_SUPPORTS = 1;
/*
PROPAGATION_MANDATORY强制要求当前存在一个事务,如果不存在,则抛出异常。
*/
int PROPAGATION_MANDATORY = 2;
/*
不管当前是否存在事务, 都会创建一个新的事务。如果当前存在事务,会将当前事务挂起。如果某个业务对象不想影响外层事务,可以选择PROPAGATION_REQUIRES_NEW。
*/
int PROPAGATION_REQUIRES_NEW = 3;
/*
不支持当前事务,而是在没有事务的情况下执行。如果当前存在事务,则将当前事务挂起,但要看PlatformTransactionManager的实现类是否支持事务的挂起。
*/
int PROPAGATION_NOT_SUPPORTED = 4;
/*
永远不支持事务,如果存在事务,则抛出异常。
*/
int PROPAGATION_NEVER = 5;
/*
如果存在事务,则在当前事务的一个嵌套事务中执行。与PROPAGATION_REQUIRES_NEW类似,但与PROPAGATION_REQUIRES_NEW创建的事务与原有事务为同一档次,在新事务运行时原有事务被挂起;PROPAGATION_NESTED创建的事务寄生在外层事务,地位比外层事务低,嵌套事务执行时外层事务也处于活跃状态。
*/
int PROPAGATION_NESTED = 6;
事务的超时时间
//默认为-1,不超时
int TIMEOUT_DEFAULT = -1;
int getTimeout();
事务的只读属性
boolean isReadOnly();
事务属性实现类
TransactionDefinition 相关实现类
按照编程式事务场景和声明式事务场景划分为两个分支。DefaultTransactionDefinition
是 TransactionDefinition
的默认实现类,它提供了各事务属性的默认值,并且通过它的setter方法,我们可以更改这些默认值。
private int propagationBehavior = PROPAGATION_REQUIRED;
private int isolationLevel = ISOLATION_DEFAULT;
private int timeout = TIMEOUT_DEFAULT;
private boolean readOnly = false;
TransactionTemplate
是Spring提供的编程式事务管理的模板类,它直接继承了DefaultTransactionDefinition
。所以我们可以通过使用TransactionTemplate
本身 提供事务控制属性。
public class TransactionTemplate extends DefaultTransactionDefinition
implements TransactionOperations, InitializingBean {
...
public PlatformTransactionManager getTransactionManager() {
return this.transactionManager;
}
...
}
TransactionTemplate
是继承自TransactionDefinition
的接口定义,主要面向使用Spring AOP进行声明式事务的场合。它在TransactionDefinition
的基础上定义了rollbackOn接口方法,这样就可以指定抛出哪些异常可以回滚事务。
public interface TransactionAttribute extends TransactionDefinition {
String getQualifier();
boolean rollbackOn(Throwable ex);
}
DefaultTransactionAttribute 相关实现类
DefaultTransactionAttribute 实现了TransactionAttribute
接口,有两个直接子类Ejb3TransactionAttribute
,RuleBasedTransactionAttribute
。
RuleBasedTransactionAttribute
允许我们同时指定多个回滚规则,这些规则以RollbackRuleAttribute
或者NoRollbackRuleAttribute
的List形式存在。
public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable {
private List<RollbackRuleAttribute> rollbackRules;
/**
* Set the list of {@code RollbackRuleAttribute} objects
* (and/or {@code NoRollbackRuleAttribute} objects) to apply.
* @see RollbackRuleAttribute
* @see NoRollbackRuleAttribute
*/
public void setRollbackRules(List<RollbackRuleAttribute> rollbackRules) {
this.rollbackRules = rollbackRules;
}
public boolean rollbackOn(Throwable ex) {
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
if (this.rollbackRules != null) {
for (RollbackRuleAttribute rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}
// User superclass behavior (rollback on unchecked) if no rule matches.
if (winner == null) {
logger.trace("No relevant rollback rule found: applying default rules");
return super.rollbackOn(ex);
}
return !(winner instanceof NoRollbackRuleAttribute);
}
}
TransactionStatus
TransactionStatus接口定义整个事务处理过程中的事务状态,更多时候,我们在编程式事务中使用。
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
//如果相应的PlatformTransactionManager支持Savepoint,则可以在当前事务中嵌套内联事务
boolean hasSavepoint();
//标记当前事务使其回滚
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();
}
AbstractTransactionStatus
AbstractTransactionStatus
是TransactionStatus
的抽象实现,为继承的子类提供一些共同抽象部分代码。DefaultTransactionStatus
是Spring框架中TransactionStatus
的主要实现类,各个TransactionManager
的实现,大都借助于DefaultTransactionStatus
来记载事务信息。
PlatformTransactionManager
PlatformTransactionManager
是事务抽象框架的核心组件,对事务界定进行统一抽象。接口方法如下:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager的实现类
整个PlatformTransactionManager
抽象体系基于Strategy模式,由PlatformTransactionManager
,具体的界定策略实现由具体的实现类负责,实现可以划分为面向局部事务和面向全局事务两个分支。
面向局部事务实现类
Spring为各种数据访问技术提供了现成的实现支持,如提供了实现类。根据这些实现类,在使用Spring框架进行事务管理的时候,只需要选择对应的PlatformTransactionManager
的实现类即可。
技术访问技术 |
PlatformTransactionManager 的实现类 |
---|---|
JDBC/Mybatis | DataSourceTransactionManager |
Hibernate | HibernateTransactionManager |
JPA | JpaTransactionManager |
... | ... |
面向全局事务实现类
待补充
剖析DataSourceTransactionManager
PlatformTransactionManager
的实现类遵循统一的结构和理念,所以选择DataSourceTransactionManager
作为切入点。但是有几个概念需要提前引入,因为后文会用到
-
transaction objet
transaction objet承载了当前事务的必要信息,PlatformTransactionManager实现类可以根据transaction objet所提供的信息来决定如何处理当前的事务。transaction objet的概念类似于JTA(Java Transaction API)规范中的
javax.transaction.Transaction
。 -
TransactionSynchronization
TransactionSynchronization是可以注册到事务处理过程的回调接口。它就像事务处理的事件监听器,当事务处理的某些规定时点发生时,会调用TransactionSynchronization上的一些方法来执行相应的回调,如在事务完成后清理相应的系统资源等。transaction objet的概念类似于JTA(Java Transaction API)规范中的
javax.transaction.Synchronization
。 -
TransactionSynchronizationManager
通过TransactionSynchronizationManager来管理TransactionSynchronization、当前事务状态以及具体的事务资源。比如java.sql.Connection或者hibernateSession需要绑定到线程,TransactionSynchronizationManager就是最终的目的地,更多的是对TransactionSynchronization的管理。
下图是PlatformTransactionManager的继承层次,从各个实现类的继承层次来看,Spring事务框架更多的依赖于模板方法模式。PlatformTransactionManager
主要关注事务处理过程中的主体逻辑,具体和事务资源相关的处理由子类来实现。
主要体现在以下几个方法中:
1. getTransaction
获取TransactionStatus
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
/*
获取Object transaction,以判断是否存在当前事务。
Object接受是因为此时不同的TransactionManager会返回不同的TransactionObject,
比如DataSourceTransactionManager返回DataSourceTransactionObject。
doGetTransaction()是模板方式,公开给子类去实现。
*/
Object transaction = doGetTransaction();
//检查TransactionDefinition,如果为空,则创建一个默认的DefaultTransactionDefinition
if (definition == null) {
// Use defaults if no transaction definition given.
definition = new DefaultTransactionDefinition();
}
//判断当前是否存在事务,默认返回false,需要子类重写此方法
if (isExistingTransaction(transaction)) {
//如果存在事务,由此方法检查传播行为做出不同的处理并返回TransactionStatus
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// 检查默认事务过期时间
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// 如果不存在事务,抛出异常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
//如果是以下几种传播方式,开启新事务
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
//如果由注册的Synchronization,需要先将这些如果由注册的Synchronization挂起
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException ex) {
resume(null, suspendedResources);
throw ex;
}
catch (Error err) {
resume(null, suspendedResources);
throw err;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + definition);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
2. rollback
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); //见下面方法
}
private void processRollback(DefaultTransactionStatus status) {
try {
try {
//触发事件
triggerBeforeCompletion(status);
//如果是嵌套事务,则通过status释放SavePoint
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
//如果是一个新事务,则调用子类的doRollback进行真正的回滚,doRollback是个抽象方法
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
//如果当前存在事务
else if (status.hasTransaction()) {
//rollbackOnly状态被设置,则带哦用子类的doSetRollbackOnly
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
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");
}
}
catch (RuntimeException ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
catch (Error err) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw err;
}
//触发事件
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
}
finally {
/*
清理事务资源等一系列操作,包括:
1.设置DefaultTransactionStatus为完成状态
2.清理与事务相关的Synchronization
3.释放事务资源,对于DataSourceTransactionManager来说,关闭数据库连接,接触对应数据的绑定。
4.如果之前由挂起的事务,则恢复挂起的事务
*/
cleanupAfterCompletion(status);
}
}
3. commit
提交事务
public final void commit(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;
//如果设置了只读,则进行回滚,否则进行正常提交操作
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus);
return;
}
//提前检测全局rollbackOnly标志,如果满足这两个条件则抛出异常
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus);
// Throw UnexpectedRollbackException only at outermost transaction boundary
// or if explicitly asked to.
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
return;
}
processCommit(defStatus);
}
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
boolean globalRollbackOnly = false;
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
globalRollbackOnly = status.isGlobalRollbackOnly();
}
//如果status持有savePoint,则释放它。实际上为处理嵌套事务的提交
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
status.releaseHeldSavepoint();
}
/*
如果为一个新事物,调用子类实现。对于DataSourceTransactionManager来说,
事务的提交由connection来负责,所以会调用connection.commit();
*/
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
doCommit(status);
}
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
if (globalRollbackOnly) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// 如果rollbackOnCommitFailure设置为true,则需要在抛出异常时进行事务回滚。默认为false
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
catch (Error err) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, err);
throw err;
}
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
/*
做最后的清理工作,参照rolleback
*/
cleanupAfterCompletion(status);
}
}
还有两个模板方法没有列出来,分别是suspend()
和resume()
。源码注释写的很清楚,也很好理解,这里就不再贴出源码了。
使用Spring进行事务管理
编程式事务管理
使用编程式事务管理由两种方式,分别是直接PlatformTransactionManager
或者使用TransactionTemplate
。一般来说,Spring团队推荐使用TransactionTemplate
进行编程式事务管理。
PlatformTransactionManager
只要为TransactionManager提供合适的实现,然后结合TransactionDefinition开启事务,最后根据TransactionStatus来回滚或提交事务,就可以完成对当前对象整个事务管理。
import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.annotation.Resource;
import javax.sql.DataSource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring/applicationContext.xml" })
public class TransactionTest {
@Resource
private PlatformTransactionManager txManager;
@Resource
private DataSource dataSource;
private static JdbcTemplate jdbcTemplate;
Logger logger= Logger.getLogger(TransactionTest.class);
private static final String INSERT_SQL = "insert into student(name) values (?)";
private static final String COUNT_SQL = "select count(*) from student";
@Test
public void testdelivery(){
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
//事务的隔离级别
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
//事务的传播行为
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//设置超时
def.setTimeout(20);
TransactionStatus status = txManager.getTransaction(def);
jdbcTemplate = new JdbcTemplate(dataSource);
int i = jdbcTemplate.queryForObject(COUNT_SQL,Integer.class);
System.out.println("表中记录总数:"+i);
try{
jdbcTemplate.update(INSERT_SQL, "1");
txManager.commit(status); //提交status中绑定
}catch (RuntimeException e){
txManager.rollback(status); //事务完成
throw e; //此处需要抛出异常,否则好像什么都没发生一样
}
i = jdbcTemplate.queryForObject(COUNT_SQL,Integer.class);
System.out.println("表中记录总数:" + i);
}
}
虽然PlatformTransactionManager 屏蔽了不同的事务管理API差异,但是缺点还是很明显的:需要自己对异常进行处理,在每个需要事务管理的地方,都会出现大量的重复代码。所以就有了TransactionTemplate
这种更方便的编程式事务。
TransactionTemplate
TransactionTemplate
对与PlatformTransactionManager
相关的事务界定操作以及相关的异常处理做了模板化封装,开发人员更多的关注于通过相应的Callback接口提供具体的事务界定内容即可。Spring针对TransactionTemplate
提供了两个Callback接口:TransactionCallback
和TransactionCallbackWithoutResult
,二者的唯一区别就是是否需要返回结果集。
使用TransactionTemplate
进行事务管理的代码,看起来要比直接使用PlatformTransactionManager
容易的多。
@Autowired
TransactionTemplate txTemplate; //需要在xml文件中配置TransactionTeplate,这时才可以注入
public void xxxService{
Object result1 = txTemplate.execute(new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus status) {
//事务操作
return new Object();
}
});
Object result2 = txTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
//事务操作
}
});
}
TransactionTemplate
会捕捉TransactionCallback
或者TransactionCallbackWithoutResult
中抛出的unchecked Exception并回滚事务,然后将异常抛给上层处理。
如果事务没有任何问题,TransactionTemplate
会为我们提交事务,唯一需要我们干预的就是某些情况下的回滚了。如果在操作过程中需要让事务回滚而不是最终提交,一般有两种处理方式:
-
事务抛出了unchecked exception,TransactionTemplate会自动回滚,如果为checked Exception,则需要在callback内部捕获,然后转义为unchecked exception抛出。
Object result2 = txTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { try{ //事务操作 }catch(CheckedException e){ throw new RuntimeException(e); } } });
-
使用Callback接口公开的
TransactionStatus
将事务标记为 rollBackOnly。在TransactionTemplate最终提交事务时,检测到rollBackOnly被设置,则将提交事务更改为回滚事务。Object result2 = txTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { boolean needRoleBack = false; //事务操作 if(needRoleBack) status.setRollbackOnly(); } });
对于事务中可能抛出checked Exception,既想回滚,又想以unchecked exception方式向上传播,可以在捕捉代码中设置 status.setRollbackOnly();
Object result2 = txTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try{
//事务操作
}catch(CheckedException e){
//日志记录不能忽略,事务进行回滚,但是日志中如果没有任何异常信息。
logger.warn("transaction is rolled back!" + e);
status.setRollbackOnly();
}
}
});
上面都是伪代码,下面代码贴上可运行的代码
@Test
public void testTransactionTemplate(){
jdbcTemplate = new JdbcTemplate(dataSource);
int i = jdbcTemplate.queryForObject(COUNT_SQL,Integer.class);
System.out.println("表中记录总数:"+i);
//构造函数初始化TransactionTemplate
TransactionTemplate template = new TransactionTemplate(txManager);
template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
template.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.update(INSERT_SQL, "1");
}
});
i = jdbcTemplate.queryForObject(COUNT_SQL,Integer.class);
System.out.println("表中记录总数:"+i);
}
声明式事务管理
直接使用编程式事务管理的不足就是,需要将事务管理代码和业务逻辑代码写在一起。而声明式事务管理则可以避免这一点。声明式事务基于Spring AOP,我们只需要关注事务的横切关注点,所以我们完全可以为其提供相应的Advice实现,然后织入到系统中需要该横切逻辑的Joinpoint处。
我们只需要提供一个拦截器,在业务方法开始之前开启一个事务,当方法执行完成或异常退出的时候提交或者回滚事务。
public class PrototypeTransactionInterceptor implements MethodInterceptor {
private PlatformTransactionManager txTransactionManager;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
TransactionDefinition def = getTransactionDefinitionByMethod(method);
TransactionStatus status = txTransactionManager.getTransaction(def);
Object result = null;
try{
result = invocation.proceed();
}catch (Throwable t){
if(needRollbackOn(t)){
txTransactionManager.rollback(status);
}else{
txTransactionManager.commit(status);
}
}
return null;
}
//根据方法获取def
TransactionDefinition getTransactionDefinitionByMethod(Method method){
return null;
}
//是否需要回滚
boolean needRollbackOn(Throwable t){
return false;
}
}
另外,还需要知道业务方法是否需要事务,和当抛出异常时如何处理,是否需要回滚。一般来说,可以写死在拦截器中,更好的办法是将这些配置信息放到XML文件中或Java注解的形式标注。
Spring提供了声明式事务管理的类TransactionInterceptor
来代替上面我们自己定义的拦截器,所以,我们只需要决定使用XML方式还是注解的方式即可。
XML
Spring2.x以后提供基于XML Schema的配置方式,专门为事务管理提供一个单独的命名空间tx用于简化配置,底层使用的还是TransactionInterceptor
。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.example"/>
<context:property-placeholder location="classpath:spring/db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!--连接池中保留的最小连接数。 -->
<property name="minPoolSize">
<value>5</value>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 底层依赖自动代理机制 -->
<aop:config>
<aop:pointcut expression="execution(* com.example.*.service.impl.*.*(..))" id="pointcut"/>
<!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
<!-- 通过tx指定的事务信息,需要Spring AOP的支持才能织入具体业务对象 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
name:需要事务管理的方法名称,可以使用*通配符
propagation:事务传播行为
isolution:事务的隔离度
timeout:事务超时时间
read-only:指定事务是否只读
rollback-for:指定触发事务回滚的异常类型
no-rollback-for:指定不触发事务回滚的异常类型
-->
<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="-1" read-only="false" no-rollback-for="" rollback-for=""/>
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
</beans>
Java注解
Spring定义了Transactional
用于标注业务方法对应的事务,可以直接在注解标注到业务方法或业务方法所在的对象定义上。通过Transactional
,可以指定与<tx:advice/>
几乎相同的信息。
下面是Transactional的源码:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
Java注解具体使用如下,一般推荐在实现类上使用@Transactional,是因为CGLIB代理会遇到问题,因为它是基于类的代理,而不是基于接口。
@Service
@Transactional
public class UserService {
@Transactional(propagation = Propagation.REQUIRED,readOnly = false,
timeout = 20,isolation = Isolation.DEFAULT)
public void printUser(){
System.out.println("user print");
}
}
只使用@Transactional标注并不会为方法带来事务的管理,因为它只是一个标志,需要在执行业务方法的时候,通过反射读取这些信息,并根据这些信息构建事务,这些底层逻辑不需要我们去写,通过在容器中指定下面这句配置即可,所有的工作交给Spring IOC容器搞定。
<tx:annotation-driven transaction-manager="transactionManager"/>
写在最后
本来没想写这么多的,最开始只想解决一个开发过程中调遇到的事务问题:在同一个业务类中,一个方法中调用另一个方法,没有开启新事务。
现在就很清晰了,原因是业务方法中的事务是IOC容器在方法执行前后去织入的,手动调用相当于没有走IOC容器,解决办法有多种,可以将业务方法写在其他业务类中,然后通过注入的方式调用;还可以从IOC容器中获取当前业务类对象,然后调用其方法,但是这种方式需要在配置中暴漏代理对象。
<aop:aspectj-autoproxy expose-proxy="true"/>
public void printUser(){
System.out.println("user print");
((UserService)AopContext.currentProxy()).c();
}
public void c(){}
还可以通过注解去设置暴漏代理对象,翻了下源码,在4.2.5中没有该配置,在5.x版本中可以。
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {
}