事务控制的核心——Connection
在开始之前,先让我们回忆一下数据库较原始的JDBC是怎么管理事务的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//仅做演示,代码不完整,不完全规范 try { con.setAutoCommit( false ); statement1 = con.prepareStatement(sql); statement1.executeUpdate(); statement2 = con.prepareStatement(sql1); statement2.executeUpdate(); con.commit(); } catch (SQLException e) { try { con.rollback(); } catch (SQLException e1) { } } |
可以很明显的看到,JDBC框架下的事务控制是由connection完成的。因为不论是MyBatis还是MyBatis-Spring都是在JDBC框架基础上的高层框架,所以他们的原理仍然应该是一致的,也就是说想控制事务,必须要控制Connection。
我们常说事务要切在Service层,所以连接需要在整个Service请求中都是同一个,不能变。
用AOP技术保持当前的Connection
Spring的事务管理就是使用AOP技术,通过对Service层设置切面,注入事务管理的逻辑。
Spring的事务管理切面配置采用了声明式事务,最常用的两种方法是 tx:Advice 和 tx:annotation-driven 两种方式。
两种方式的配置文件解析器分别是:
org.springframework.transaction.config.TxAdviceBeanDefinitionParser
org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser
细看其中的代码和配置内容,就会发现,不论哪种方式都会创建包含事务处理功能的动态代理。代理关联的切面(Advice)类是 TransactionInterceptor 。
一起看下关键代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { //...... if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); //-----1.开启事务 Object retVal = null ; try { retVal = invocation.proceedWithInvocation(); //...2.执行被代理的请求 } catch (Throwable ex) { completeTransactionAfterThrowing(txInfo, ex); //...3.异常回滚 throw ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); //...4.提交事务 return retVal; } //...... |
上边的代码里是不是没有看到TransactionManger和Connection?那么这两个东西的作用在哪里呢?
TransactionManger是上述TransactionInterceptor的一个属性(不严的说),主要作用是用来创建connection,创建之后的Connection会被保存在TransactionInfo里面。
TransactionInfo在上述代码片段中被后续传递给事务提交和事务回滚的代码。
Service层和Dao层共享Connection
我们都知道事务要切在Service层,也就是说上一节的切面只是在Service层有效,那么Dao层怎么获取到Connection连接呢?
如果Service层和Dao层的连接不是一个连接那么回滚和提交操作就等同于无效了!
这里只用MyBatis来说明,其他的ORM框架实现原理基本也是一样的。
要明白这一点需要先弄明白MyBatis本身的事务管理机制,可以参考MyBatis源码解析之Transaction事务模块。MyBatis提供了两种事务管理机制一种是自己内部用的JDBC模式,一种是支持代理给外部控制的MANAGED模式。
第二种模式下会把事务的交给外部控制,外部只需要提供一个实现了 org.apache.ibatis.transaction.Transaction 接口的控制类即可。
一起来看一下Transaction需要提供哪些方法:
1
2
3
4
5
6
|
public interface Transaction { Connection getConnection() throws SQLException; void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; } |
注意里边的getConnection方法,也就是说MyBatis的连接也是交给外部来获取的!!那么只需要想办法把Service层的Connection存起来,然后让自己实现Transaction获取到即可。
Spring采用的是ThreadLocal本地线程变量的技术来做到的,我们可以看下mybatis-spring的 org.mybatis.spring.transaction.SpringManagedTransaction 中getConnection的实现就明白了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public Connection getConnection() throws SQLException { if ( this .connection == null ) { openConnection(); } return this .connection; } private void openConnection() throws SQLException { this .connection = DataSourceUtils.getConnection( this .dataSource); //...1.关键点在这里!! 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" ); } } |
其中
1
|
this .connection = DataSourceUtils.getConnection( this .dataSource); |
一行就会从ThreadLocal中拿到Connection对象。
事务为什么要切在Service层的理由
对于这个常识,有一点个人的理解:
事务的ACID要求事务要有原子性,也就是一个事务里边的多项DB操作要同时成功,同时失败,成功一半的情况是不允许的。
也就是说,一般需要事务的时候,都是包含多个功能单元的。那么我们都放在一个Dao里面就显得不那么职能分明,也就是不那么符合设计原则的单一职责原则。
spring事务与数据库事务的区别
先说一下什么是事务,事务(Transaction):
一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。。
之前一直觉得事务只针对于数据库当中,5种隔离级别,7种传播行为,后来才发现这是针对Spring的,对数据库来说隔离级别只有4种,Spring多了一个DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
总的来说,本质上其实是同一个概念
spring的事务是对数据库的事务的封装,最后本质的实现还是在数据库,假如数据库不支持事务的话,spring的事务是没有作用的
数据库的事务说简单就只有开启,回滚和关闭,spring对数据库事务的包装,原理就是拿一个数据连接,根据spring的事务配置,操作这个数据连接对数据库进行事务开启,回滚或关闭操作.但是spring除了实现这些,还配合spring的传播行为对事务进行了更广泛的管理.其实这里还有个重要的点,那就是事务中涉及的隔离级别,以及spring如何对数据库的隔离级别进行封装.事务与隔离级别放在一起理解会更好些。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://buqutianya.blog.csdn.net/article/details/78946473