spring Transactional

常常用到老搞混,从网上摘了点记录下来。java

// 业务方法须要在一个事物中运行,若是方法运行时,已经存在一个事物中,spring

// 那么加入该事物,不然为本身建立一个新事物。sql

@Transactional(propagation = Propagation.REQUIRED)数据库

public void test1() {缓存

}服务器

// 声明方法不须要事务,若是方法没有关联到一个事务,容器不会为它开启事物。session

// 若是方法在一个事物中被调用,该事物会被挂起,在方法调用结束后,原先的并发

// 事物便会恢复执行。app

@Transactional(propagation = Propagation.NOT_SUPPORTED)框架

public void test2() {

}

// 代表无论是否存在事物,业务方法总会为本身发起一个新事物。

// 若是方法已经运行在一个事物中,则原有事物会被挂起,

// 新的事物会被建立,直到方法执行结束,新事物才算结束,

// 原先的事务才会被恢复执行。

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void test3() {

}

// 该属性指定业务方法只能在一个已经存在的事物中执行,

// 业务方法不能发起本身的事物,若是业务方法在没有事物的环境

// 下调用,容器就会抛出异常。

@Transactional(propagation = Propagation.MANDATORY)

public void test4() {

}

// 这个事物属性代表,若是业务方法在某个事务范围内被调用,则方法成为该事物的一部分,

// 若是业务方法在事务范围外被调用,则方法在没有事物的环境下执行。

@Transactional(propagation = Propagation.SUPPORTS)

public void test5() {

}

// 指定业务方法绝对不能在事物范围内执行。若是业务方法在某个事物中执行,

// 容器会抛出异常,只有业务方法没有关联到任何事物,才能正常执行。

@Transactional(propagation = Propagation.NEVER)

public void test6() {

}

// 若是一个活动的事物存在,则运行在一个嵌套的事物中,若是没有活动事物,

// 则按REQUIRED属性执行,它使用了一个单独的事物,这个事物拥有多个回滚的保存点,

// 内部事务的回滚不会对外事物形成影响,它只对DataSourceTransactionManager

// 事务管理器起效。

@Transactional(propagation = Propagation.NESTED)

public void test7() {

}

@Transactional(isolation = Isolation.DEFAULT)

public void test8() {

}

// 读已提交数据(会出现不可重复读和幻读)

@Transactional(isolation = Isolation.READ_COMMITTED)

public void test9() {

}

// 读未提交数据(会出现脏读、不可重复读和幻读)

@Transactional(isolation = Isolation.READ_UNCOMMITTED)

public void test10() {

}

// 可重复读(会出现幻读)

@Transactional(isolation = Isolation.REPEATABLE_READ)

public void test11() {

}

// 串行化

@Transactional(isolation = Isolation.SERIALIZABLE)

public void test12() {

}

// 抛出Exception异常时,记录回滚

@Transactional(rollbackFor = Exception.class)

public void test13() throws Exception {

}

// 抛出Exception异常时,记录不回滚

@Transactional(noRollbackFor = Exception.class)

public void test14() throws Exception {

}

脏读:一个事物读取到另外一个事物未提交的更新数据。

不可重复读:在同一事物中,屡次读取同一数据返回的结果有所不一样。换句话说就是,后续读取能够读到另外一事务已提交的更新数据。

可重复读:在同一事物中屡次读取数据时,可以保证所读数据同样,也就是,后续读取不能读到另外一事务已提交的更新数据。

幻读:一个事务读取到另外一事务提交的insert数据。

 

Property Type Description
value String Optional qualifier specifying the transaction manager to be used.
propagation enum: Propagation Optional propagation setting.
isolation enum: Isolation Optional isolation level.
readOnly boolean Read/write vs. read-only transaction
timeout int (in seconds granularity) Transaction timeout.
rollbackFor Array of Class objects, which must be derived from Throwable. Optional array of exception classes thatmust cause rollback.
rollbackForClassname Array of class names. Classes must be derived from Throwable. Optional array of names of exception classes that must cause rollback.
noRollbackFor Array of Class objects, which must be derived from Throwable. Optional array of exception classes thatmust not cause rollback.
noRollbackForClassname Array of String class names, which must be derived from Throwable. Optional array of names of exception classes that must not cause rollback.

 

在service类前加上@Transactional,声明这个service全部方法须要事务管理。每个业务方法开始时都会打开一个事务。

