Spring事务的传播行为15504.pdf
《Spring事务的传播行为15504.pdf》由会员分享,可在线阅读,更多相关《Spring事务的传播行为15504.pdf(15页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、Spring 事务的传播行为 在service类前加上Transactional,声明这个service所有方法需要事务管理。每一个业务方法开始时都会打开一个事务。Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚。这个例外是 unchecked 如果遇到 checked意外就不回滚。如何改变默认规则:1 让 checked例外也回滚:在整个方法前加上 Transactional(rollbackFor=Exception.class)2 让 unchecked例外不回滚:Transactional(notRollbackFor=RunTimeExceptio
2、n.class)3 不需要事务管理的(只查询的)方法:Transactional(propagation=Propagation.NOT_SUPPORTED)在整个方法运行前就不会开启事务 还可以加上:Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true),这样就做成一个只读事务,可以提高效率。各种属性的意义:REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器
3、不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行
4、。NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按 REQUIRED属性执行。它使用了一个单独的事务,这个事务 拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。事务陷阱-1 2010-06-10 11:13 清单 1.使用 JDBC 的简单数据库插入 view plaincopy to clipboardprint?Stateless public class TradingServiceImpl implements TradingService Resourc
5、e SessionContext ctx;Resource(mappedName=java:jdbc/tradingDS)DataSource ds;public long insertTrade(TradeData trade)throws Exception Connection dbConnection=ds.getConnection();try Statement sql=dbConnection.createStatement();String stmt=INSERT INTO TRADE(ACCT_ID,SIDE,SYMBOL,SHARES,PRICE,STATE)+VALUES
6、(+trade.getAcct()+,+trade.getAction()+,+trade.getSymbol()+,+trade.getShares()+,+trade.getPrice()+,+trade.getState()+);sql.executeUpdate(stmt,Statement.RETURN_GENERATED_KEYS);ResultSet rs=sql.getGeneratedKeys();if(rs.next()return rs.getBigDecimal(1).longValue();else throw new Exception(Trade Order In
7、sert Failed);finally if(dbConnection!=null)dbConnection.close();Stateless public class TradingServiceImpl implements TradingService Resource SessionContext ctx;Resource(mappedName=java:jdbc/tradingDS)DataSource ds;public long insertTrade(TradeData trade)throws Exception Connection dbConnection=ds.ge
8、tConnection();try Statement sql=dbConnection.createStatement();String stmt=INSERT INTO TRADE(ACCT_ID,SIDE,SYMBOL,SHARES,PRICE,STATE)+VALUES(+trade.getAcct()+,+trade.getAction()+,+trade.getSymbol()+,+trade.getShares()+,+trade.getPrice()+,+trade.getState()+);sql.executeUpdate(stmt,Statement.RETURN_GEN
9、ERATED_KEYS);ResultSet rs=sql.getGeneratedKeys();if(rs.next()return rs.getBigDecimal(1).longValue();else throw new Exception(Trade Order Insert Failed);finally if(dbConnection!=null)dbConnection.close();清单 1 中的 JDBC 代码没有包含任何事务逻辑,它只是在数据库中保存 TRADE 表中的交易订单。在本例中,数据库处理事务逻辑。在 LUW 中,这是一个不错的单个数据库维护操作。但是如果需要
10、在向数据库插入交易订单的同时更新帐户余款呢?如清单 2 所示:清单 2.在同一方法中执行多次表更新 view plaincopy to clipboardprint?public TradeData placeTrade(TradeData trade)throws Exception try insertTrade(trade);updateAcct(trade);return trade;catch(Exception up)/log the error throw up;public TradeData placeTrade(TradeData trade)throws Exceptio
11、n try insertTrade(trade);updateAcct(trade);return trade;catch(Exception up)/log the error throw up;在本例中,insertTrade()和 updateAcct()方法使用不带事务的标准 JDBC 代码。insertTrade()方法结束后,数据库保存(并提交了)交易订单。如果 updateAcct()方法由于任意原因失败,交易订单仍然会在 placeTrade()方法结束时保存在 TRADE 表内,这会导致数据库出现不一致的数据。如果 placeTrade()方法使用了事务,这两个活动都会包含在
12、一个 LUW 中,如果帐户更新失败,交易订单就会回滚。事务陷阱-2 2010-06-10 11:14 随 着 Java 持久性框架的不断普及,如 Hibernate、TopLink 和 Java 持久性 API(Java Persistence API,JPA),我们很少再会去编写简单的 JDBC 代码。更常见的情况是,我们使用更新的对象关系映射(ORM)框架来减轻工作,即用几个简单的方法调用替换所有麻烦的 JDBC 代码。例如,要插入 清单 1 中 JDBC 代码示例的交易订单,使用带有 JPA 的 Spring Framework,就可以将 TradeData 对象映射到 TRADE 表,
13、并用清单 3 中的 JPA 代码替换所有 JDBC 代码:清单 3.使用 JPA 的简单插入 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 class TradingServiceImpl Pe
14、rsistenceContext(unitName=trading)EntityManager em;public long insertTrade(TradeData trade)throws Exception em.persist(trade);return trade.getTradeId();注意,清单 3 在 EntityManager 上调用了 persist()方法来插入交易订单。很简单,是吧?其实不然。这段代码不会像预期那样向 TRADE 表插入交易订单,也不会抛出异常。它只是返回一个值 0 作为交易订单的键,而不会更改数据库。这是事务处理的主要陷阱之一:基于 ORM 的框架
15、需要一个事务来触发对象缓存与数据库之间的同步。这通过一个事务提交完成,其中会生成 SQL 代码,数据库会执行需要的操作(即插入、更新、删除)。没有事务,就不会触发 ORM 去生成 SQL 代码和保存更改,因此只会终止方法 没有异常,没有更新。如果使用基于 ORM 的框架,就必须利用事务。您不再依赖数据库来管理连接和提交工作。这些简单的示例应该清楚地说明,为了维护数据完整性和一致性,必须使用事务。不过对于在 Java 平台中实现事务的复杂性和陷阱而言,这些示例只是涉及了冰山一角。Spring Framework Transactional 注释陷阱-3 清单 4.使用 Transactional
16、 注释 view plaincopy to clipboardprint?public class TradingServiceImpl PersistenceContext(unitName=trading)EntityManager em;Transactional public long insertTrade(TradeData trade)throws Exception em.persist(trade);return trade.getTradeId();public class TradingServiceImpl PersistenceContext(unitName=tra
17、ding)EntityManager em;Transactional public long insertTrade(TradeData trade)throws Exception em.persist(trade);return trade.getTradeId();现在重新测试代码,您发现上述方法仍然不能工作。问题在于您必须告诉 Spring Framework,您正在对事务管理应用注释。除非您进行充分的单元测试,否则有时候很难发现这个陷阱。这通常只会导致开发人员在 Spring 配置文件中简单地添加事务逻辑,而不会使用注释。要在 Spring 中使用 Transactional 注释
18、,必须在 Spring 配置文件中添加以下代码行:view plaincopy to clipboardprint?transaction-manager 属性保存一个对在 Spring 配置文件中定义的事务管理器 bean 的引用。这段代码告诉 Spring 在应用事务拦截器时使用 Transaction 注释。如果没有它,就会忽略 Transactional 注释,导致代码不会使用任何事务。让基本的 Transactional 注释在 清单 4 的代码中工作仅仅是开始。注意,清单 4 使用 Transactional 注释时没有指定任何额外的注释参数。我发现许多开发人员在使用 Transa
19、ctional 注释时并没有花时间理解它的作用。例如,像我一样在清单 4 中单独使用 Transactional 注释时,事务传播模式被设置成什么呢?只读标志被设置成什么呢?事务隔离级别的设置是怎样的?更重要的是,事务应何时回滚工作?理解如何使用这个注释对于 确保在应用程序中获得合适的事务支持级别非常重要。回答我刚才提出的问题:在单独使用不带任何参数的 Transactional 注释时,传播模式要设置为 REQUIRED,只读标志设置为 false,事务隔离级别设置为 READ_COMMITTED,而且事务不会针对受控异常(checked exception)回滚。Transactional
20、 只读标志陷阱 我在工作中经常碰到的一个常见陷阱是 Spring Transactional 注释中的只读标志没有得到恰当使用。这里有一个快速测试方法:在使用标准 JDBC 代码获得 Java 持久性时,如果只读标志设置为 true,传播模式设置为 SUPPORTS,清单 5 中的 Transactional 注释的作用是什么呢?清单 5.将只读标志与 SUPPORTS 传播模式结合使用 JDBC view plaincopy to clipboardprint?Transactional(readOnly=true,propagation=Propagation.SUPPORTS)publi
21、c long insertTrade(TradeData trade)throws Exception /JDBC Code.Transactional(readOnly=true,propagation=Propagation.SUPPORTS)public long insertTrade(TradeData trade)throws Exception /JDBC Code.当执行清单 5 中的 insertTrade()方法时,猜一猜会得到下面哪一种结果:抛出一个只读连接异常 正确插入交易订单并提交数据 什么也不做,因为传播级别被设置为 SUPPORTS 是哪一个呢?正确答案是 B。交
22、易订单会被正确地插入到数据库中,即使只读标志被设置为 true,且事务传播模式被设置为 SUPPORTS。但这是如何做到的呢?由于传播模式被设置为 SUPPORTS,所以不会启动任何事物,因此该方法有效地利用了一个本地(数据库)事务。只读标志只在事务启动时应用。在本例中,因为没有启动任何事 务,所以只读标志被忽略。Spring Framework Transactional 注释陷阱-4 清单 6 中的 Transactional 注释在设置了只读标志且传播模式被设置为 REQUIRED 时,它的作用是什么呢?清单 6.将只读标志与 REQUIRED 传播模式结合使用 JDBC view pl
23、aincopy 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.执行清单 6 中的 insert
24、Trade()方法会得到下面哪一种结果呢:抛出一个只读连接异常 正确插入交易订单并提交数据 什么也不做,因为只读标志被设置为 true 根据前面的解释,这个问题应该很好回答。正确的答案是 A。会抛出一个异常,表示您正在试图对一个只读连接执行更新。因为启动了一个事务(REQUIRED),所以连接被设置为只读。毫无疑问,在试图执行 SQL 语句时,您会得到一个异常,告诉您该连接是一个只读连接。关于只读标志很奇怪的一点是:要使用它,必须启动一个事务。如果只是读取数据,需要事务吗?答案是根本不需要。启动一个事务来执行只读操作会增加处 理线程的开销,并会导致数据库发生共享读取锁定(具体取决于使用的数据库
25、类型和设置的隔离级别)。总的来说,在获取基于 JDBC 的 Java 持久性时,使用只读标志有点毫无意义,并会启动不必要的事务而增加额外的开销。使用基于 ORM 的框架会怎样呢?按照上面的测试,如果在结合使用 JPA 和 Hibernate 时调用 insertTrade()方法,清单 7 中的 Transactional 注释会得到什么结果?清单 7.将只读标志与 REQUIRED 传播模式结合使用 JPA view plaincopy to clipboardprint?Transactional(readOnly=true,propagation=Propagation.REQUIRED
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Spring 事务 传播 行为 15504
限制150内