事务管理最佳实践多余的话之三Spring声明式事务管理出错示例与解决之道

 
                              事务管理最佳实践多余的话之三
              Spring 声明式事务管理出错示例与解决之道
 
 
 
前言
今天,发现了一个以前写的使用Spring声明式事务管理的程序爆出了数据库连接错误,感觉是非常典型的一个误用Spring声明式事务管理的例子,拿出来为大家点评一下。
 
 
Spring声明式事务管理出错示例
这个应用程序是使用Spring管理的iBatis程序。事务使用了Spring的声明式事务管理。
爆出了如下的错误:
Caused by:
java.sql.SQLException : Connection is read-only. Queries leading to data modification are not allowed
 
at com.withub.wcms.manage.collectnews.systemNewsinfo.dao.WcmsSystemNewsinfoDao.add( WcmsSystemNewsinfoDao.java:50 )
    at com.withub.wcms.manage.collectnews.systemNewsinfo.service.WcmsSystemNewsinfoService.saveOrUpdate( WcmsSystemNewsinfoService.java:103 )
    at com.withub.wcms.manage.collectnews.systemNewsinfo.service.WcmsSystemNewsinfoService.formatRawInfoToHtml( WcmsSystemNewsinfoService.java:89 )
 
可以看出,出现的错误是,Spring管理下的iBatis使用的数据库连接是只读的。而在DAO类的方法中,却使用了修改数据库的iBatis方法。
 
源代码:
WcmsSystemNewsinfoDao类的源代码就不列出来了。这个Dao类的add方法使用了iBatis的update方法,更新数据库数据。
下面是 WcmsSystemNewsinfoService 类的部分相关调用方法的源代码:
public String formatRawInfoToHtml(String infoRawId) throws Exception{
       WcmsSystemNewsinfoRawModule wcmsSystemNewsinfoRawModule= new WcmsSystemNewsinfoRawModule();
       wcmsSystemNewsinfoRawModule.setInfoId(infoRawId);
       // 原表数据
        wcmsSystemNewsinfoRawModule= this .getManageNewsinfoService().queryRawInfoById(wcmsSystemNewsinfoRawModule);
       WcmsSystemNewsinfoModule wcmsSystemNewsinfoModule= new WcmsSystemNewsinfoModule();
       // 目标表数据
       BeanUtils.copyProperties(wcmsSystemNewsinfoModule, wcmsSystemNewsinfoRawModule);
      
       wcmsSystemNewsinfoModule.setInfoRawId(wcmsSystemNewsinfoRawModule.getInfoId());
       wcmsSystemNewsinfoModule.setInfoId( null );
      
       return this .saveOrUpdate(wcmsSystemNewsinfoModule);
      
    }
    /**
      * 需要把目标表 Model 信息保存在目标表中。如果该记录已存在,则更新,否则,新增 !
      *
      * @param module
      * @throws Exception
      */
    public String saveOrUpdate(WcmsSystemNewsinfoModule module) throws Exception{
       WcmsSystemNewsinfoModule loadModule= this .getWcmsSystemNewsinfoDao().selectWcmsSystemNewsinfoModuleByInfoRawId(module.getInfoRawId());
       String infoId= null ;
       if (loadModule== null ){
           // 插入
           infoId= this .getWcmsSystemNewsinfoDao().add(module);
          
       } else {
           // 更新
           /**
             * 更新
             * 1 ,找到主键条件
             */
           infoId=loadModule.getInfoId();
           module.setInfoId(loadModule.getInfoId());
           this .getWcmsSystemNewsinfoDao().updateProcessedInfo(module);
          
       }
       return infoId;
    }
 
源代码说明:
WcmsSystemNewsinfoService类的formatRawInfoToHtml(String infoRawId)方法,根据传入的参数,准备好所需的数据,然后调用本类的saveOrUpdate(WcmsSystemNewsinfoModule module)方法。
saveOrUpdate()方法,根据情况,调用WcmsSystemNewsinfoDao类的add或者update方法。
 
Spring事务配置文件
一、事务配置抽象Bean声明
< bean id = "txProxyTemplate" abstract = "true" class = "org.springframework.transaction.interceptor.TransactionProxyFactoryBean" >
       < property name = "transactionManager" ref = "transactionManager" />
       < property name = "transactionAttributes" >
           < props >
              < prop key = "*" > readOnly </ prop >
              < prop key = "add*" > PROPAGATION_REQUIRED,-Exception </ prop >
              < prop key = "save*" > PROPAGATION_REQUIRED,-Exception </ prop >
              < prop key = "modify*" > PROPAGATION_REQUIRED,-Exception </ prop >
              < prop key = "update*" > PROPAGATION_REQUIRED,-Exception </ prop >
              < prop key = "delete*" > PROPAGATION_REQUIRED,-Exception </ prop >
              < prop key = "remove*" > PROPAGATION_REQUIRED,-Exception </ prop >
              < prop key = "query*" > PROPAGATION_REQUIRED, readOnly,-Exception </ prop >
              < prop key = "load*" > PROPAGATION_REQUIRED, -Exception </ prop >
           </ props >
       </ property >
    </ bean >
 
二、服务类的Spring声明式事务管理
<!--
    wcmsSystemNewsinfoService
     -->
    < bean id = "wcmsSystemNewsinfoService"
       parent = "txProxyTemplate" >
       < property name = "target" >
           < ref bean = "wcmsSystemNewsinfoServiceTarget" />
       </ property >
   
    </ bean >
 