Spring默认状况下会对运行期例外(RunTimeException)进行事务回滚。这个例外是unchecked

若是遇到checked意外就不回滚。

如何改变默认规则:

1 让checked例外也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)

2 让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

3 不须要事务管理的(只查询的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)

 

注意: 若是异常被try{}catch{}了,事务就不回滚了,若是想让事务回滚必须再往外抛try{}catch{throw Exception}。

spring——@Transactional事务无论理jdbc,因此要本身把jdbc事务回滚。

@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true),这样就作成一个只读事务,能够提升效率。

       各类属性的意义:

       REQUIRED:业务方法须要在一个容器里运行。若是方法运行时,已经处在一个事务中,那么加入到这个事务,不然本身新建一个新的事务。

       NOT_SUPPORTED:声明方法不须要事务。若是方法没有关联到一个事务,容器不会为他开启事务,若是方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。

       REQUIRESNEW:无论是否存在事务,该方法总汇为本身发起一个新的事务。若是方法已经运行在一个事务中,则原有事务挂起,新的事务被建立。

       MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起本身的事务。若是在没有事务的环境下被调用,容器抛出例外。

       SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。若是方法在该事务范围外被调用,该方法就在没有事务的环境下执行。

       NEVER:该方法绝对不能在事务范围内执行。若是在就抛例外。只有该方法没有关联到任何事务,才正常执行。

       NESTED:若是一个活动的事务存在,则运行在一个嵌套的事务中。若是没有活动事务,则按REQUIRED属性执行。它使用了一个 单独的事务,这个事务 拥有多个能够回滚的保存点。内部事务的回滚不会对外部事务形成影响。它只对 DataSourceTransactionManager事务管理器起效。

事务陷阱-1

 

清单 1. 使用 JDBC 的简单数据库插入

view plaincopy to clipboardprint?

@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.getConnection();   

      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_GENERATED_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();   

      }   

   }   

@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.getConnection();

      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_GENERATED_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 中,这是一个不错的单个数据库维护操做。可是若是须要在向数据库插入交易订单的同时更新账户余款呢?如清单 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 Exception {

   try {

      insertTrade(trade);

      updateAcct(trade);

      return trade;

   } catch (Exception up) {

      //log the error

      throw up;

   }

}

在本例中,insertTrade() 和 updateAcct() 方法使用不带事务的标准 JDBC 代码。insertTrade() 方 法结束后,数据库保存(并提交了)交易订单。若是 updateAcct() 方法因为任意缘由失败,交易订单仍然会在 placeTrade() 方法 结束时保存在 TRADE 表内,这会致使数据库出现不一致的数据。若是 placeTrade() 方法使用了事务,这两个活动都会包含在一 个 LUW 中,若是账户更新失败,交易订单就会回滚。

 

 

事务陷阱-2

 

随着 Java 持久性框架的不断普及,如 Hibernate、TopLink 和 Java 持久 性 API(Java Persistence API,JPA),咱们不多再会去编写简单的 JDBC 代码。更常见的状况是,咱们使用更新的对象关系 映射(ORM)框架来减轻工做,即用几个简单的方法调用替换全部麻烦的 JDBC 代码。例如,要插入 清单 1 中 JDBC 代码示例的交易订单,使 用带有 JPA 的 Spring Framework,就能够将 TradeData 对象映射到 TRADE 表,并用清单 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 {

    @PersistenceContext(unitName="trading") EntityManager em;

    public long insertTrade(TradeData trade) throws Exception {

       em.persist(trade);

       return trade.getTradeId();

    }

}

注意,清单 3 在 EntityManager 上调用了 persist() 方法来插入交易订单。很简单,是吧?其实否则。这段代码不会像预 期那样向 TRADE 表插入交易订单,也不会抛出异常。它只是返回一个值 0 做为交易订单的键,而不会更改数据库。这是事务处理的主要陷阱之一:基 于 ORM 的框架须要一个事务来触发对象缓存与数据库之间的同步。这经过一个事务提交完成,其中会生成 SQL 代码,数据库会执行须要的操做(即插 入、更新、删除)。没有事务,就不会触发 ORM 去生成 SQL 代码和保存更改,所以只会终止方法 — 没有异常,没有更新。若是使用基 于 ORM 的框架,就必须利用事务。您再也不依赖数据库来管理链接和提交工做。

