对于普通项目不太关注使用Spring
扩展点进行定制时,个人觉得使用Spring
两个最大价值:IoC
容器管理Bean
,另一个就是事务管理。Spring
使用声明式事务方式,对业务代码没有侵入就可以实现事务,如果自己去管理事务的话,将会带来非常大的额外工作量,繁琐且会对业务代码侵入,影响代码质量。所以,如果你去问一些开发者为什么使用Spring
时,可能他会给你一个他最直观感受就是简化事务管理。Spring
事务管理就是一个借助AOP
实现的一个典型的且具有很大实用价值的经典案例,今天,我们就来分析下Spring
中的事务管理。
Spring
中对事务这块抽象出三个核心接口:TransactionDefinition
、PlatformTransactionManager
和TransactionStatus
。下面就来逐一分析下这三个接口的作用。
TransactionDefinition
接口定义如下,该接口很好理解,就是存放的事务的配置信息,如:事务隔离级别、事务传播特性、超时、只读事务等等。
public interface TransactionDefinition {
//事务的7个传播行为
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
//事务的5个隔离级别
int ISOLATION_DEFAULT = -1;//默认的隔离级别,表示使用底层数据库的默认隔离级别
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;
//事务超时时间 事务在超时前能运行多久,超过时间后,事务被回滚
int TIMEOUT_DEFAULT = -1;
//返回传播行为
int getPropagationBehavior();
//返回隔离级别
int getIsolationLevel();
//返回超时时间
int getTimeout();
//是否只读事务 只读事务不修改任何数据,事务管理器可以针对只读事务应用一些优化措施,提高运行性能
boolean isReadOnly();
//返回事务名称
String getName();
}
PlatformTransactionManager
事务管理器,其定义见下,只提供了与事务相关的三个操作:事务创建、事务提交、事务回滚。
public interface PlatformTransactionManager {
/**
* 返回当前活动的事务或创建一个新的事务,参数definition描述了事务的属性,比如传播行为、隔离级别、超时等
*/
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
/**
* 根据给定事务的状态提交给定事务
*/
void commit(TransactionStatus status) throws TransactionException;
/**
* 执行给定事务的回滚
*/
void rollback(TransactionStatus status) throws TransactionException;
}
Spring
抽象出PlatformTransactionManager
事务管理器,主要是因为事务操作相关需要依赖具体的ORM
框架实现,Spring
可以和很多ORM
框架进行集成,所以就存在很多其实现类。这里同样采用了Spring
常规套路:使用模板设计模式,定义一个抽象类AbstractPlatformTransactionManager
实现了事务处理的常规流程,涉及到具体底层实现定义出抽象方法供具体实现类扩展。常见的实现类见下:
大致描述如下:
JmsTransactionManager
:实现ActiveMQ
中间件事务管理;
HibernateTransactionManager
:和Hibernate
集成使用的事务管理器;
DataSourceTransactionManager
:常规的JDBC
方式进行事务管理器,比如和Mybatis
集成就是使用这个事务管理器;
JpaTransactionManager
:Jpa
事务管理器;
JtaTransactionManager
:Jta
事务管理器;
PlatformTransactionManager
主要用于管理所有的事务,而TransactionStatus
则代表的是具体某一个事务运行状态,通过它就可以对具体某个事务进行提交、回滚操作。
public interface TransactionStatus extends SavepointManager, Flushable {
//当前事务状态是否是新事务
boolean isNewTransaction();
//当前事务是否有保存点
boolean hasSavepoint();
//设置当前事务应该回滚,如果设置这个,则commit不起作用
void setRollbackOnly();
//当前事务是否应该回滚
boolean isRollbackOnly();
//用于刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话,可能对如JDBC类型的事务无任何影响
@Override
void flush();
//当前事务是否已经完成
boolean isCompleted();
}
该接口继承于
SavepointManager
接口,SavepointManager
接口基于JDBC 3.0
保存点的分段事务控制能力提供了嵌套事务的机制。
简单梳理下它们三者关系:平台事务管理器真正管理事务对象,其会根据事务定义信息TransactionDefinition
进行事务管理,在管理事务中产生一些状态信息会记录到TransactionStatus
中。
平时开发中我们更多使用@Transactional
注解方式即可很简单的搞定事务,简化同时也隐藏了Spring
事务实现方式,即声明式事务。今天我们主要是从编程式事务入手,研究下Spring
底层具体实现事务的细节。
Spring
提供了一个轻量级的ORM
工具类:JdbcTemplate
,如下:
String sql = "insert into t_emp (empno, ename, sal) values (9000, 'Scott02', 1111)";
jdbcTemplate.update(sql);
默认是不能进行手工事务管理的,即jdbcTemplate
执行完成后会自动提交,如下,代码2处抛出异常,但是代码1已经自动提交了,不受影响,代码3则无法执行。
jdbcTemplate.update(sql1); //1
int i=1/0; //2
jdbcTemplate.update(sql2); //3
如果我们想让代码1和代码3放到同一个事务中怎么做:
1、首先,创建一个事务管理器,因为JdbcTemplate
直接基于JDBC
,使用DataSourceTransactionManager
类型即可:
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
return dataSourceTransactionManager;
}
2、测试用例
@Test
public void testTransaction() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
//1.开启一个事务
TransactionStatus status = transactionManager.getTransaction(def);
try {
String sql1 = "insert into t_emp (empno, ename, sal) values (8000, 'Scott02', 8888)";
String sql2 = "insert into t_emp (empno, ename, sal) values (9000, 'Scott02', 9999)";
//2.执行sql1
jdbcTemplate.update(sql1);
int i = 1 / 0;
//3.执行sql2
jdbcTemplate.update(sql2);
//4.正常时进行事务提交
transactionManager.commit(status);
}catch (Exception e){
//5.出现异常后进行事务回滚
transactionManager.rollback(status);
e.printStackTrace();
}
}
把int i = 1 / 0
这句注释掉,执行后数据库表中正常插入两条记录,然后把int i = 1 / 0
,执行后数据库表中一条记录都没有新增,说明事务起作用了。
Spring提供了一个专门处理事务工具类:TransactionTemplate
。
1、创建TransactionTemplate
实例:
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager){
return new TransactionTemplate(transactionManager);
}
2、编写测试用例:
@Test
public void testTransactionTemplate() {
transactionTemplate.executeWithoutResult( status -> {
try {
String sql1 = "insert into t_emp (empno, ename, sal) values (8000, 'Scott02', 8888)";
String sql2 = "insert into t_emp (empno, ename, sal) values (9000, 'Scott02', 9999)";
//2.执行sql1
jdbcTemplate.update(sql1);
int i = 1 / 0;
//3.执行sql2
jdbcTemplate.update(sql2);
}catch (Exception e){
//5.出现异常后进行事务回滚,注意,如果这里不进行回滚依然提交,sql1会被提交,sql2由于异常导致没有执行到
status.setRollbackOnly();
}
});
}
transactionTemplate
通过接口回调方式,在方法中可以获取到事务运行状态信息TransactionStatus
,然后通过它即可实现提交or回滚。如果需要回滚事务,只需要执行status.setRollbackOnly()
即可,否则就会进行事务提交。
一般使用如下方式就可以让JdbcTemplate
在事务中执行,那这里原理到底是什么呢?下面我们就来逐一分下下。
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
String sql1 = "insert into t_emp (empno, ename, sal) values (8000, 'Scott02', 8888)";
jdbcTemplate.update(sql1);
//事务提交
transactionManager.commit(status);
}catch (Exception e){
//事务回滚
transactionManager.rollback(status);
}
首先,来看下PlatformTransactionManager#getTransaction
这句代码背后做了哪些事情。PlatformTransactionManager#getTransaction
在AbstractPlatformTransactionManager
抽象类中提供通用实现,即采用模板设计模式。
1、doGetTransaction()
:创建事务对象
Object transaction = doGetTransaction();
大致描述:
doGetTransaction()
创建事务对象,涉及到具体的orm
框架,比如DataSourceTransactionManager
这里创建的是DataSourceTransactionObject
。
然后从TransactionSynchronizationManager#resources
中获取到当前线程的ConnectionHolder
,由于可能存在多数据源,所以resources
类型是ThreadLocal
,即每个线程对应的是一个Map
,key就是DataSource
,value
是ConnectionHolder
。
由于当前线程中还没有绑定资源,所以这里获取到的ConnectionHolder
实际上是null
。
2、startTransaction()
:开启事务
startTransaction(def, transaction, debugEnabled, suspendedResources);
主要工作是在startTransaction()
方法中完成,现在我们来分析下这个方法。
1、newTransactionStatus()
:创建出TransactionStatus
实例,相当于将之前创建的事务对象DataSourceTransactionObject
进行了一层包装,将各种类型包装成一个统一的TransactionStatus
类型供调用方使用,如果是DataSourceTransactionManager
管理器,这里实际实现类是DefaultTransactionStatus
:
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
2、doBegin()
:这个方法涉及底层实现,需要依赖具体的orm
实现框架,所以在AbstractPlatformTransactionManager
没有实现,需要具体的事务管理器实现类实现。这里使用的是DataSourceTransactionManager
,其定义如下:
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// 从连接池DataSource中获取一个连接Connection
Connection newCon = obtainDataSource().getConnection();
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
// readOnly、transactionIsolation设置
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
// 将Connection的autoCommit设置成false
con.setAutoCommit(false);
}
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
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 for transaction", ex);
}
}
大致描述:
Connection newCon = obtainDataSource().getConnection()
:从连接池DataSource
获取到一个底层Connection
,然后将其包装成ConnectionHolder
类型,并设置到DataSourceTransactionObject
中;
con.setAutoCommit(false)
:Connection
的autoCommit
设置成false
,这样才能手动管理事务;
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder())
:将配置好的Connection
包装类ConnectionHolder
绑定到上下文线程中:TransactionSynchronizationManager#resources
,这样在其它地方需要使用到数据库连接时就可以从这里获取到配置好的Connection
。
分析到这里,PlatformTransactionManager#getTransaction
大致工作内容基本已经清楚:
利用DataSource
获取到底层连接Connection
;
然后进行一些配置,主要是的是将Connection
的autoCommit
设置成false
,这样才能手动管理事务;
通过bindResource()
将其绑定到线程上下文中:TransactionSynchronizationManager#resources
,这样在其它地方需要使用到数据库连接时就可以从这里获取到配置好的Connection
。
transactionManager.getTransaction(def)
方法开启事务后,然后就jdbcTemplate.update(sql1)
就开始执行数据库操作,追踪代码发现这里关键位于
JdbcTemplate#execute()
中:
public T execute(StatementCallback action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
关键是Connection con = DataSourceUtils.getConnection(obtainDataSource());
这里获取Connection
,然后进行各种JDBC
操作。
跟踪DataSourceUtils.getConnection()
逻辑大致是:TransactionSynchronizationManager.getResource(dataSource);
从线程上下文中获取;获取不到再回去DataSource
中获取。上个流程PlatformTransactionManager#getTransaction
中,已经将配置好的ConnectionHolder
绑定到了线程上下文中,所以这里就可以获取到配置好的ConnectionHolder
,进而获取到Connection
。
操作完成后,就可以使用transactionManager.commit(status)
或transactionManager.rollback(status)
进行事务提交或回滚。
比如事务提交,核心是在事务管理器实现类的doCommit()
方法中:
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
try {
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
原理很简单,就是从TransactionStatus
获取到底层的Connection
,然后执行commit()
方法即可。事务回滚逻辑基本一致,就不再分析。
TransactionDefinition
、PlatformTransactionManager
和TransactionStatus
三大核心类如何相互配合实现Spring中编程式事务的原理应该大致比较了解了。这里再简单总结下:
配置:开启事务时从DataSource
获取到底层Connection
实例,然后进行配置,最主要的是将其autoCommit
设置成false
;
绑定:然后通过TransactionSynchronizationManager.bindResource()
绑定到线程上下文中;
使用:需要使用事务操作时,就从上步绑定的线程上下文中获取配置好的Connection
进行数据库操作即可;
提交/回滚:当对TransactionStatus
执行commit
或rollback
时,由TransactionStatus
是可以直接获取到Connection
实例,然后对其进行commit
或rollback
即可。
长按识别关注,持续输出原创