javascript
事务里面捕获异常-凯发k8官方网
让我们先从事务说起,“什么是事务?我们为什么需要事务?”。
事务是一组无法被分割的操作,要么所有操作全部成功,要么全部失败。我们在开发中需要通过事务将一些操作组成一个单元,来保证程序逻辑上的正确性,例如全部插入成功,或者回滚,一条都不插入。作为程序员的我们,对于事务管理,所需要做的便是进行事务的界定,即通过类似begin transaction和end transaction的操作来界定事务的开始和结束。
下面是一个基本的jdbc事务管理代码:
// 开启数据库连接connection con = openconnection();try { // 关闭自动提交 con.setautocommit(false); // 业务处理 // ... // 提交事务 con.commit();} catch (sqlexception | myexception e) { // 捕获异常,回滚事务 try { con.rollback(); } catch (sqlexception ex) { ex.printstacktrace(); }} finally { // 关闭连接 try { con.setautocommit(true); con.close(); } catch (sqlexception e) { e.printstacktrace(); }}直接使用jdbc进行事务管理的代码直观上来看,存在两个问题:
而如果我们需要更换其他数据访问技术,例如hibernate、mybatis、jpa等,虽然事务管理的操作都类似,但api却不同,则需使用相应的api来改写。这也会引来第三个问题:
上文列出了三个待解决的问题,下面我们看spring事务是如何解决。
针对该问题,我们很容易可以想到,在众多事务管理的api上抽象一层。通过定义接口屏蔽具体实现,再使用策略模式来决定具体的api。下面我们看下spring事务中定义的抽象接口。
在spring事务中,核心接口是platformtransactionmanager,也叫事务管理器,其定义如下:
public interface platformtransactionmanager extends transactionmanager { // 获取事务(新的事务或者已经存在的事务) transactionstatus gettransaction(@nullable transactiondefinition definition)throws transactionexception; // 提交事务 void commit(transactionstatus status) throws transactionexception; // 回滚事务 void rollback(transactionstatus status) throws transactionexception;}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实现如下:
public final void commit(transactionstatus status) throws transactionexception { // 1.检查事务是否已完成 if (status.iscompleted()) { throw new illegaltransactionstateexception( "transaction is already completed - do not call commit or rollback more than once per transaction"); } // 2.检查事务是否需要回滚(局部事务回滚) defaulttransactionstatus defstatus = (defaulttransactionstatus) status; if (defstatus.islocalrollbackonly()) { if (defstatus.isdebug()) { logger.debug("transactional code has requested rollback"); } processrollback(defstatus, false); return; } // 3.检查事务是否需要回滚(全局事务回滚) if (!shouldcommitonglobalrollbackonly() && defstatus.isglobalrollbackonly()) { if (defstatus.isdebug()) { logger.debug("global transaction is marked as rollback-only but transactional code requested commit"); } processrollback(defstatus, true); return; } // 4.提交事务 processcommit(defstatus);}在commit模板方法中定义了事务提交的基本逻辑,通过查看status的事务状态来决定抛出异常还是回滚,或是提交。其中的processrollback和processcommit方法也是模板方法,进一步定义了回滚、提交的逻辑。以processcommit方法为例,具体的提交操作将由抽象方法docommit完成。
protected abstract void docommit(defaulttransactionstatus status) throws transactionexception;docommit的实现取决于具体的数据访问技术。我们看下jdbc相应的具体实现类datasourcetransactionmanager中的docommit实现。
protected void docommit(defaulttransactionstatus status) { // 获取status中的事务对象 datasourcetransactionobject txobject = (datasourcetransactionobject) status.gettransaction(); // 通过事务对象获得数据库连接对象 connection con = txobject.getconnectionholder().getconnection(); if (status.isdebug()) { logger.debug("committing jdbc transaction on connection [" con "]"); } try { // 执行commit con.commit(); } catch (sqlexception ex) { throw new transactionsystemexception("could not commit jdbc transaction", ex); }}在commit和processcommit方法中我们根据入参的transactionstatus提供的事务状态来决定事务行为,而在docommit中需要执行事务提交时将会通过transactionstatus中的事务对象来获得数据库连接对象,再执行最后的commit操作。通过这个示例我们可以理解transactionstatus所提供的事务状态和事务对象的作用。
下面是用spring事务api改写后的事务管理代码:
// 获得事务管理器platformtransactionmanager txmanager = getplatformtransactionmanager();defaulttransactiondefinition def = new defaulttransactiondefinition();// 指定事务元信息def.setname("sometxname");def.setpropagationbehavior(transactiondefinition.propagation_required);// 获得事务transactionstatus status = txmanager.gettransaction(def);try { // 业务处理}catch (myexception ex) { // 捕获异常,回滚事务 txmanager.rollback(status); throw ex;}// 提交事务txmanager.commit(status);无论是使用jdbc、hibernate还是mybatis,我们只需要传给txmanager相应的具体实现就可以在多种数据访问技术中切换。
小结:spring事务通过platformtransactionmanager、transactiondefinition和transactionstatus接口统一事务管理api,并结合策略模式和模板方法决定具体实现。
spring事务api代码还有个特点有没有发现,sqlexception不见了。下面来看spring事务是如何解决大量的异常处理代码。
为什么使用jdbc的代码中会需要写这么多的异常处理代码。这是因为connection的每个方法都会抛出sqlexception,而sqlexception又是检查异常,这就强制我们在使用其方法时必须进行异常处理。那spring事务是如何解决该问题的。我们看下docommit方法:
protected void docommit(defaulttransactionstatus status) { datasourcetransactionobject txobject = (datasourcetransactionobject) status.gettransaction(); connection con = txobject.getconnectionholder().getconnection(); if (status.isdebug()) { logger.debug("committing jdbc transaction on connection [" con "]"); } try { con.commit(); } catch (sqlexception ex) { // 异常转换 throw new transactionsystemexception("could not commit jdbc transaction", ex); }}connection的commit方法会抛出检查异常sqlexception,在catch代码块中sqlexception将被转换成transactionsystemexception抛出,而transactionsystemexception是一个非检查异常。通过将检查异常转换成非检查异常,让我们能够自行决定是否捕获异常,不强制进行异常处理。
spring事务中几乎为数据库的所有错误都定义了相应的异常,统一了jdbc、hibernate、mybatis等不同异常api。这有助于我们在处理异常时使用统一的异常api接口,无需关心具体的数据访问技术。
小结:spring事务通过异常转换避免强制异常处理。
在2.1节中给出了使用spring事务api的写法,即编程式事务管理,但仍未解决“业务处理代码与事务管理代码混杂”的问题。这时候就可以利用spring aop将事务管理代码这一横切关注点从代码中剥离出来,即声明式事务管理。以注解方式为例,通过为方法标注@transaction注解,将为该方法提供事务管理。其原理如下图所示:
spring事务会为@transaction标注的方法的类生成aop增强的动态代理类对象,并且在调用目标方法的拦截链中加入transactioninterceptor进行环绕增加,实现事务管理。
下面我们看下transactioninterceptor中的具体实现,其invoke方法中将调用invokewithintransaction方法进行事务管理,如下所示:
protected object invokewithintransaction(method method, class> targetclass, final invocationcallback invocation) throws throwable { // 查询目标方法事务属性、确定事务管理器、构造连接点标识(用于确认事务名称) final transactionattribute txattr = gettransactionattributesource().gettransactionattribute(method, targetclass); final platformtransactionmanager tm = determinetransactionmanager(txattr); final string joinpointidentification = methodidentification(method, targetclass, txattr); if (txattr == null || !(tm instanceof callbackpreferringplatformtransactionmanager)) { // 创建事务 transactioninfo txinfo = createtransactionifnecessary(tm, txattr, joinpointidentification); object retval = null; try { // 通过回调执行目标方法 retval = invocation.proceedwithinvocation(); } catch (throwable ex) { // 目标方法执行抛出异常,根据异常类型执行事务提交或者回滚操作 completetransactionafterthrowing(txinfo, ex); throw ex; } finally { // 清理当前线程事务信息 cleanuptransactioninfo(txinfo); } // 目标方法执行成功,提交事务 committransactionafterreturning(txinfo); return retval; } else { // 带回调的事务执行处理,一般用于编程式事务 // ... }}在调用目标方法前后加入了创建事务、处理异常、提交事务等操作。这让我们不必编写事务管理代码,只需通过@transaction的属性指定事务相关元信息。
小结:spring事务通过aop提供声明式事务将业务处理代码和事务管理代码分离。
spring事务为了我们解决了第一节中列出的三个问题,但同时也会带来些新的问题。
@transactional只有标注在public级别的方法上才能生效,对于非public方法将不会生效。这是由于spring aop不支持对private、protect方法进行拦截。从原理上来说,动态代理是通过接口实现,所以自然不能支持private和protect方法的。而cglib是通过继承实现,其实是可以支持protect方法的拦截的,但spring aop中并不支持这样使用,笔者猜测做此限制是出于代理方法应是public的考虑,以及为了保持cglib和动态代理的一致。如果需要对protect或private方法拦截则建议使用aspectj。
当通过在bean的内部方法直接调用带有@transactional的方法时,@transactional将失效,例如:
public void saveab(a a, b b){ savea(a); saveb(b);}@transactionalpublic void savea(a a){ dao.savea(a);}@transactionalpublic void saveb(b b){ dao.saveb(b);}在saveab中调用savea和saveb方法,两者的@transactional都将失效。这是因为spring事务的实现基于代理类,当在内部直接调用方法时,将不会经过代理对象,而是直接调用目标对象的方法,无法被transactioninterceptor拦截处理。解决办法:
(1)applicationcontextaware
通过applicationcontextaware注入的上下文获得代理对象。
public void saveab(a a, b b){ test self = (test) applicationcontext.getbean("test"); self.savea(a); self.saveb(b);}(2)aopcontext
通过aopcontext获得代理对象。
public void saveab(a a, b b){ test self = (test)aopcontext.currentproxy(); self.savea(a); self.saveb(b);}(3)@autowired
通过@autowired注解注入代理对象。
@componentpublic class test { @autowired test self; public void saveab(a a, b b) { self.savea(a); self.saveb(b); } // ...}(4)拆分
将savea、saveb方法拆分到另一个类中。
public void saveab(a a, b b){ txoperate.savea(a); txoperate.saveb(b);}上述两个问题都是由于spring事务的实现方式的限制导致的问题。下面再看两个由于使用不当容易犯错的两个问题。
在默认情况下,抛出非检查异常会触发回滚,而检查异常不会。
根据invokewithintransaction方法,我们可以知道异常处理逻辑在completetransactionafterthrowing方法中,其实现如下:
protected void completetransactionafterthrowing(@nullable transactioninfo txinfo, throwable ex) { if (txinfo != null && txinfo.gettransactionstatus() != null) { if (logger.istraceenabled()) { logger.trace("completing transaction for [" txinfo.getjoinpointidentification() "] after exception: " ex); } if (txinfo.transactionattribute != null && txinfo.transactionattribute.rollbackon(ex)) { try { // 异常类型为回滚异常,执行事务回滚 txinfo.gettransactionmanager().rollback(txinfo.gettransactionstatus()); } catch (transactionsystemexception ex2) { logger.error("application exception overridden by rollback exception", ex); ex2.initapplicationexception(ex); throw ex2; } catch (runtimeexception | error ex2) { logger.error("application exception overridden by rollback exception", ex); throw ex2; } } else { try { // 异常类型为非回滚异常,仍然执行事务提交 txinfo.gettransactionmanager().commit(txinfo.gettransactionstatus()); } catch (transactionsystemexception ex2) { logger.error("application exception overridden by commit exception", ex); ex2.initapplicationexception(ex); throw ex2; } catch (runtimeexception | error ex2) { logger.error("application exception overridden by commit exception", ex); throw ex2; } } }}根据rollbackon判断异常是否为回滚异常。只有runtimeexception和error的实例,即非检查异常,或者在@transaction中通过rollbackfor属性指定的回滚异常类型,才会回滚事务。否则将继续提交事务。所以如果需要对非检查异常进行回滚,需要记得指定rollbackfor属性,不然将回滚失效。
在3.3节中我们说到只有抛出非检查异常或是rollbackfor中指定的异常才能触发回滚。如果我们把异常catch住,而且没抛出,则会导致无法触发回滚,这也是开发中常犯的错误。例如:
@transactionalpublic void insert(list users) { try { jdbctemplate jdbctemplate = new jdbctemplate(datasource); for (user user : users) { string insertusersql = "insert into user (id, name) values (?,?)"; jdbctemplate.update(insertusersql, new object[] { user.getid(), user.getname() }); } } catch (exception e) { e.printstacktrace(); }}这里由于catch住了所有exception,并且没抛出。当插入发生异常时,将不会触发回滚。
但同时我们也可以利用这种机制,用try-catch包裹不用参与事务的数据操作,例如对于写入一些不重要的日志,我们可将其用try-catch包裹,避免抛出异常,则能避免写日志失败而影响事务的提交。
作者:草捏子
来源:掘金
总结
以上是凯发k8官方网为你收集整理的事务里面捕获异常_三问spring事务:解决什么问题?如何解决?存在什么问题?...的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: python中的import详解_pyt
- 下一篇: lingo变量无限制版本_【运筹学】用l