Spring Framework @Tr

发布时间:2019-09-14 09:22:47编辑:auto阅读(1582)

    转自:http://blog.163.com/sir_876/blog/static/1170522320106259562270/

    清单 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 中,如果帐户更新失败,交易订单就会回滚。

    清单 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,所以不会启动任何事物,因此该方法有效地利用了一个本地(数据库)事务。只读标志只在事务启动时应用。在本例中,因为没有启动任何事 务,所以只读标志被忽略。

关键字