错误解析
错误的原因就在上面的Spring声明式事务里。执行WcmsSystemNewsinfoService类的formatRawInfoToHtml()方法时,会应用txProxyTemplate的配置,由于它的方法名与所有的特殊配置都不匹配,因此,会应用第一个声明式事务:
    < prop key = "*" > readOnly </ prop >
因此,SpringAOP创建了一个只读的数据库连接和事务。
然后,调用WcmsSystemNewsinfoService类的saveOrUpdate()方法,也会查找上面的事务配置,匹配:
< prop key = "save*" > PROPAGATION_REQUIRED,-Exception </ prop >
SpringAOP会去得到数据库连接和设置事务。由于在本地线程变量中已经找到了前面提供的只读连接,就会直接使用这个数据库连接,并在其上设置事务。
最后,调用WcmsSystemNewsinfoDao类的add方法。由于使用的是只读连接,执行add方法中的update语句,就发生了上面的错误:
Caused by:
java.sql.SQLException : Connection is read-only. Queries leading to data modification are not allowed
 
 
Spring真的能够管理一切吗?
像上面这样的Spring事务配置和Service、Dao的写法,应当说是相当普遍的,我们认为Spring已经把数据库连接、事务、O-R映射等等管得妥妥当当了,不用我们再操心了,再理解事务了!
真的如此吗?错!
漠视数据库、漠视事务,我们的系统到底有多少类似的隐患?我们的业务服务类Service浪费了多少SPringAOP的帮助?降低了多少性能?
Spring、EJB这样的声明式事务,确实大大方便了我们处理数据库连接和事务。但是,我们还是需要自己理解业务逻辑对数据库连接,对事务的需要!
工具只能帮助我们解决我们认识到的问题,解决不了我们都没理解的问题。
 
    不能再把一切扔给框架、容器、工具!首先理解你的业务逻辑,理解你要实现的功能,然后搞清楚框架、容器、工具会帮助我们做什么。只有理解了自己的业务逻辑,理解了自己的代码,理解了自己要用到的第三方代码,才能真正完美地实现我们需要的功能!
 
用我们的命名方法来重构上面的问题代码
一、给Service 类中的2 个方法加上后缀名标识对事务的依赖
     当然,Service接口相应的方法也要改变。
/* (non-Javadoc)
     * @see com.withub.wcms.manage.collectnews.systemNewsinfo.service.IWcmsSystemNewsinfoService#formatRawInfoToHtml(java.lang.String)
     */
    public String formatRawInfoToHtmlTransaction(String infoRawId) throws Exception{
       WcmsSystemNewsinfoRawModule wcmsSystemNewsinfoRawModule= new WcmsSystemNewsinfoRawModule();
       wcmsSystemNewsinfoRawModule.setInfoId(infoRawId);
       // 原表数据
        wcmsSystemNewsinfoRawModule= this .getManageNewsinfoService().queryRawInfoById(wcmsSystemNewsinfoRawModule);
   
       WcmsSystemNewsinfoModule wcmsSystemNewsinfoModule= new WcmsSystemNewsinfoModule();
       // 目标表数据
       BeanUtils.copyProperties(wcmsSystemNewsinfoModule, wcmsSystemNewsinfoRawModule);
      
       wcmsSystemNewsinfoModule.setInfoRawId(wcmsSystemNewsinfoRawModule.getInfoId());
       wcmsSystemNewsinfoModule.setInfoId( null );
      
       return this .saveOrUpdateDao(wcmsSystemNewsinfoModule);
      
    }
    /**
      * 需要把目标表 Model 信息保存在目标表中。如果该记录已存在,则更新,否则,新增 !
      *
      * @param module
      * @throws Exception
      */
    public String saveOrUpdateDao(WcmsSystemNewsinfoModule module) throws Exception{
       WcmsSystemNewsinfoModule loadModule= this .getWcmsSystemNewsinfoDao().selectWcmsSystemNewsinfoModuleByInfoRawId(module.getInfoRawId());
       String infoId= null ;
       if (loadModule== null ){
           // 插入
           infoId= this .getWcmsSystemNewsinfoDao().add(module);
          
       } else {
           // 更新
           /**
             * 更新
             * 1 ,找到主键条件
         */
           infoId=loadModule.getInfoId();
           module.setInfoId(loadModule.getInfoId());
           this .getWcmsSystemNewsinfoDao().updateProcessedInfo(module);
          
       }
       return infoId;
    }
这样, formatRawInfoToHtmlTransaction 方法可以直接被最终用户调用。它将能够创建或得到数据库连接,管理事务,最后关闭数据库连接。
而, saveOrUpdateDao 方法,不能直接被最终用户调用。如果它需要数据库连接,它可以使用本地线程变量中保存的数据库连接。
 
二、修改Service 类的声明式事务的配置文件
<!--
    wcmsSystemNewsinfoService
     -->
    < bean id = "wcmsSystemNewsinfoService"
       parent = "txProxyTemplate" >
       < property name = "target" >
           < ref bean = "wcmsSystemNewsinfoServiceTarget" />
       </ property >
       < property name = "transactionAttributes" >
           < props >
              < prop key = "*Transaction" > PROPAGATION_REQUIRED,-Exception </ prop >
           </ props >
       </ property >
   
    </ bean >
这里,重载了 txProxyTemplate的声明式事务配置。我们只对Service类中的以Transaction结尾的方法,应用事务,在发生异常时,回滚。
这样,Transaction方法直接或者间接调用的DAO接口中的方法,就可以使用本地线程变量中保存的由Transaction方法的AOP代理方法创建的数据库连接。
数据库连接和事务被真正的摆平了!世界真美好!
 
 
 
 
 
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值