这些简单的示例应该清楚地说明,为了维护数据完整性和一致性,必须使用事务。不过对于在 Java 平台中实现事务的复杂性和陷阱而言,这些示例只是涉及了冰山一角。

 

 

 

Spring Framework @Transactional 注释陷阱-3

 

 

清单 4. 使用 @Transactional 注释

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="trading") EntityManager em;

   @Transactional

   public long insertTrade(TradeData trade) throws Exception {

      em.persist(trade);

      return trade.getTradeId();

   }

}

如今从新测试代码,您发现上述方法仍然不能工做。问题在于您必须告诉 Spring Framework,您正在对事务管理应用注释。除非您进行充 分的单元测试,不然有时候很难发现这个陷阱。这一般只会致使开发人员在 Spring 配置文件中简单地添加事务逻辑,而不会使用注释。

要在 Spring 中使用 @Transactional 注释,必须在 Spring 配置文件中添加如下代码行:

view plaincopy to clipboardprint?

<tx:annotation-driven transaction-manager="transactionManager"/> 

<tx:annotation-driven transaction-manager="transactionManager"/>

transaction-manager 属性保存一个对在 Spring 配置文件中定义的事务管理器 bean 的引用。这段代码告 诉 Spring 在应用事务拦截器时使用 @Transaction 注释。若是没有它,就会忽略 @Transactional 注释,致使代码不会 使用任何事务。

让基本的 @Transactional 注释在 清单 4 的代码中工做仅仅是开始。注意,清单 4 使用 @Transactional 注释 时没有指定任何额外的注释参数。我发现许多开发人员在使用 @Transactional 注释时并无花时间理解它的做用。例如,像我同样在清 单 4 中单独使用 @Transactional 注释时,事务传播模式被设置成什么呢?只读标志被设置成什么呢?事务隔离级别的设置是怎样的?更重要 的是,事务应什么时候回滚工做?理解如何使用这个注释对于 确保在应用程序中得到合适的事务支持级别很是重要。回答我刚才提出的问题:在单独使用不带任何参数 的 @Transactional 注释时,传播模式要设置为 REQUIRED,只读标志设置为 false,事务隔离级别设置 为 READ_COMMITTED,并且事务不会针对受控异常(checked exception)回滚。

@Transactional 只读标志陷阱

我在工做中常常碰到的一个常见陷阱是 Spring @Transactional 注释中的只读标志没有获得恰当使用。这里有一个快速测试方法: 在使用标准 JDBC 代码得到 Java 持久性时,若是只读标志设置为 true,传播模式设置为 SUPPORTS,清单 5 中 的 @Transactional 注释的做用是什么呢?

清单 5. 将只读标志与 SUPPORTS 传播模式结合使用 — JDBC

view plaincopy to clipboardprint?

@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...

}

当执行清单 5 中的 insertTrade() 方法时,猜一猜会获得下面哪种结果:

抛出一个只读链接异常 

正确插入交易订单并提交数据 

什么也不作,由于传播级别被设置为 SUPPORTS 

是哪个呢?正确答案是 B。交易订单会被正确地插入到数据库中,即便只读标志被设置为 true,且事务传播模式被设置为 SUPPORTS。但 这是如何作到的呢?因为传播模式被设置为 SUPPORTS,因此不会启动任何事物,所以该方法有效地利用了一个本地(数据库)事务。只读标志只在事务启 动时应用。在本例中,由于没有启动任何事 务,因此只读标志被忽略。

 

 

Spring Framework @Transactional 注释陷阱-4

 

清单 6 中的 @Transactional 注释在设置了只读标志且传播模式被设置为 REQUIRED 时,它的做用是什么呢?

清单 6. 将只读标志与 REQUIRED 传播模式结合使用 — JDBC

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...

}

执行清单 6 中的 insertTrade() 方法会获得下面哪种结果呢:

抛出一个只读链接异常 

正确插入交易订单并提交数据 

什么也不作,由于只读标志被设置为 true 

根据前面的解释,这个问题应该很好回答。正确的答案是 A。会抛出一个异常,表示您正在试图对一个只读链接执行更新。由于启动了一个事务(REQUIRED),因此链接被设置为只读。毫无疑问,在试图执行 SQL 语句时,您会获得一个异常,告诉您该链接是一个只读链接。

