事务可实现“要么完全成功,要不全部不成功”,保证数据的完整性和一致性,使我们在开发中能方便地实现一些业务逻辑。比如,在股票交易时,除了记录交易的过程,还要更新交易完成之后的账户状态。这两个操作显然必须“要么完全成功,要么全部不成功”,否则,你的麻烦就大了。
当然,如果你不关心数据的完整性和一致性的问题,那么忘了事务吧,因为引入锁、数据库并发等机制之后,对性能还是有影响的。
下面代码中,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去掉吧。
相关推荐
详细讲解了通过消息来解决分布式事务的问题
kettle使用事务的转换
本代码使用H2内存数据库演示spring事务使用,包括编程式事务,声明式事务@Transactional使用,自定义事务事务注解实现自定义事务管理器
Visual C++源代码 112 如何使用事务管理删除数据库记录Visual C++源代码 112 如何使用事务管理删除数据库记录Visual C++源代码 112 如何使用事务管理删除数据库记录Visual C++源代码 112 如何使用事务管理删除数据库...
SQLServer 存储过程 游标的使用方法 事务的使用方法
主要介绍了SQLServer存储过程中事务的使用方法,简短的代码带大家更好的学习使用SQLServer存储过程中事务,感兴趣的小伙伴们可以参考一下
在编程中,经常需要使用事务。所谓事务,就是一系列必须都成功的操作,只要有一步操作失败,所有其他的步骤也必须撤销。比如用ASP开发一个网络硬盘系统,其用户注册部分要做的事有:1、将用户信息记入数据库 .2、为...
方法被调用时自动开启事务,在事务范围内使用则使用同一个事务,否则开启新事务。 2、Propagation.REQUIRES_NEW 无论何时自身都会开启事务 3、Propagation.SUPPORTS 自身不会开启事务,在事务范围内则...
MySql示例2:未使用事务进行转账.zip MySql示例2:未使用事务进行转账.zip MySql示例2:未使用事务进行转账.zip
使用事务管理删除数据库记录 编程小实例,C++.net源代码编写
使用事务批量修改数据库,大师大师答道大大大声大声大大大大大大哒哒哒发放
使用事务实现更新多个数据表
介绍在oracle中如何使用事务,有例子,很详细
C#编程 数据库操作应用 在存储过程中使用事务(源码)(源码)C#编程 数据库操作应用 在存储过程中使用事务(源码)(源码)C#编程 数据库操作应用 在存储过程中使用事务(源码)(源码)C#编程 数据库操作应用 在存储过程中使用...
我在开发中用的事务代码,在。net中使用事务让程序更规范,更安全。
SQL Server中的事务日志无疑是SQL Server中最重要的部分之一。因为SQL SERVER利用事务日志来确保...本系列文章将会从事务日志的概念,原理,SQL Server如何使用日志来确保持久性属性等方面来谈SQL Server的事务日志.
MySql示例3:使用事务进行转账.zip MySql示例3:使用事务进行转账.zip MySql示例3:使用事务进行转账.zip
配制Spring事务和JdbcTemplate使用 配制Spring事务和JdbcTemplate使用
SAP-BASIS-日常使用事务代码.doc
分布式事务服务 (Distributed Transaction Service, DTS) 是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性。DTS 从架构上分为 dts-core 、dts-schedule、 dts-server 两部分,dts-core是一个...