我猜数据库没有这条数据,number值是1。事务没理由能把primitive类型的数据都管起来吧?

解决方案 »

  1.   

    这就引出一个问题了:这里怎么在事务里撤消“number++”这个动作呢?
      

  2.   

    抓到异常之后在catch块里撤消罗。
      

  3.   

    这里讨论的是如何借助事务回滚操作;
    如果都catch块里撤消来实现的话,那
    addTestTable("1");
    addTestTable("1");
    造成的一条脏数据,也可以在catch块里用delTestTable("1");来保持原子性;
    但是用catch块来处理所有事务问题,肯定局限性太大。(EdRoman 精通EJB20关于事务章节有论述catch块来处理事务问题缺陷性)
      

  4.   

    回滚是什么?说白了,就是把删除脏数据之类的操作封装到同一个方法里,然后在catch块里集中调用一下。数据库之外的其他资源如果没有提供回滚的API,你当然就只有在catch块里手动撤消操作了,这有什么好奇怪的?或者你就自己给这个资源写一个回滚方法,把撤消动作封装起来。道理不都是一样吗?
      

  5.   

    来自AOP的一种解决方案,可以把try...catch...块限制在抽象基类,具体类只要实现自己的业务方法就可以。但是,只有在“抽象基类了解所有需要回滚的资源”时,这种方案才有效。如果像你举例的这个事务,具体类增加了新的需要回滚的资源,谁都帮不上忙——因为根本不可能有其他任何人知道对这项资源的操作是需要事务支持的。
      

  6.   

    这里是如何利用JTA或者容器事务来处理这种问题,你把这种定义为“新的需要回滚的资源,它不认的”,那是不是你肯定了这样句话呢“事务只认的关于操作数据库的资源,其他的它不能回滚。”
    有没有把握确定?
      

  7.   

    再重声一下题意:JTA或者容器事务能处理这种问题吗?
      

  8.   

    这里是如何利用JTA或者容器事务来处理这种问题,你把这种定义为“新的需要回滚的资源,它不认的”,那是不是你肯定了这样句话呢“事务只认的关于操作数据库的资源,其他的它不能回滚。”
    ==================================================================================不错,
    确切说是 提交/回滚 只认得通过事务管理器所能管理的资源,
    普通的JAVA编码运算,是无法自动回滚的。
      

  9.   

    我来帮你
    .......

    UserTransaction ut = null;
    ut=sessionContext.getUserTransaction();
    int oldnumber;//再会滚前的数据
    try
    {
    ut.begin();
             oldnumber = number;  
    number++;

    addTestTable("1");
    addTestTable("1");
        
    ut.commit();
      
    }catch(Exception ex) {
        
     ut.rollback();
              number = oldnumber;
     System.out.println(number);
    }
        
    ......
      

  10.   

    yabbi21(yabbi21):那我怎么听说SESSION BEAN同时实现了SessionSynchronization接口,就可以做到那一点了呢。
      

  11.   

    to tianboguang(其实,我是一个程序员) :实现了SessionSynchronization接口只是给你提供了三个手工处理数据同步的时机。
    其中:
    1)afterBegin():容器在开始事务内第一个商业方法前调用该方法。
    2)beforeCompletion():容器在将事务正式提交到数据库前调用该方法。
    3)afterCompletion(bool flag):事务提交完成后调用该方法,其中参数flag为真表示提交成功,flag为假则表示事务回滚,此时你可能需要手编码从数据库同步提交前的数据。至于上面写的类似number++这样的普通java代码,
    因为其只是普通的寄存器运算操作,不存在像持久化介质同步的问题,
    因而这些用于事务上概念(如commit,rollback等)也不适用它。
      

  12.   

    to tianboguang(其实,我是一个程序员) :
    那我怎么听说SESSION BEAN同时实现了SessionSynchronization接口,就可以做到那一点了呢。你在哪儿听的,瞎扯。你当事务处理是万能的,不支持事务处理的你用1000个 rollback 都不管用。不信就用用 access 来个事务,看看 rollback 有没有用,access 也是数据库啊,但是他不支持事务,记住事务处理只能使用在支持事务的机制上。变量的运算,java 虚拟机不支持这种事务(而且也没必要)必须自己去实现相应的操作,包括SessionSynchronization。
      

  13.   

    下面是J2EE指南里的例子,目录在j2eetutorial\examples\src\ejb\bank
    请看transferToSaving(double amount)方法里的
    “checkingBalance -= amount;
      savingBalance += amount;”
    这两步涉及成员变量的业务运算,没有涉及任何数据库操作,所有的对这个两个成员变量的符值,回滚数据库操作都是实现在afterBegin(),afterCompletion(bool flag)里的,当事务回滚时,它回自动调用afterCompletion(bool flag)来撤消“checkingBalance -= amount;savingBalance += amount;”这两个运算。
    ---------------------------------------------------------------------------------
    /*
     *
     * Copyright 2001 Sun Microsystems, Inc. All Rights Reserved.
     * 
     * This software is the proprietary information of Sun Microsystems, Inc.  
     * Use is subject to license terms.
     * 
     */import java.util.*;
    import javax.ejb.*;
    import java.sql.*;
    import javax.sql.*;
    import javax.naming.*;public class BankBean implements SessionBean, SessionSynchronization {   private String customerId;
       private double checkingBalance;
       private double savingBalance;
       private SessionContext context;
       private Connection con;
       private String dbName = "java:comp/env/jdbc/BankDB";   public void transferToSaving(double amount) throws 
          InsufficientBalanceException  {      checkingBalance -= amount;
          savingBalance += amount;      try {
             updateChecking(checkingBalance);
             if (checkingBalance < 0.00) {
                context.setRollbackOnly();
                throw new InsufficientBalanceException();
             }
             updateSaving(savingBalance);
          } catch (SQLException ex) {
              throw new EJBException 
                 ("Transaction failed due to SQLException: " 
                 + ex.getMessage());
          }
       }   public double getCheckingBalance() {      return checkingBalance;
       }   public double getSavingBalance() {      return savingBalance;
       }   public void ejbCreate(String id) throws CreateException {      customerId = id;      try {
             makeConnection();
             checkingBalance = selectChecking();
             savingBalance = selectSaving();
          } catch (Exception ex) {
              throw new CreateException(ex.getMessage());
          }
         
       }
       public void ejbRemove() {      try {
              con.close();
          } catch (SQLException ex) {
              throw new EJBException("ejbRemove SQLException: " + ex.getMessage());
          }
       }   public void ejbActivate() {      try {
             makeConnection();
          } catch (Exception ex) {
              throw new EJBException("ejbActivate Exception: " + ex.getMessage());
          }
       }   public void ejbPassivate() {      try {
            con.close();
          } catch (SQLException ex) {
              throw new EJBException("ejbPassivate Exception: " + ex.getMessage());
          }
       }
       public void setSessionContext(SessionContext context) {
          this.context = context;
       }   public void afterBegin() {      System.out.println("afterBegin()");
          try {
             checkingBalance = selectChecking();
             savingBalance = selectSaving();
          } catch (SQLException ex) {
              throw new EJBException("afterBegin Exception: " + ex.getMessage());
          }
             
       }   public void beforeCompletion() {      System.out.println("beforeCompletion()");
       }   public void afterCompletion(boolean committed) {      System.out.println("afterCompletion: " + committed);
          if (committed == false) {
             try {
                checkingBalance = selectChecking();
                savingBalance = selectSaving();
             } catch (SQLException ex) {
                 throw new EJBException("afterCompletion SQLException: " +
                    ex.getMessage());
             }
          }
       }   public BankBean() {}/************************** Database Routines **********************/   private void updateChecking(double amount) throws SQLException {      String updateStatement =
                "update checking set balance =  ? " +
                "where id = ?";      PreparedStatement prepStmt = 
                con.prepareStatement(updateStatement);      prepStmt.setDouble(1, amount);
          prepStmt.setString(2, customerId);
          prepStmt.executeUpdate();
          prepStmt.close();
       }   private void updateSaving(double amount) throws SQLException {      String updateStatement =
                "update saving set balance =  ? " +
                "where id = ?";      PreparedStatement prepStmt =
                con.prepareStatement(updateStatement);      prepStmt.setDouble(1, amount);
          prepStmt.setString(2, customerId);
          prepStmt.executeUpdate();
          prepStmt.close();
       }   private double selectChecking() throws SQLException {      String selectStatement =
                "select balance " +
                "from checking where id = ? ";
          PreparedStatement prepStmt =
                con.prepareStatement(selectStatement);      prepStmt.setString(1, customerId);      ResultSet rs = prepStmt.executeQuery();      if (rs.next()) {
             double result = rs.getDouble(1);
             prepStmt.close();
             return result;
          }
          else {
             prepStmt.close();
             throw new EJBException
                ("Row for id " + customerId + " not found.");
          } 
       }
       private double selectSaving() throws SQLException {      String selectStatement =
                "select balance " +
                "from saving where id = ? ";
          PreparedStatement prepStmt =
                con.prepareStatement(selectStatement);      prepStmt.setString(1, customerId);      ResultSet rs = prepStmt.executeQuery();      if (rs.next()) {
             double result = rs.getDouble(1);
             prepStmt.close();
             return result;
          }
          else {
             prepStmt.close();
             throw new EJBException
                ("Row for id " + customerId + " not found.");
          } 
       }   private void makeConnection() 
          throws NamingException, SQLException {      InitialContext ic = new InitialContext();
          DataSource ds = (DataSource) ic.lookup(dbName);
          con =  ds.getConnection();
       }} // BankBean 
      

  14.   

    以下是J2EE指南Container-Managed Transactions章节的一段话,具体描述了这种情况的解决办法。
    ---------------------------------------------------------------------------
    When the container rolls back a transaction, it always undoes the changes to data made by SQL calls within the transaction. However, only in entity beans will the container undo changes made to instance variables. (It does so by automatically invoking the entity bean's ejbLoad method, which loads the instance variables from the database.) When a rollback occurs, a session bean must explicitly reset any instance variables changed within the transaction. The easiest way to reset a session bean's instance variables is by implementing the SessionSynchronization interface.
      

  15.   

    为什么不等成功了才去number++;?
      

  16.   

    to tianboguang(其实,我是一个程序员) :afterCompletion不是有如下的代码么,
    if (committed == false) {
        try {
             checkingBalance = selectChecking();
             savingBalance = selectSaving();
        } 
        ... 
    }
    当事务提交失败时,committed的值肯定为假(容器负责为你设置);
    此时,你需要做checkingBalance = selectChecking();
    该操作实现上就是从数据库中重新读取一次数据并赋值给checkingBalance 。
    因为事务提交失败了,所以此时数据库中的值并没有被改变,
    checkingBalance = selectChecking()重新读取的效果正好相当于把checkingBalance恢复成事务提交前的值。至于selectChecking()方法具体内容,是需要你手工编码实现的。
     
      

  17.   

    yabbi21(yabbi21) :是呀,这里是从数据库重新读取值撤消操作。但是transferToSaving(double amount)方法里的
    “checkingBalance -= amount;
      savingBalance += amount;”
    这两步是没有数据库操作的,只是普通的JAVA运算,因为BEAN实现SessionSynchronization,所以这两步的数据也可以和updateChecking(checkingBalance)操作数据库的方法一样,得到事务的回滚。只不过它的具体实现是通过容器调用afterCompletion这个方法来完成的,也就是说,事务可以回滚成员变量的普通运算,但是你要实现SessionSynchronization这个接口。
      

  18.   

    事实上,对上面的开头的例子,加上下面的SessionSynchronization接口方法实现就可以搞定了,不信可以试试的。可以看到所有方法都没有涉及数据库操作,但是它也随着事务回滚了,呵呵。
    ---------------------------------------------------------------------------
    ...... public void afterBegin() {
        number = 0; 
     } public void beforeCompletion() {
     } public void afterCompletion(boolean committed) {
        if (committed == false) 
        {
           number--;
        }
     }......
      

  19.   

    学习,佩服并且崇拜yabbi21(yabbi21)!
      

  20.   

    liushenling(六神) :yabbi21(yabbi21)都回答错了,你还要崇拜他,他肯定了“事务只认的关于操作数据库的资源,其他的它不能回滚。”这句话,他还说“普通的JAVA编码运算,是无法自动回滚的。”,呵呵
      

  21.   

    tianboguang(其实,我是一个程序员) :如果你应要把
    [    if (committed == false) 
        {
           number--;
        }
     }
    ] 这样的编码理解成事务回滚 我也没有办法:)number--;这一行是需要你手工编码的,容器不会自动为你生成它。
    如果你不小心写成了number-=2,那数据完整性仍然会被破坏。
      

  22.   

    当然,和普通的数据库事务回滚还是有些区别,毕竟要你自己去实现撤消方法,但是碰到这类问题,J2EE推荐的最好做法还是去实现SessionSynchronization接口,SessionSynchronization接口就是用来为事务处理服务的。否则的话,你就只能把什么都写在catch块的,这样做弊端肯定很多。
    谢谢大家热情参与讨论,来者都有分。