让我们先从事务提及,“什么是事务?我们为什么须要事务?”。
事务是一组无法被分割的操作,要么所有操作全部成功,要么全部失落败。
我们在开拓中须要通过事务将一些操作组成一个单元,来担保程序逻辑上的精确性,例如全部插入成功,或者回滚,一条都不插入。
作为程序员的我们,对付事务管理,所须要做的便是进行事务的界定,即通过类似begin transaction和end transaction的操作来界定事务的开始和结束。

下面是一个基本的JDBC事务管理代码

//开启数据库连接Connectioncon=openConnection();try{//关闭自动提交con.setAutoCommit(false);//业务处理//...//提交事务con.commit();}catch(SQLException|MyExceptione){//捕获非常,回滚事务try{con.rollback();}catch(SQLExceptionex){ex.printStackTrace();}}finally{//关闭连接try{con.setAutoCommit(true);con.close();}catch(SQLExceptione){e.printStackTrace();}}

直策应用JDBC进行事务管理的代码直不雅观上来看,存在两个问题:

jspcallback三问Spring事务解决什么问题若何解决存在什么问题 Vue.js

业务处理代码与事务管理代码殽杂;大量的非常处理代码(在catch中还要try-catch)。

而如果我们须要改换其他数据访问技能,例如Hibernate、MyBatis、JPA等,虽然事务管理的操作都类似,但API却不同,则需利用相应的API来改写。
这也会引来第三个问题:

繁杂的事务管理API。

上文列出了三个待办理的问题,下面我们看Spring事务是如何办理。

2. 如何办理2.1 繁杂的事务管理API

针对该问题,我们很随意马虎可以想到,在浩瀚事务管理的API上抽象一层。
通过定义接口屏蔽详细实现,再利用策略模式来决定详细的API。
下面我们看下Spring事务中定义的抽象接口。

在Spring事务中,核心接口是PlatformTransactionManager,也叫事务管理器,其定义如下:

