对于普通项目不太关注使用 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<Object, Object>> Map 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> T execute(StatementCallback<T> 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
长按识别关注, 持续输出原创