关于只读标志很奇怪的一点是:要使用它,必须启动一个事务。若是只是读取数据,须要事务吗?答案是根本不须要。启动一个事务来执行只读操做会增长 处 理线程的开销,并会致使数据库发生共享读取锁定(具体取决于使用的数据库类型和设置的隔离级别)。总的来讲,在获取基于 JDBC 的 Java 持 久性时,使用只读标志有点毫无心义,并会启动没必要要的事务而增长额外的开销。

使用基于 ORM 的框架会怎样呢?按照上面的测试,若是在结合使用 JPA 和 Hibernate 时调用 insertTrade() 方法,清单 7 中的 @Transactional 注释会获得什么结果?

清单 7. 将只读标志与 REQUIRED 传播模式结合使用 — JPA

view plaincopy to clipboardprint?

@Transactional(readOnly = true, propagation=Propagation.REQUIRED)   

public long insertTrade(TradeData trade) throws Exception {   

   em.persist(trade);   

   return trade.getTradeId();   

@Transactional(readOnly = true, propagation=Propagation.REQUIRED)

public long insertTrade(TradeData trade) throws Exception {

   em.persist(trade);

   return trade.getTradeId();

}

清单 7 中的 insertTrade() 方法会获得下面哪种结果:

抛出一个只读链接异常 

正确插入交易订单并提交数据 

什么也不作,由于 readOnly 标志被设置为 true 

正确的答案是 B。交易订单会被准确无误地插入数据库中。请注意,上一示例代表,在使用 REQUIRED 传播模式时,会抛出一个只读链接异常。 使用 JDBC 时是这样。使用基于 ORM 的框架时,只读标志只是对数据库的一个提示,而且一条基于 ORM 框架的指令(本例中 是 Hibernate)将对象缓存的 flush 模式设置为 NEVER,表示在这个工做单元中,该对象缓存不该与数据库同步。不 过,REQUIRED 传播模式会覆盖全部这些内容,容许事务启动并工做,就好像没有设置只读标志同样。

这令我想到了另外一个我常常碰到的主要陷阱。阅读了前面的全部内容后,您认为若是只对 @Transactional 注释设置只读标志,清单 8 中的代码会获得什么结果呢?

清单 8. 使用只读标志 — JPA

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);

}

清单 8 中的 getTrade() 方法会执行如下哪种操做?

启动一个事务,获取交易订单,而后提交事务 

获取交易订单,但不启动事务 

正确的答案是 A。一个事务会被启动并提交。不要忘了,@Transactional 注释的默认传播模式是 REQUIRED。这意味着事务会在 没必要要的状况下启动。根据使用的数据库,这会引发没必要要的共享锁,可能会使数据库中出现死锁的状况。此外,启动和中止 事务将消耗没必要要的处理时间和资 源。总的来讲,在使用基于 ORM 的框架时,只读标志基本上毫无用处,在大多数状况下会被忽略。但若是您坚持使用它,请记得将传播模式设置 为 SUPPORTS(如清单 9 所示),这样就不会启动事务:

清单 9. 使用只读标志和 SUPPORTS 传播模式进行选择操做

view plaincopy to clipboardprint?

@Transactional(readOnly = true, propagation=Propagation.SUPPORTS)   