publicinterfacePlatformTransactionManagerextendsTransactionManager{//获取事务(新的事务或者已经存在的事务)TransactionStatusgetTransaction(@NullableTransactionDefinitiondefinition)throwsTransactionException;//提交事务voidcommit(TransactionStatusstatus)throwsTransactionException;//回滚事务voidrollback(TransactionStatusstatus)throwsTransactionException;}

getTransaction通过入参TransactionDefinition来得到TransactionStatus,即通过定义的事务元信息来创建相应的事务工具。
在TransactionDefinition中会包含事务的元信息:

PropagationBehavior:传播行为;IsolationLevel:隔离级别;Timeout:超时时间;ReadOnly:是否只读。

根据TransactionDefinition得到的TransactionStatus中会封装事务工具,并供应了操作事务和查看事务状态的方法,例如:

setRollbackOnly:标记事务为Rollback-only,以使其回滚;isRollbackOnly:查看是否被标记为Rollback-only;isCompleted:查看事务是否已完成(提交或回滚完成)。

还支持嵌套事务的干系方法:

createSavepoint:创建savepoint;rollbackToSavepoint:回滚到指定savepoint;releaseSavePoint:开释savepoint。

TransactionStatus事务工具可被传入到commit方法或rollback方法中,完成事务的提交或回滚。

下面我们通过一个详细实现来理解TransactionStatus的浸染。
以commit方法为例,如何通过TransactionStatus完成事务的提交。
AbstractPlatformTransactionManager是PlatformTransactionManager接口的的实现,作为模板类,其commit实现如下:

publicfinalvoidcommit(TransactionStatusstatus)throwsTransactionException{//1.检讨事务是否已完成if(status.isCompleted()){thrownewIllegalTransactionStateException("Transactionisalreadycompleted-donotcallcommitorrollbackmorethanoncepertransaction");}//2.检讨事务是否须要回滚(局部事务回滚)DefaultTransactionStatusdefStatus=(DefaultTransactionStatus)status;if(defStatus.isLocalRollbackOnly()){if(defStatus.isDebug()){logger.debug("Transactionalcodehasrequestedrollback");}processRollback(defStatus,false);return;}//3.检讨事务是否须要回滚(全局事务回滚)if(!shouldCommitOnGlobalRollbackOnly()&&defStatus.isGlobalRollbackOnly()){if(defStatus.isDebug()){logger.debug("Globaltransactionismarkedasrollback-onlybuttransactionalcoderequestedcommit");}processRollback(defStatus,true);return;}//4.提交事务processCommit(defStatus);}

在commit模板方法中定义了事务提交的基本逻辑,通过查看status的事务状态来决定抛出非常还是回滚,或是提交。
个中的processRollback和processCommit方法也是模板方法,进一步定义了回滚、提交的逻辑。
以processCommit方法为例,详细的提交操作将由抽象方法doCommit完成。

protectedabstractvoiddoCommit(DefaultTransactionStatusstatus)throwsTransactionException;

doCommit的实现取决于详细的数据访问技能。
我们看下JDBC相应的详细实现类DataSourceTransactionManager中的doCommit实现。

protectedvoiddoCommit(DefaultTransactionStatusstatus){//获取status中的事务工具DataSourceTransactionObjecttxObject=(DataSourceTransactionObject)status.getTransaction();//通过事务工具得到数据库连接工具Connectioncon=txObject.getConnectionHolder().getConnection();if(status.isDebug()){logger.debug("CommittingJDBCtransactiononConnection["+con+"]");}try{//实行commitcon.commit();}catch(SQLExceptionex){thrownewTransactionSystemException("CouldnotcommitJDBCtransaction",ex);}}

在commit和processCommit方法中我们根据入参的TransactionStatus供应的事务状态来决定事务行为,而在doCommit中须要实行事务提交时将会通过TransactionStatus中的事务工具来得到数据库连接工具,再实行末了的commit操作。
通过这个示例我们可以理解TransactionStatus所供应的事务状态和事务工具的浸染。

下面是用Spring事务API改写后的事务管理代码:

//得到事务管理器PlatformTransactionManagertxManager=getPlatformTransactionManager();DefaultTransactionDefinitiondef=newDefaultTransactionDefinition();//指定事务元信息def.setName("SomeTxName");def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//得到事务TransactionStatusstatus=txManager.getTransaction(def);try{//业务处理}catch(MyExceptionex){//捕获非常,回滚事务txManager.rollback(status);throwex;}//提交事务txManager.commit(status);

无论是利用JDBC、Hibernate还是MyBatis,我们只须要传给txManager相应的详细实现就可以在多种数据访问技能中切换。

小结:Spring事务通过PlatformTransactionManager、TransactionDefinition和TransactionStatus接口统一事务管理API,并结合策略模式和模板方法决定详细实现。

Spring事务API代码还有个特点有没有创造,SQLException不见了。
下面来看Spring事务是如何办理大量的非常处理代码。

2.2 大量的非常处理代码

为什么利用JDBC的代码中会须要写这么多的非常处理代码。
这是由于Connection的每个方法都会抛出SQLException,而SQLException又是检讨非常,这就逼迫我们在利用其方法时必须进行非常处理。
那Spring事务是如何办理该问题的。
我们看下doCommit方法:

protectedvoiddoCommit(DefaultTransactionStatusstatus){DataSourceTransactionObjecttxObject=(DataSourceTransactionObject)status.getTransaction();Connectioncon=txObject.getConnectionHolder().getConnection();if(status.isDebug()){logger.debug("CommittingJDBCtransactiononConnection["+con+"]");}try{con.commit();}catch(SQLExceptionex){//非常转换thrownewTransactionSystemException("CouldnotcommitJDBCtransaction",ex);}}

Connection的commit方法会抛出检讨非常SQLException,在catch代码块中SQLException将被转换成TransactionSystemException抛出,而TransactionSystemException是一个非检讨非常。
通过将检讨非常转换成非检讨非常,让我们能够自行决定是否捕获非常,不逼迫进行非常处理。

Spring事务中险些为数据库的所有缺点都定义了相应的非常,统一了JDBC、Hibernate、MyBatis平分歧非常API。
这有助于我们在处理非常时利用统一的非常API接口,无需关心详细的数据访问技能。

小结:Spring事务通过非常转换避免逼迫非常处理。

2.3 业务处理代码与事务管理代码殽杂

在2.1节中给出了利用Spring事务API的写法,即编程式事务管理,但仍未办理“业务处理代码与事务管理代码殽杂”的问题。
这时候就可以利用Spring AOP将事务管理代码这一横切关注点从代码中剥离出来,即声明式事务管理。
以表明办法为例,通过为方法标注@Transaction表明,将为该方法供应事务管理。
其事理如下图所示:

声明式事务事理

Spring事务会为@Transaction标注的方法的类天生AOP增强的动态代理类工具,并且在调用目标方法的拦截链中加入TransactionInterceptor进行环抱增加,实现事务管理。

下面我们看下TransactionInterceptor中的详细实现,其invoke方法中将调用invokeWithinTransaction方法进行事务管理,如下所示:

protectedObjectinvokeWithinTransaction(Methodmethod,Class<?>targetClass,finalInvocationCallbackinvocation)throwsThrowable{//查询目标方法事务属性、确定事务管理器、布局连接点标识(用于确认事务名称)finalTransactionAttributetxAttr=getTransactionAttributeSource().getTransactionAttribute(method,targetClass);finalPlatformTransactionManagertm=determineTransactionManager(txAttr);finalStringjoinpointIdentification=methodIdentification(method,targetClass,txAttr);if(txAttr==null||!(tminstanceofCallbackPreferringPlatformTransactionManager)){//创建事务TransactionInfotxInfo=createTransactionIfNecessary(tm,txAttr,joinpointIdentification);ObjectretVal=null;try{//通过回调实行目标方法retVal=invocation.proceedWithInvocation();}catch(Throwableex){//目标方法实行抛出非常,根据非常类型实行事务提交或者回滚操作completeTransactionAfterThrowing(txInfo,ex);throwex;}finally{//清理当前哨程事务信息cleanupTransactionInfo(txInfo);}//目标方法实行成功,提交事务commitTransactionAfterReturning(txInfo);returnretVal;}else{//带回调的事务实行处理,一样平常用于编程式事务//...}}

在调用目标方法前后加入了创建事务、处理非常、提交事务等操作。
这让我们不必编写事务管理代码,只需通过@Transaction的属性指定事务干系元信息。

小结:Spring事务通过AOP供应声明式事务将业务处理代码和事务管理代码分离。

3. 存在什么问题

Spring事务为了我们办理了第一节中列出的三个问题,但同时也会带来些新的问题。

3.1 非public方法失落效

@Transactional只有标注在public级别的方法上才能生效,对付非public方法将不会生效。
这是由于Spring AOP不支持对private、protect方法进行拦截。
从事理上来说,动态代理是通过接口实现,以是自然不能支持private和protect方法的。
而CGLIB是通过继续实现,实在是可以支持protect方法的拦截的,但Spring AOP中并不支持这样利用,笔者预测做此限定是出于代理方法应是public的考虑,以及为了保持CGLIB和动态代理的同等。
如果须要对protect或private方法拦截则建议利用AspectJ。

3.2 自调用失落效

当通过在Bean的内部方法直接调用带有@Transactional的方法时,@Transactional将失落效,例如:

publicvoidsaveAB(Aa,Bb){saveA(a);saveB(b);}@TransactionalpublicvoidsaveA(Aa){dao.saveA(a);}@TransactionalpublicvoidsaveB(Bb){dao.saveB(b);}

在saveAB中调用saveA和saveB方法,两者的@Transactional都将失落效。
这是由于Spring事务的实现基于代理类,当在内部直接调用方法时,将不会经由代理工具,而是直接调用目标工具的方法,无法被TransactionInterceptor拦截处理。
办理办法:

(1)ApplicationContextAware

通过ApplicationContextAware注入的高下文得到代理工具。

publicvoidsaveAB(Aa,Bb){Testself=(Test)applicationContext.getBean("Test");self.saveA(a);self.saveB(b);}

(2)AopContext

通过AopContext得到代理工具。

publicvoidsaveAB(Aa,Bb){Testself=(Test)AopContext.currentProxy();self.saveA(a);self.saveB(b);}

(3)@Autowired

通过@Autowired表明注入代理工具。

@ComponentpublicclassTest{@AutowiredTestself;publicvoidsaveAB(Aa,Bb){self.saveA(a);self.saveB(b);}//...}

(4)拆分

将saveA、saveB方法拆分到另一个类中。

publicvoidsaveAB(Aa,Bb){txOperate.saveA(a);txOperate.saveB(b);}

上述两个问题都是由于Spring事务的实现办法的限定导致的问题。
下面再看两个由于利用不当随意马虎犯错的两个问题。

3.3 检讨非常默认不回滚

在默认情形下,抛出非检讨非常会触发回滚,而检讨非常不会。

根据invokeWithinTransaction方法,我们可以知道非常处理逻辑在completeTransactionAfterThrowing方法中,实在现如下:

protectedvoidcompleteTransactionAfterThrowing(@NullableTransactionInfotxInfo,Throwableex){if(txInfo!=null&&txInfo.getTransactionStatus()!=null){if(logger.isTraceEnabled()){logger.trace("Completingtransactionfor["+txInfo.getJoinpointIdentification()+"]afterexception:"+ex);}if(txInfo.transactionAttribute!=null&&txInfo.transactionAttribute.rollbackOn(ex)){try{//非常类型为回滚非常,实行事务回滚txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());}catch(TransactionSystemExceptionex2){logger.error("Applicationexceptionoverriddenbyrollbackexception",ex);ex2.initApplicationException(ex);throwex2;}catch(RuntimeException|Errorex2){logger.error("Applicationexceptionoverriddenbyrollbackexception",ex);throwex2;}}else{try{//非常类型为非回滚非常,仍旧实行事务提交txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());}catch(TransactionSystemExceptionex2){logger.error("Applicationexceptionoverriddenbycommitexception",ex);ex2.initApplicationException(ex);throwex2;}catch(RuntimeException|Errorex2){logger.error("Applicationexceptionoverriddenbycommitexception",ex);throwex2;}}}}

根据rollbackOn判断非常是否为回滚非常。
只有RuntimeException和Error的实例,即非检讨非常,或者在@Transaction中通过rollbackFor属性指定的回滚非常类型,才会回滚事务。
否则将连续提交事务。
以是如果须要对非检讨非常进行回滚,须要记得指定rollbackFor属性,不然将回滚失落效。

3.4 catch非常无法回滚

在3.3节中我们说到只有抛出非检讨非常或是rollbackFor中指定的非常才能触发回滚。
如果我们把非常catch住,而且没抛出,则会导致无法触发回滚,这也是开拓中常犯的缺点。
例如:

@Transactionalpublicvoidinsert(List<User>users){try{JdbcTemplatejdbcTemplate=newJdbcTemplate(dataSource);for(Useruser:users){StringinsertUserSql="insertintoUser(id,name)values(?,?)";jdbcTemplate.update(insertUserSql,newObject[]{user.getId(),user.getName()});}}catch(Exceptione){e.printStackTrace();}}

这里由于catch住了所有Exception,并且没抛出。
当插入发生非常时,将不会触发回滚。

但同时我们也可以利用这种机制,用try-catch包裹不用参与事务的数据操作,例如对付写入一些不主要的日志,我们可将其用try-catch包裹,避免抛出非常,则能避免写日志失落败而影响事务的提交。

各种知识点总结

下面的文章都有对应的原创精美PDF,在持续更新中,可以来找我催更~

92页的Mybatis

129页的多线程

141页的Servlet

158页的JSP

76页的凑集

64页的JDBC

105页的数据构造和算法

142页的Spring

58页的过滤器和监听器

30页的HTTP

42页的SpringMVC

Hibernate

AJAX

Redis

......

转发+关注 私信我【资料】即可免费领取

转发+关注 私信我【资料】即可免费领取

转发+关注 私信我【资料】即可免费领取