欢迎访问 生活随笔!

ag凯发k8国际

当前位置: ag凯发k8国际 > 前端技术 > javascript >内容正文

javascript

事务里面捕获异常-ag凯发k8国际

发布时间:2024/10/14 javascript 23 豆豆
ag凯发k8国际 收集整理的这篇文章主要介绍了 事务里面捕获异常_三问spring事务:解决什么问题?如何解决?存在什么问题?... 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

让我们先从事务说起,“什么是事务?我们为什么需要事务?”。

事务是一组无法被分割的操作,要么所有操作全部成功,要么全部失败。我们在开发中需要通过事务将一些操作组成一个单元,来保证程序逻辑上的正确性,例如全部插入成功,或者回滚,一条都不插入。作为程序员的我们,对于事务管理,所需要做的便是进行事务的界定,即通过类似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进行事务管理的代码直观上来看,存在两个问题:

  • 业务处理代码与事务管理代码混杂;
  • 大量的异常处理代码(在catch中还要try-catch)。
  • 而如果我们需要更换其他数据访问技术,例如hibernate、mybatis、jpa等,虽然事务管理的操作都类似,但api却不同,则需使用相应的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包裹,避免抛出异常,则能避免写日志失败而影响事务的提交。

    作者:草捏子
    来源:掘金

    总结

    以上是ag凯发k8国际为你收集整理的事务里面捕获异常_三问spring事务:解决什么问题?如何解决?存在什么问题?...的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得ag凯发k8国际网站内容还不错,欢迎将ag凯发k8国际推荐给好友。

    网站地图