public TradeData getTrade(long tradeId) throws Exception {   

   return em.find(TradeData.class, tradeId);   

@Transactional(readOnly = true, propagation=Propagation.SUPPORTS)

public TradeData getTrade(long tradeId) throws Exception {

   return em.find(TradeData.class, tradeId);

}

另外,在执行读取操做时,避免使用 @Transactional 注释,如清单 10 所示:

清单 10. 删除 @Transactional 注释进行选择操做

view plaincopy to clipboardprint?

public TradeData getTrade(long tradeId) throws Exception {   

   return em.find(TradeData.class, tradeId);   

public TradeData getTrade(long tradeId) throws Exception {

   return em.find(TradeData.class, tradeId);

}

REQUIRES_NEW 事务属性陷阱

无论是使用 Spring Framework,仍是使用 EJB,使用 REQUIRES_NEW 事务属性都会获得很差的结果并致使数据损坏和 不一致。REQUIRES_NEW 事务属性老是会在启动方法时启动一个新的事务。许多开发人员都错误地使用 REQUIRES_NEW 属性,认为它是 确保事务启动的正确方法。

 

 

 

 

 

Spring Framework @Transactional 注释陷阱-5

 

 

清单 11. 使用 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 {...}

@Transactional(propagation=Propagation.REQUIRES_NEW)

public void updateAcct(TradeData trade) throws Exception {...}

注意,清单 11 中的两个方法都是公共方法,这意味着它们能够单独调用。当使用 REQUIRES_NEW 属性的几个方法经过服务间通讯或编排 在同一逻辑工做单元内调用时,该属性就会出现问题。例如,假设在清单 11 中,您能够独立于一些用例中的任何其余方法来调 用 updateAcct() 方法,但也有在 insertTrade() 方法中调用 updateAcct() 方法的状况。如今若是调 用 updateAcct() 方法后抛出异常,交易订单就会回滚,但账户更新将会提交给数据库,如清单 12 所示:

清单 12. 使用 REQUIRES_NEW 事务属性的屡次更新

view plaincopy to clipboardprint?

@Transactional(propagation=Propagation.REQUIRES_NEW)   

public long insertTrade(TradeData trade) throws Exception {   

   em.persist(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 {

   em.persist(trade);

   updateAcct(trade);

   //exception occurs here! Trade rolled back but account update is not!

   ...

}

之因此会发生这种状况是由于 updateAcct() 方法中启动了一个新事务,因此在 updateAcct() 方法结束后,事务将被提交。 使用 REQUIRES_NEW 事务属性时,若是存在现有事务上下文,当前的事务会被挂起并启动一个新事务。方法结束后,新的事务被提交,原来的事务继 续执行。

因为这种行为,只有在被调用方法中的数据库操做须要保存到数据库中,而无论覆盖事务的结果如什么时候,才应该使用 REQUIRES_NEW 事务属 性。好比,假设尝试的全部股票交易都必须被记录在一个审计数据库中。出于验证错误、资金不足或其余缘由,无论交易是否失败,这条信息都须要被持久 化。如 果没有对审计方法使用 REQUIRES_NEW 属性,审计记录就会连同尝试执行的交易一块儿回滚。使用 REQUIRES_NEW 属性能够确保无论初 始事务的结果如何,审计数据都会被保存。这里要注意的一点是,要始终使用 MANDATORY 或 REQUIRED 属性,而不 是 REQUIRES_NEW,除非您有足够的理由来使用它,相似审计示例中的那些理由。

事务回滚陷阱

我将最多见的事务陷阱留到最后来说。遗憾的是,我在生产代码中屡次遇到这个错误。我首先从 Spring Framework 开始,而后介绍 EJB 3。

到目前为止,您研究的代码相似清单 13 所示:

清单 13. 没有回滚支持

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;

   }

}

假设账户中没有足够的资金来购买须要的股票,或者尚未准备购买或出售股票,并抛出了一个受控异常(例 如 FundsNotAvailableException),那么交易订单会保存在数据库中吗?仍是整个逻辑工做单元将执行回滚?答案出乎意料:根据受 控异 常(无论是在 Spring Framework 中仍是在 EJB 中),事务会提交它还未提交的全部工做。使用清单 13,这意味着,若是在执 行 updateAcct() 方法期间抛出受控异常,就会保存交易订单,但不会更新账户来反映交易状况。

这多是在使用事务时出现的主要数据完整性和一致性问题了。运行时异常(即非受控异常)自动强制执行整个逻辑工做单元的回滚,但受控异常不会。所以,清单 13 中的代码从事务角度来讲毫无用处;尽管看上去它使用事务来维护原子性和一致性,但事实上并无。

尽管这种行为看起来很奇怪,但这样作自有它的道理。首先,不是全部受控异常都是很差的;它们可用于事件通知或根据某些条件重定向处理。但更重要 的 是,应用程序代码会对某些类型的受控异常采起纠正操做,从而使事务所有完成。例如,考虑下面一种场景:您正在为在线书籍零售商编写代码。要完成图书的 订 单,您须要将电子邮件形式的确认函做为订单处理的一部分发送。若是电子邮件服务器关闭,您将发送某种形式的 SMTP 受控异常,表示邮件没法发送。 若是受控异常引发自动回滚,整个图书订单就会因为电子邮件服务器的关闭所有回滚。经过禁止自动回滚受控异常,您能够捕获该异 常并执行某种纠正操做(如向 挂起队列发送消息),而后提交剩余的订单。

 

 

 

 

 

Spring Framework @Transactional 注释陷阱-6

 

使用 Declarative 事务模式时,必须指定容器或框架应该如何处理受控异常。在 Spring Framework 中,经过 @Transactional 注释中的 rollbackFor 参数进行指定,如清单 14 所示:

清单 14. 添加事务回滚支持 — Spring

view plaincopy to clipboardprint?

@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)   

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, rollbackFor=Exception.class)

