`
唯快不破
  • 浏览: 83002 次
  • 性别: Icon_minigender_1
  • 来自: 南宁
社区版块
存档分类
最新评论

使用事务时应该避免的陷井

阅读更多

事务可实现“要么完全成功,要不全部不成功”,保证数据的完整性和一致性,使我们在开发中能方便地实现一些业务逻辑。比如,在股票交易时,除了记录交易的过程,还要更新交易完成之后的账户状态。这两个操作显然必须“要么完全成功,要么全部不成功”,否则,你的麻烦就大了。

当然,如果你不关心数据的完整性和一致性的问题,那么忘了事务吧,因为引入锁、数据库并发等机制之后,对性能还是有影响的。

下面代码中,placeTrade是一个完整的业务逻辑单元LUW(Logical units of work),实现记录交易并更新账户的操作。
public class TradingServiceImpl {
    @PersistenceContext(unitName="trading") EntityManager em;

    public long insertTrade(TradeData trade) throws Exception {
       em.persist(trade);
       return trade.getTradeId();
    }
    public TradeData placeTrade(TradeData trade) throws Exception {
       try {
          insertTrade(trade);
          updateAcct(trade);
          return trade;
       } catch (Exception up) {
          //log the error
          throw up;
       }
    } 
}

  使用事务时应该避免的陷井 收藏
事务可实现“要么完全成功,要不全部不成功”,保证数据的完整性和一致性,使我们在开发中能方便地实现一些业务逻辑。比如,在股票交易时,除了记录交易的过程,还要更新交易完成之后的账户状态。这两个操作显然必须“要么完全成功,要么全部不成功”,否则,你的麻烦就大了。

当然,如果你不关心数据的完整性和一致性的问题,那么忘了事务吧,因为引入锁、数据库并发等机制之后,对性能还是有影响的。

下面代码中,placeTrade是一个完整的业务逻辑单元LUW(Logical units of work),实现记录交易并更新账户的操作。

view plaincopy to clipboardprint?
public class TradingServiceImpl {  
    @PersistenceContext(unitName="trading") EntityManager em;  
 
    public long insertTrade(TradeData trade) throws Exception {  
       em.persist(trade);  
       return trade.getTradeId();  
    }  
    public TradeData placeTrade(TradeData trade) throws Exception {  
       try {  
          insertTrade(trade);  
          updateAcct(trade);  
          return trade;  
       } catch (Exception up) {  
          //log the error  
          throw up;  
       }  
    }   

public class TradingServiceImpl {
    @PersistenceContext(unitName="trading") EntityManager em;

    public long insertTrade(TradeData trade) throws Exception {
       em.persist(trade);
       return trade.getTradeId();
    }
    public TradeData placeTrade(TradeData trade) throws Exception {
       try {
          insertTrade(trade);
          updateAcct(trade);
          return trade;
       } catch (Exception up) {
          //log the error
          throw up;
       }
    }
}

这里使用了JPA,没有使用事务。

上面的代码没有问题,但是在执行的时候,却会发现insertTrade并没有返回预期的交易编号,而是返回0,并且没有任何异常。这就是这个文章提到的第一个陷井:ORM框架需要使用事务来同步对象缓存和数据库。上面的代码没有使用事务,而且也没有显式地调用Flush,因此,在insert操作之后,数据并没有被保存到数据库中,因此,后面的更新操作也就不可能正确。

好,我们使用Spring来管理事务,通过Annotation使上面的代码具有事务的能力。

public class TradingServiceImpl {
   @PersistenceContext(unitName="trading") EntityManager em;
   @Transactional
   public long insertTrade(TradeData trade) throws Exception {
      em.persist(trade);
      return trade.getTradeId();
   }
}

在加上@TRansactional之后,你会发现,事务还是没有被引入。这就是第二个陷井:使用Annotation引入事务,需要在Spring配置文件中添加<tx:annotation-driven transaction-manager="transactionManager"/>.  看到了吧,你还没有告诉Spring用哪个transactionManager来管理事务,Spring怎么会知道如何处理事务呢?你以为Spring是傻瓜相机吗?

但是仅仅使用@Transactional是远远不够的,很多时候,即使你的代码中加上了@Transactional,事务还是不起作用,原因就是你没有指定事务参数。

默认的@Transactional如果没有设定参数时,其propagation模式是REQUIRED, read-only标记是false。isolation level是READ_COMMITTED,这时,如果你的代码中抛出异常,而且又被你通过try catch捕获的话,事务是不回滚的。

好,我们知道了光用Annotation是不够的,还需要加上一些参数,那么,你已经很了解事务了吗?我们再做下面的测试:

@Transactional(readOnly = true, propagation=Propagation.SUPPORTS)  
public long insertTrade(TradeData trade) throws Exception {  
   // JDBC CODE  

   @Transactional(readOnly = true, propagation=Propagation.SUPPORTS)
   public long insertTrade(TradeData trade) throws Exception {
      // JDBC CODE
   }

上面代码中,我们设置read-only标记为true,也就是说事务为只读,但是却要进行插入操作。上面代码执行结果如何呢?

答案是会正确执行。But why????

因为这里我们用了JDBC操作,没有用ORM,而且propagation设为SUPPORTS, 这样Spring是不会创建一个事务的,而会将事务相关工作委托给数据库的事务。而只有Spring开始了一个事务,read-only才起作用。这里没有事务,因此,read-only就被忽略了。

再看下面的代码:

view plaincopy to clipboardprint?
@Transactional(readOnly = true, propagation=Propagation.REQUIRED)  
public long insertTrade(TradeData trade) throws Exception {  
   // JDBC CODE  

   @Transactional(readOnly = true, propagation=Propagation.REQUIRED)
   public long insertTrade(TradeData trade) throws Exception {
      // JDBC CODE
   }

相同的代码,我们把propagation设为REQUIRED,那么这时执行结果会怎么样呢? 答案是会抛出一个read only connection exception。为什么?自己想想吧。

如果对于只读的一些查询操作使用readonly标记会怎么样?考虑下面代码。

view plaincopy to clipboardprint?
@Transactional(readOnly = true)  
public TradeData getTrade(long tradeId) throws Exception {  
   return em.find(TradeData.class, tradeId);  

@Transactional(readOnly = true)
public TradeData getTrade(long tradeId) throws Exception {
   return em.find(TradeData.class, tradeId);
}

问题是:getTrade的执行会在一个事务中吗? 从字面上看,好像不会,因为只有查询操作,而且read-only。不幸地是,你错了。你忘了propagation的默认设置值是REQUIRED,因此缺省状态下,上面代码也会启动一个事务,执行完后COMMIT。不需要却还是用上了事务,性能肯定受影响。所以,正确的方法是read-only的同时还要指定propagation=SUPPORTS,更好的方式是:查询操作根本不需要使用事务。

关于Propagation.REQUIRES_NEW的陷井

有时候,开发人员搞不清楚REQUIRES_NEW和REQUIRED的区别,误用了REQUIRES_NEW而造成很难发现的问题。比如下面代码:

view plaincopy to clipboardprint?
@Transactional(propagation=Propagation.REQUIRES_NEW)  
public long insertTrade(TradeData trade) throws Exception {...}  
 
@Transactional(propagation=Propagation.REQUIRES_NEW)  
public void updateAcct(TradeData trade) throws Exception {...}  
 
@Transactional(propagation=Propagation.REQUIRES_NEW)  
public long insertTrade(TradeData trade) throws Exception {  
   insertTrade(trade);  
   updateAcct(trade);  
   //exception occurs here! Trade rolled back but account update is not!  
   ...  

@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception {...}

@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updateAcct(TradeData trade) throws Exception {...}

@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception {
   insertTrade(trade);
   updateAcct(trade);
   //exception occurs here! Trade rolled back but account update is not!
   ...
}

问题是:如果updateAcct发生异常,insertTrade操作会回滚吗? 答案是:不会。Propagation.REQUIRES_NEW会挂起当前的事务,新建一个独立的事务,执行完之后,提交并返回,激活先前挂起的事务继续执行。此时发生异常回滚只能回滚第一个事务中的逻辑,而前面独立事务已经COMMITTED了,抓瞎了吧。记录了交易,却没有更新账户状态,哈哈。解决办法是:将REQUIRES_NEW改为REQUIRED。这样,如果方法发现自己已经处在一个事务中了,就不会重新启动一个事务了。

关于Roll back的陷井

看下面的代码:

view plaincopy to clipboardprint?
@Transactional(propagation=Propagation.REQUIRED)  
public TradeData placeTrade(TradeData trade) throws Exception {  
   try {  
      insertTrade(trade);  
      updateAcct(trade);  
      return trade;  
   } catch (Exception up) {  
      //log the error  
      throw up;  
   }  

@Transactional(propagation=Propagation.REQUIRED)
public TradeData placeTrade(TradeData trade) throws Exception {
   try {
      insertTrade(trade);
      updateAcct(trade);
      return trade;
   } catch (Exception up) {
      //log the error
      throw up;
   }
}

怎么样,设置了使用事务,而且REQUIRED强调必须采用事务,这回应该没问题了吧。

你又错了。

在updateAcct时,如果发现该账户根本没有足够的钱来做这个交易,应该回滚,但是,不幸的是,回滚没有发生。原因就在:你自己捕获并处理了Exception. 懊恼吧,我自己受累多写了代码,还有错了?

没错。这可能是使用事务方面最大的一个问题:运行时异常(未捕获的异常)会自动强迫整个事务逻辑单元(LUW)回滚,但是被捕获的异常则不会。

怎么样,出力不讨好了吧。赶紧把try catch去掉吧。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/smilingleo/archive/2009/02/10/3873851.aspx
分享到:
评论

相关推荐

    java陷阱常见面试题

    4. 并发问题:线程安全是Java并发编程的核心,理解synchronized、volatile和Atomic类的使用可以避免竞态条件和死锁。 5. 类加载器:不恰当的类加载可能导致类冲突,理解双亲委派模型有助于避免这类问题。 二、Java...

    Spring事务不生效.pdf

    在Spring框架中,事务管理是核心特性之一,用于确保数据库操作的原子性和一致性。然而,如果在实际开发中不注意一些细节,可能...在实际开发中,细心检查和配置事务管理,避免潜在的陷阱,是保证系统稳定性的关键步骤。

    JAVA设计模式之事务处理.pdf

    ### JAVA设计模式之事务处理 #### 一、引言 在企业级应用开发中,事务...通过将事务管理集中在Service层,并结合Spring框架提供的工具和技术,可以有效地提高代码的可维护性和可扩展性,同时避免一些常见的设计陷阱。

    无处不在的Spring AOP事务及踩过的坑

    为避免这种情况,可以在需要时尽早初始化关联数据,或者使用Open Session in View(OSIV)模式。 5. **事务隔离级别**:Spring支持多种事务隔离级别,如READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、...

    NoSQL误用和常见陷阱分析

    ### NoSQL误用和常见陷阱分析 ...总的来说,在选择NoSQL数据库之前,应该对其特性和限制有足够的了解,避免掉入上述提到的各种陷阱。同时,也应该考虑现有团队的技术背景和未来的业务发展方向,做出更加合理的选择。

    高中语文选修语言文字应用迷幻陷阱——误读和异读PPT课件.pptx

    这两个成语提醒我们在处理事务时要避免被别人利用,同时也告诫我们要有道德底线。 误读现象在日常生活中普遍存在,主要分为几类:1) 多音字误读,如"厦门"的"厦"易误读为"shà";2) 音近字误读,如"绮丽"的"绮"易...

    律师事务所如何规划市场营销?.pdf

    因此,律师事务所应该认识到广告只是营销策略的一个组成部分,而不能单纯依赖它来塑造专业形象和获取领先地位。 再者,模仿竞争对手的成功经验并不可取。每个事务所的成功都由独特的因素构成,简单复制他人的路径...

    Java程序员面试可能遭遇的个专业技术陷阱解析.pdf,这是一份不错的文件

    - 面试陷阱:面试官可能会问到如何避免内存泄漏或如何理解对象的生命周期。 - 解析:Java中的内存管理主要通过垃圾回收(GC)机制来实现,理解对象的可达性分析和不同的垃圾收集器如Serial、Parallel、CMS、G1等的...

    RF-FW-ZD-01 法律事务管理制度.zip

    合同审查确保所有签订的合同都符合法律标准,避免合同陷阱;知识产权保护则关乎企业的创新成果不被侵犯;纠纷处理机制用于解决企业内部或与外界的法律纠纷;合规审计则定期检查企业各项活动的合法性。 3. **法律...

    Java程序员面试陷阱共48页.pdf.zip

    这份48页的PDF文档“Java程序员面试陷阱”旨在揭示这些问题,帮助求职者避免在关键面试时刻掉入这些误区。本文将深入探讨其中的关键知识点。 1. **基础概念陷阱**:面试中,Java的基础知识如类、对象、封装、继承、...

    信息时代高校学生事务管理的思考——契约文化的角度.pdf

    在处理学生事务时,应确保所有决策过程的公开透明,避免任何形式的偏见。例如,奖学金的评定、违规行为的处理等,都需要有明确的标准和程序,让学生感受到公平对待,从而增强对学校的信任感和归属感。 此外,专业...

    sqlserver编程规范.doc

    6. 事务的陷阱 事务管理对于保持数据库的一致性至关重要。需要注意: - 事务应尽可能短小,减少并发问题和回滚风险。 - 使用BEGIN TRANSACTION、COMMIT和ROLLBACK语句来明确事务边界。 - 了解并避免长时间锁定,...

    高效使用JavaEE+ORM框架.pdf

    本文将深入探讨ORM框架的原理、使用方法及避免常见陷阱的策略。 首先,ORM框架的核心原理是将JavaBean类的属性与数据库表的字段建立映射关系。通过读取配置文件,ORM框架能够自动关联JavaBean的属性和数据库字段。...

    Java事物设计策略

    然而,这种方式容易引入编码陷阱,如事务环境问题,即在事务边界内调用其他事务边界内的方法,可能会导致事务嵌套或事务泄漏等问题。 ### 声明式事务模型 相比之下,声明式事务管理通过配置元数据(如注解或XML...

    c++容器使用经验总结

    C++中的容器是标准模板库(STL)...在实际开发中,应根据具体需求和性能要求选择合适的容器,并确保正确处理元素拷贝和内存管理,避免潜在的陷阱和资源泄露。通过熟练掌握这些经验,可以编写出更加高效和可靠的C++代码。

    day4-Spring JdbcTemplate & 声明式事务.md

    它通过提供一个高级抽象层来减少编码工作量,并帮助开发者避免处理JDBC API中常见的陷阱,如资源管理、异常处理等。Spring框架为不同的数据访问技术提供了多种类似的模板类: - **JdbcTemplate**:适用于关系型...

    如何高效使用JavaEE ORM框架

    例如,在创建新对象时,应该使用非持久态对象,而在更新已存在的对象时,则应使用持久态对象。混用不同状态的对象可能会导致数据不一致或异常行为。 #### 正确使用CRUD操作 创建、读取、更新和删除(CRUD)是...

Global site tag (gtag.js) - Google Analytics