精华内容
下载资源
问答
  • spring事务传播行为   在service类前加上@Transactional,声明这个service所有方法需要事务管理。每一个业务方法开始时都会打开一个事务。 Spring默认情况下会对运行期例外(RunTimeException)进行事务...

    spring事务的传播行为

     

    在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事务回滚。

    下面给出了回滚JDBC事务的代码示例:

    Java代码
    1. public void processT(String orders) {   
    2. Context initCtx = new InitialContext();   
    3. javax.sql.DataSource ds = javax.sql.DataSource)initCtx.lookup   
    4. (“java:comp/env/jdbc/OrdersDB”);   
    5. java.sql.Connection conn = ds.getConnection();   
    6. try {   
    7. conn.setAutoCommit( false ); //更改JDBC事务的默认提交方式   
    8. orderNo = createOrder( orders );   
    9. updateOrderStatus(orderNo, “orders created”);   
    10. conn.commit(); //提交JDBC事务   
    11. } catch ( Exception e ){   
    12. try {   
    13. conn.rollback(); //回滚sJDBC事务   
    14. throw new EJBException(“事务回滚: “ + e.getMessage());   
    15. } catch ( SQLException sqle ){   
    16. throw new EJBException(“出现SQL操作错误: “ + sqle.getMessage());   
    17. }   
    18. }   
    19. }  
    [java]  view plain copy
    1. public void processT(String orders) {  
    2. Context initCtx = new InitialContext();  
    3. javax.sql.DataSource ds = javax.sql.DataSource)initCtx.lookup  
    4. (“java:comp/env/jdbc/OrdersDB”);  
    5. java.sql.Connection conn = ds.getConnection();  
    6. try{  
    7. conn.setAutoCommit( false ); //更改JDBC事务的默认提交方式  
    8. orderNo = createOrder( orders );  
    9. updateOrderStatus(orderNo, “orders created”);  
    10. conn.commit();//提交JDBC事务  
    11. }catch( Exception e ){  
    12. try{  
    13. conn.rollback();//回滚sJDBC事务  
    14. throw new EJBException(“事务回滚: “ + e.getMessage());  
    15. }catch( SQLException sqle ){  
    16. throw new EJBException(“出现SQL操作错误: “ + sqle.getMessage());  
    17. }  
    18. }  
    19. }  

     

    下面给出了JTA事务代码示例:

    Java代码
    1. public void processOrder(String orderMessage) {   
    2. UserTransaction transaction = mySessionContext.getUserTransaction(); //获得JTA事务   
    3. try {   
    4. transaction.begin(); //开始JTA事务   
    5. orderNo = sendOrder(orderMessage);   
    6. updateOrderStatus(orderNo, “order sent”);   
    7. transaction.commit(); //提交JTA事务   
    8. } catch (Exception e){   
    9. try {   
    10. transaction.rollback(); //回滚JTA事务   
    11. } catch (SystemException se){   
    12. se.printStackTrace();   
    13. }   
    14. throw new EJBException(“事务回滚: “ + e.getMessage());   
    15. }   
    16. }  

     

     

    在整个方法运行前就不会开启事务

           还可以加上:@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(事务隔离等级)

     

    1、Serializable:最严格的级别,事务串行执行,资源消耗最大;
    2、REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
    3、READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
    4、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 readsnon-repeatable readsphantom reads
    Serializable不会不会不会
    REPEATABLE READ不会不会
    READ COMMITTED不会
    Read Uncommitted

    readOnly
    事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。
    展开全文
  • @Transactional配置—传播行为

    千次阅读 2018-10-19 11:58:12
    传播行为(Propagation):方法之间调用事务采用的策略问题。在Spring中,当一个方法调用另外一个方法是,可以让事务采取不同的策略工作,如新建事务或者挂起事务等。 我们看下Propagation的源码。其中的注释即要讲解...

    传播行为

    传播行为(Propagation):方法之间调用事务采用的策略问题。在Spring中,当一个方法调用另外一个方法是,可以让事务采取不同的策略工作,如新建事务或者挂起事务等。

    我们看下Propagation的源码。其中的注释即要讲解的内容

    package org.springframework.transaction.annotation;
    
    import org.springframework.transaction.TransactionDefinition;
    
    
    public enum Propagation {
    
    	/**
    	 * 需要事务,默认传播行为
         * 如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法
    	 */
    	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
    
    	/**
    	 * 支持事务
    	 * 如果存在事务,就沿用当前事务;如果不存在,则继续采用无事务的方式运行子方法
    	 */
    	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
    
    	/**
    	 * 必须使用事务
    	 * 如果存在事务,就沿用当前事务;如果不存在,则抛出异常
    	 */
    	MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
    
    	/**
    	 * 必须使用事务
    	 * 如果存在事务,就沿用当前事务;如果不存在,则新建一个事务运行子方法
    	 */
    	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
    
    	/**
    	 * 不支持事务
         * 当前存在事务时,将事务挂起,运行方法
    	 */
    	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
    
    	/**
    	 * 不支持事务
    	 * 如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
    	 */
    	NEVER(TransactionDefinition.PROPAGATION_NEVER),
    
    	/**
    	 * 在向前方法调用子方法时,如果子方法发生异常,只回滚子方法执行的sql,而不回滚当前方法事务
    	 */
    	NESTED(TransactionDefinition.PROPAGATION_NESTED);
    
    
    	private final int value;
    
    
    	Propagation(int value) { this.value = value; }
    
    	public int value() { return this.value; }
    
    }
    

     

    展开全文
  • 基于Spring AOP的事务管理,即声明式事务管理,默认是针对RuntimeException回滚,既默认只对RuntimeException()及其子类进行事务回滚;非运行时类型的异常默认是不会回滚的(new Exception())。 2,异常失效 1,多...

    1,回滚异常

    基于Spring AOP的事务管理,即声明式事务管理,默认是针对RuntimeException回滚,既默认只对RuntimeException()及其子类进行事务回滚;非运行时类型的异常默认是不会回滚的(new Exception())。

    可以指定@Transactional(rollbackFor = Exception.class)

    2,异常失效

    1,多线程当中(一个事务操作内不能有多线程操作。会导致事务失效,且异常捕获不到)。

    2,异常被自己捕获

    3,在自己的回滚异常之外

    4,数据库引擎不支持事务

    5,注解所在类未被加载成bean被spring管理

    6,方法未被public修饰

    7,自身调用(调该类自己的方法,而没有经过 Spring 的代理类)

    3,spring默认的传播行为 propagation = Propagation.REQUIRED

    业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务。

    a,单独一个方法,方法A会在事务中运行

    @Transactional(propagation = Propagation.REQUIRED)
    // 等同于@Transactional()
    public void methodA() {
        // TODO: do something   
    }

    b, 一个方法调用另一个方法,方法B也会加入到方法A中的事务来(自身调用也有效),方法B异常也会导致A回滚。

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // TODO: do something
        methodB();
    }
    
    public void methodB() {
        // TODO: do something
    }

    c, 一个方法调用另一个方法,两个方法都开启事务,方法B会加入到A中,共用一个事务。就算B出现异常在A中被捕获也会导致整个事务回滚。(前提条件:非自身调用,methodB为其他类的方法)。如果为自身调用,B方法出现异常也会导致A回滚,但是如果此时捕获了B的异常的话A,B都不会回滚(自身调用可以理解为代码合并为一个方法,try,catch方法B就相当于在同一个方法内部进行处理了,对外时无感知的 

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // TODO: do something
        try {
            methodB();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodB() {
        // TODO: do something
    }

    }

    d,自身调用情况下,方法A没开启事务,方法B开启事务,这时B是无效的。(非自身调用则是有效的)

    public void methodA() {
            methodB();
        }
    
        @Transactional(propagation = Propagation.REQUIRED)
        public void methodB() {
             // TODO: do something
            throw new RuntimeException();
        }
    展开全文
  • 一 @Transactional的... 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) rollbackFor 该属性用于...
  • @Transactional注解中事务传播行为总结 在事务的注解@Transactional中的propagation字段定义了事务传播行为事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中...
  • @Transactional 详解

    万次阅读 多人点赞 2018-11-23 16:47:14
    @Transactional 是声明式事务管理 编程中使用的注解 1 .添加位置 1)接口实现类或接口实现方法上,而不是接口类中。 2)访问权限:public 的方法才起作用。@Transactional 注解应该只被应用到 public 方法上,这是...
  • @Transactional事务有如下可添加参数: 参数名 参数值 参数作用 noRollbackFor Class<? extends Throwable>[] 一组异常类,遇到时不回滚。默认为{} isolation 枚举org.springframework.transaction....
  • 基于@Transactional注解操作事务的传播行为一.@Transactional中七种事务传播行为二.为什么要控制事务的传播行为三.事务的传播行为使用例子四.扩展——关于调用同一个类下方法的事务问题五.总结 一.@Transactional中...
  • 文章目录事务传播传播类型REQUIREDSUPPORTSMANDATORYREQUIRES_NEWNOT_SUPPORTEDNEVERNESTED 事务传播 在spring中使用事务的时候,经常会写如下代码: @Transactional(propagation = Propagation.REQUIRED) ,不明...
  • 从@Transactional事务传播和隔离

    千次阅读 2019-02-16 19:48:17
    文章目录1.... propagation 事务传播行为 ,默认值为REQUIRED3. isolation 事务的隔离度 默认值为DEFAULT4. timeout 事务的超时时间 默认值为-1. 超时自动回滚5. readOnly 是否为只读事务,默认值为fal...
  • } 如果存在事务,事务传播类型PROPAGATION_MANDATORY,抛出异常 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { throw new IllegalTransactionStateException...
  • 在平常的项目里面,最常看到的就是使用注解@Transactional 去操作事务。 如果稍微对spring事物有过了解的,会知道关于事物传播机制,存在6种, 也就是: 同样可以在注解@Transactional里面看到, 默认配置了...
  • @Transactional注解支持9个属性的设置...其中propagation属性用来枚举事务传播行为,isolation用来设置事务隔离级别,readOnly进行读写事务控制。 @Service @Transactional(readOnly = true) public class AppTr...
  • Spring Boot @Transactional 配置事务管理

    千次阅读 2019-04-29 08:37:16
    propagation 事务传播行为 @Transactional 事务实现机制 rollbackFor 与 noRollbackFor Spring 事务管理概述 1、Spring 事务管理分为编程式和声明式的两种方式。编程式事务通过编码方式实现事务...
  • @Transactional(propagation = Propagation.REQUIRED) 不写属性默认这个 传播行为 意义 PROPAGATION_REQUIRES 表示当前方法必须在一个事务中运行。如果一个现有事务正在进行中,该方法将在那个事务中运行,...
  • @Transactional注解事务不回滚不起作用无效

    万次阅读 多人点赞 2017-01-09 11:23:58
    这几天在项目里面发现我使用@Transactional注解事务之后,抛了异常居然不回滚。后来终于找到了原因。 如果你也出现了这种情况,可以从下面开始排查。 一、特性 先来了解一下@Transactional注解事务的特性吧,...
  • Spring声明式事务@Transactional传播行为

    千次阅读 2018-07-13 16:29:52
    在@Transactional注解中,可以propagation属性用来配置事务传播,支持7种不同的传播机制:REQUIRED:业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务...
  • 传送门:...前提:事务传播行为用在事务事务之间 举例:前提:abc都具有事务,ac的传播模式为默认的required,有一步出错,该方法下面的方法就不会再执行了 @Transactional public v...
  • public class AController{ @Autowired Aservice aService; @Autowired Bservice bService;... 用什么样的事务传播级别才能使两个方法任何一个方法出现异常,两个方法都进行回滚! 请大神赐教,十分感谢!
  • 1、service类标签(一般不建议在接口上)上添加@Transactional,可以将整个类纳入spring事务管理,在每个业务方法执行时都会开启一个事务,不过这些事务采用相同的管理方式。 2、@Transactional 注解只能应用到 ...
  • 关于@Transactional事务设置,文章下面有测试参考
  • 目前有个两个方法,使用@Transactional声明事务。a方法中声明事务捕捉到Exception异常会回滚事务。现在当b方法中抛出异常,a方法就会回滚,目前有这样一个需求:当b方法抛出异常,a方法事务不回滚。 @Transactional...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,501
精华内容 5,400
关键字:

@transactional默认事务传播行为