public TradeData placeTrade(TradeData trade) throws Exception {

   try {

      insertTrade(trade);

      updateAcct(trade);

      return trade;

   } catch (Exception up) {

      //log the error

      throw up;

   }

}

注意,@Transactional 注释中使用了 rollbackFor 参数。这个参数接受一个单一异常类或一组异常类,您也可使 用 rollbackForClassName 参数将异常的名称指定为 Java String 类型。还可使用此属性的相反形式 (noRollbackFor)指定除某些异常之外的全部异常应该强制回滚。一般大多数开发人员指定 Exception.class 做为值,表示该方 法中的全部异常应该强制回滚。

在回滚事务这一点上,EJB 的工做方式与 Spring Framework 稍微有点不一样。EJB 3.0 规范中 的 @TransactionAttribute 注释不包含指定回滚行为的指令。必须使 用 SessionContext.setRollbackOnly() 方法将事务标记为执行回滚,如清单 15 所示:

清单 15. 添加事务回滚支持 — EJB

view plaincopy to clipboardprint?

@TransactionAttribute(TransactionAttributeType.REQUIRED)   

public TradeData placeTrade(TradeData trade) throws Exception {   

   try {   

      insertTrade(trade);   

      updateAcct(trade);   

      return trade;   

   } catch (Exception up) {   

      //log the error   

      sessionCtx.setRollbackOnly();   

      throw up;   

   }   

@TransactionAttribute(TransactionAttributeType.REQUIRED)

public TradeData placeTrade(TradeData trade) throws Exception {

   try {

      insertTrade(trade);

      updateAcct(trade);

      return trade;

   } catch (Exception up) {

      //log the error

      sessionCtx.setRollbackOnly();

      throw up;

   }

}

调用 setRollbackOnly() 方法后,就不能改变主意了;唯一可能的结果是在启动事务的方法完成后回滚事务。本系列后续文章中描述的事务策略将介绍什么时候、何处使用回滚指令,以及什么时候使用 REQUIRED 与 MANDATORY 事务属性。

 

 

 

 

Isolation Level(事务隔离等级)

 

一、Serializable:最严格的级别,事务串行执行,资源消耗最大;

二、REPEATABLE READ:保证了一个事务不会修改已经由另外一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的状况,可是带来了更多的性能损失。

三、READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另外一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。

四、Read Uncommitted:保证了读取过程当中不会读取到非法数据。隔离级别在于处理多事务的并发问题。

咱们知道并行能够提升数据库的吞吐量和效率,可是并非全部的并发事务均可以并发运行。

咱们首先说并发中可能发生的3中不讨人喜欢的事情

1: Dirty reads--读脏数据。也就是说,好比事务A的未提交(还依然缓存)的数据被事务B读走,若是事务A失败回滚,会致使事务B所读取的的数据是错误的。

2: non-repeatable reads--数据不可重复读。好比事务A中两处读取数据-total-的值。在第一读的时候,total是 100,而后事务B就把total的数据改为 200,事务A再读一次,结果就发现,total居然就变成200了,形成事务A数据混乱。

3: phantom reads--幻象读数据,这个和non-repeatable reads类似,也是同一个事务中屡次读不一致的问题。但 是non-repeatable reads的不一致是由于他所要取的数据集被改变了(好比total的数据),可是phantom reads所要读的 数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。好比 Select account.id where account.name="ppgogo*",第一次读去了6个符合条件的id,第二次读取的时候, 因为事务b把一个账号的名字由"dd"改 成"ppgogo1",结果取出来了7个数据。

  Dirty reads non-repeatable reads phantom reads

Serializable 不会 不会 不会

REPEATABLE READ 不会 不会 会

READ COMMITTED 不会 会 会

Read Uncommitted 会 会 会

readOnly

相关文章
相关标签/搜索