事务可实现“要么完全成功,要不全部不成功”,保证数据的完整性和一致性,使我们在开发中能方便地实现一些业务逻辑。比如,在股票交易时,除了记录交易的过程,还要更新交易完成之后的账户状态。这两个操作显然必须“要么完全成功,要么全部不成功”,否则,你的麻烦就大了。
当然,如果你不关心数据的完整性和一致性的问题,那么忘了事务吧,因为引入锁、数据库并发等机制之后,对性能还是有影响的。
下面代码中,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; } } }
这里使用了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 }
上面代码中,我们设置read-only标记为true,也就是说事务为只读,但是却要进行插入操作。上面代码执行结果如何呢?
答案是会正确执行。But why????
因为这里我们用了JDBC操作,没有用ORM,而且propagation设为SUPPORTS, 这样Spring是不会创建一个事务的,而会将事务相关工作委托给数据库的事务。而只有Spring开始了一个事务,read-only才起作用。这里没 有事务,因此,read-only就被忽略了。
再看下面的代码:
@Transactional(readOnly = true, propagation=Propagation.REQUIRED) public long insertTrade(TradeData trade) throws Exception { // JDBC CODE }
相同的代码,我们把propagation设为REQUIRED,那么这时执行结果会怎么样呢? 答案是会抛出一个read only connection exception。为什么?自己想想吧。
如果对于只读的一些查询操作使用readonly标记会怎么样?考虑下面代码。
@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而造成很难发现的问题。比如下面代码:
@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的陷井
看下面的代码:
@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去掉吧。
相关推荐
4. 并发问题:线程安全是Java并发编程的核心,理解synchronized、volatile和Atomic类的使用可以避免竞态条件和死锁。 5. 类加载器:不恰当的类加载可能导致类冲突,理解双亲委派模型有助于避免这类问题。 二、Java...
在Spring框架中,事务管理是核心特性之一,用于确保数据库操作的原子性和一致性。然而,如果在实际开发中不注意一些细节,可能...在实际开发中,细心检查和配置事务管理,避免潜在的陷阱,是保证系统稳定性的关键步骤。
### JAVA设计模式之事务处理 #### 一、引言 在企业级应用开发中,事务...通过将事务管理集中在Service层,并结合Spring框架提供的工具和技术,可以有效地提高代码的可维护性和可扩展性,同时避免一些常见的设计陷阱。
为避免这种情况,可以在需要时尽早初始化关联数据,或者使用Open Session in View(OSIV)模式。 5. **事务隔离级别**:Spring支持多种事务隔离级别,如READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、...
### NoSQL误用和常见陷阱分析 ...总的来说,在选择NoSQL数据库之前,应该对其特性和限制有足够的了解,避免掉入上述提到的各种陷阱。同时,也应该考虑现有团队的技术背景和未来的业务发展方向,做出更加合理的选择。
这两个成语提醒我们在处理事务时要避免被别人利用,同时也告诫我们要有道德底线。 误读现象在日常生活中普遍存在,主要分为几类:1) 多音字误读,如"厦门"的"厦"易误读为"shà";2) 音近字误读,如"绮丽"的"绮"易...
因此,律师事务所应该认识到广告只是营销策略的一个组成部分,而不能单纯依赖它来塑造专业形象和获取领先地位。 再者,模仿竞争对手的成功经验并不可取。每个事务所的成功都由独特的因素构成,简单复制他人的路径...
- 面试陷阱:面试官可能会问到如何避免内存泄漏或如何理解对象的生命周期。 - 解析:Java中的内存管理主要通过垃圾回收(GC)机制来实现,理解对象的可达性分析和不同的垃圾收集器如Serial、Parallel、CMS、G1等的...
合同审查确保所有签订的合同都符合法律标准,避免合同陷阱;知识产权保护则关乎企业的创新成果不被侵犯;纠纷处理机制用于解决企业内部或与外界的法律纠纷;合规审计则定期检查企业各项活动的合法性。 3. **法律...
这份48页的PDF文档“Java程序员面试陷阱”旨在揭示这些问题,帮助求职者避免在关键面试时刻掉入这些误区。本文将深入探讨其中的关键知识点。 1. **基础概念陷阱**:面试中,Java的基础知识如类、对象、封装、继承、...
在处理学生事务时,应确保所有决策过程的公开透明,避免任何形式的偏见。例如,奖学金的评定、违规行为的处理等,都需要有明确的标准和程序,让学生感受到公平对待,从而增强对学校的信任感和归属感。 此外,专业...
6. 事务的陷阱 事务管理对于保持数据库的一致性至关重要。需要注意: - 事务应尽可能短小,减少并发问题和回滚风险。 - 使用BEGIN TRANSACTION、COMMIT和ROLLBACK语句来明确事务边界。 - 了解并避免长时间锁定,...
本文将深入探讨ORM框架的原理、使用方法及避免常见陷阱的策略。 首先,ORM框架的核心原理是将JavaBean类的属性与数据库表的字段建立映射关系。通过读取配置文件,ORM框架能够自动关联JavaBean的属性和数据库字段。...
然而,这种方式容易引入编码陷阱,如事务环境问题,即在事务边界内调用其他事务边界内的方法,可能会导致事务嵌套或事务泄漏等问题。 ### 声明式事务模型 相比之下,声明式事务管理通过配置元数据(如注解或XML...
C++中的容器是标准模板库(STL)...在实际开发中,应根据具体需求和性能要求选择合适的容器,并确保正确处理元素拷贝和内存管理,避免潜在的陷阱和资源泄露。通过熟练掌握这些经验,可以编写出更加高效和可靠的C++代码。
它通过提供一个高级抽象层来减少编码工作量,并帮助开发者避免处理JDBC API中常见的陷阱,如资源管理、异常处理等。Spring框架为不同的数据访问技术提供了多种类似的模板类: - **JdbcTemplate**:适用于关系型...
例如,在创建新对象时,应该使用非持久态对象,而在更新已存在的对象时,则应使用持久态对象。混用不同状态的对象可能会导致数据不一致或异常行为。 #### 正确使用CRUD操作 创建、读取、更新和删除(CRUD)是...