java事务的处理

 java的事务处理,若是对数据库进行屡次操做,每一次的执行或步骤都是一个事务.html

若是数据库操做在某一步没有执行或出现异常而致使事务失败,这样有的事务被执行有的就没有被执行,从而就有了事务的回滚,取消先前的操做.....     java

注:在Java中使用事务处理,首先要求数据库支持事务。如使用MySQL的事务功能,就要求MySQL的表类型为Innodb才支持事务。不然,在Java程序中作了commit或rollback,但在数据库中根本不能生效。程序员

JavaBean中使用JDBC方式进行事务处理web

public int delete(int sID) {

  dbc = new DataBaseConnection();

  Connection con = dbc.getConnection();

  try {

   con.setAutoCommit(false);

  // 更改JDBC事务的默认提交方式

   dbc.executeUpdate("delete from xiao where ID=" + sID);

   dbc.executeUpdate("delete from xiao_content where ID=" + sID);

   dbc.executeUpdate("delete from xiao_affix where bylawid=" + sID);

   con.commit();

  //提交JDBC事务

   con.setAutoCommit(true);

  // 恢复JDBC事务的默认提交方式

   dbc.close();

   return 1;

  }   catch (Exception exc) {

   con.rollBack();

  //回滚JDBC事务

   exc.printStackTrace();

   dbc.close();

   return -1;

  }

}

 

 

     在数据库操做中,一项事务是指由一条或多条对数据库更新的sql语句所组成的一个不可分割的工做单元。只有当事务中的全部操做都正常完成了,整个事务才能被提交到数据库,若是有一项操做没有完成,就必须撤消整个事务。 例如在银行的转账事务中,假定张三从本身的账号上把1000元转到李四的账号上,相关的sql语句以下:spring

update account set monery=monery-1000 where name='zhangsan' update account set monery=monery+1000 where name='lisi' 这个两条语句必须做为一个完成的事务来处理。只有当两条都成功执行了,才能提交这个事务。若是有一句失败,整个事务必须撤消。sql

在connection类中提供了3个控制事务的方法:数据库

(1) setAutoCommit(Boolean autoCommit):设置是否自动提交事务;apache

(2) commit();提交事务;编程

(3) rollback();撤消事务;设计模式

在jdbc api中,默认的状况为自动提交事务,也就是说,每一条对数据库的更新的sql语句表明一项事务,操做成功后,系统自动调用commit()来提交,不然将调用rollback()来撤消事务。

在jdbc api中,能够经过调用setAutoCommit(false) 来禁止自动提交事务。而后就能够把多条更新数据库的sql语句作为一个事务,在全部操做完成以后,调用commit()来进行总体提交。假若其中一项 sql操做失败,就不会执行commit()方法,而是产生相应的sqlexception,此时就能够捕获异常代码块中调用rollback()方法撤 消事务。

事务处理是企业应用须要解决的最主要的问题之一。J2EE经过JTA提供了完整的事务管理能力,包括多个事务性资源的管理能力。可是大部分应用都是运行在单一的事务性资源之上(一个数据库),他们并不须要全局性的事务服务。本地事务服务已然足够(好比JDBC事务管理)。      

本文并不讨论应该采用何种事务处理方式,主要目的是讨论如何更为优雅地设计事务服务。仅以JDBC事务处理为例。涉及到的DAO,Factory,Proxy,Decorator等模式概念,请阅读相关资料。      也许你据说过,事务处理应该作在service层,也许你也正这样作,可是否知道为何这样作?为何不放在DAO层作事务处理。显而易见的缘由是业务层 接口的每个方法有时候都是一个业务用例(User Case),它须要调用不一样的DAO对象来完成一个业务方法。好比简单地以网上书店购书最后的肯定定单为例,业务方法首先是调用BookDAO对象(通常 是经过DAO工厂产生),BookDAO判断是否还有库存余量,取得该书的价格信息等,而后调用 CustomerDAO从账户扣除相应的费用以及记录信息,而后是其余服务(通知管理员等)。简化业务流程大概如此:      注意,咱们的例子忽略了链接的处理,只要保证同一个线程内取的是相同的链接便可(可用ThreadLocal实现):       

首先是业务接口,针对接口,而不是针对类编程:

public interface BookStoreManager{ 

           public boolean buyBook(String bookId,int quantity)throws SystemException; 

           ....其余业务方法       } 

 

     接下来就是业务接口的实现类??业务对象:

 

public class BookStoreManagerImpl implements BookStoreManager{ 

          public boolean buyBook(String bookId)throws SystemException{ 

               Connection conn=ConnectionManager.getConnection();

    //获取数据库链接 

               boolean b=false; 

                              try{ 

                   conn.setAutoCommit(false);  

     //取消自动提交 

                   BookDAO bookDAO=DAOFactory.getBookDAO(); 

                   CustomerDAO customerDAO=DAOFactory.getCustomerDAO(); 

                     //尝试从库存中取书                     

          if(BookDAO.reduceInventory(conn,bookId,quantity)){ 

                        BigDecimal price=BookDAO.getPrice(bookId);   //取价格 

                        //从客户账户中扣除price*quantity的费用 

                        b=CustomerDAO.reduceAccount(conn,price.multiply(new BigDecimal(quantity)); 

 

                        ....                         

 

其余业务方法,如通知管理员,生成定单等. 

                         ...                          

      conn.commit();    //提交事务 

                        conn.setAutoCommit(true); 

                   } 

                }catch(SQLException e){ 

                   conn.rollback();  

        //出现异常,回滚事务 

                   con.setAutoCommit(true); 

                   e.printStackTrace(); 

                   throws new SystemException(e);   

                } 

                return b; 

          }      

 }       

 

而后是业务表明工厂:      

public final class ManagerFactory { 

       public static BookStoreManager getBookStoreManager() { 

          return new BookStoreManagerImpl(); 

       } 

    }

 

       这样的设计很是适合于DAO中的简单活动,咱们项目中的一个小系统也是采用这样的设计方案,可是它不适合于更大规模的应用。

首先,你有没有闻到代码重复的 bad smell?每次都要设置AutoCommit为false,而后提交,出现异常回滚,包装异常抛到上层,写多了不烦才怪,那能不能消除呢?

其次,业务代 表对象如今知道它内部事务管理的全部的细节,这与咱们设计业务表明对象的初衷不符。对于业务表明对象来讲,了解一个与事务有关的业务约束是至关恰当的,但 是让它负责来实现它们就不太恰当了。

再次,你是否想过嵌套业务对象的场景?业务表明对象之间的互相调用,层层嵌套,此时你又如何处理呢?你要知道按咱们现 在的方式,每一个业务方法都处于各自独立的事务上下文当中(Transaction Context),互相调用造成了嵌套事务,此时你又该如何处理?也许办法就是从新写一遍,把不一样的业务方法集中成一个巨无霸包装在一个事务上下文中。 

     咱们有更为优雅的设计来解决这类问题,若是咱们把Transaction Context的控制交给一个被业务表明对象、DAO和其余Component所共知的外部对象。当业务表明对象的某个方法须要事务管理时,它提示此外部 对象它但愿开始一个事务,外部对象获取一个链接而且开始数据库事务。也就是将事务控制从service层抽离,当 web层调用service层的某个业务表明对象时,返回的是一个通过Transaction Context外部对象包装(或者说代理)的业务对象。此代理对象将请求发送给原始业务表明对象,可是对其中的业务方法进行事务控制。那么,咱们如何实现 此效果呢?答案是JDK1.3引进的动态代理技术。动态代理技术只能代理接口,这也是为何咱们须要业务接口BookStoreManager的缘由。      

首先,咱们引入这个Transaction Context外部对象,它的代码其实很简单,若是不了解动态代理技术的请先阅读其余资料。 

 

import java.lang.reflect.InvocationHandler; 

import java.lang.reflect.Method; 

import java.lang.reflect.Proxy; 

import java.sql.Connection; 

import com.strutslet.demo.service.SystemException; 

public final class TransactionWrapper { 

     /**        * 装饰原始的业务表明对象,返回一个与业务表明对象有相同接口的代理对象        */ 

     public static Object decorate(Object delegate) { 

         return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), 

                 delegate.getClass().getInterfaces(), new XAWrapperHandler( 

                         delegate)); 

     }            //动态代理技术       

static final class XAWrapperHandler implements InvocationHandler { 

         private final Object delegate; 

         XAWrapperHandler(Object delegate) { 

            this.delegate = delegate; 

         }                   

   //简单起见,包装业务表明对象全部的业务方法           

public Object invoke(Object proxy, Method method, Object[] args) 

                 throws Throwable { 

             Object result = null; 

             Connection con = ConnectionManager.getConnection(); 

             try { 

                 //开始一个事务                   

    con.setAutoCommit(false); 

                 //调用原始业务对象的业务方法                   

    result = method.invoke(delegate, args); 

                 con.commit();    //提交事务 

                 con.setAutoCommit(true); 

             } catch (Throwable t) { 

                 //回滚                   

    con.rollback(); 

                 con.setAutoCommit(true); 

                 throw new SystemException(t); 

             } 

             return result; 

         } 

     } 

}

 

       正如咱们所见,此对象只不过把业务对象须要事务控制的业务方法中的事务控制部分抽取出来而已。

请注意,业务表明对象内部调用自身的方法将不会开始新的事务,由于这些调用不会传给代理对象。

如此,咱们去除了表明重复的味道。此时,咱们的业务表明对象修改为:

public class BookStoreManagerImpl implements BookStoreManager { 

     public boolean buyBook(String bookId)throws SystemException{ 

           Connection conn=ConnectionManager.getConnection();// 获取数据库链接 

           boolean b=false; 

           try{ 

               BookDAO bookDAO=DAOFactory.getBookDAO(); 

               CustomerDAO customerDAO=DAOFactory.getCustomerDAO(); 

               // 尝试从库存中取书                

       if(BookDAO.reduceInventory(conn,bookId,quantity)){ 

                   BigDecimal price=BookDAO.getPrice(bookId);   // 取价格 

                   // 从客户账户中扣除price*quantity的费用                     

        b=  CustomerDAO.reduceAccount(conn,price.multiply(new 

        BigDecimal(quantity));

                         ....                     

        其余业务方法,如通知管理员,生成定单等. 

                   ...               

        } 

           }catch(SQLException e){ 

              throws new SystemException(e); 

           } 

           return b; 

     }     

  .... 

}       

 

 

能够看到,此时的业务表明对象专一于实现业务逻辑,它再也不关心事务控制细节,把它们所有委托给了外部对象。业务表明工厂也修改一下,让它返回两种类型的业务表明对象:

public final class ManagerFactory { 

       //返回一个被包装的对象,有事务控制能力        

 public static BookStoreManager getBookStoreManagerTrans() { 

           return (BookStoreManager) TransactionWrapper 

                   .decorate(new BookStoreManagerImpl()); 

       }         //原始版本         

public static BookStoreManager getBookStoreManager() { 

          return new BookStoreManagerImpl(); 

       } 

       ...... 

    } 

 

     咱们在业务表明工厂上提供了两种不一样的对象生成方法:一个用于建立被包装的对象,它会为每次方法调用建立一个新的事务;另一个用于建立未被包装的版本,它用于加入到已有的事务(好比其余业务表明对象的业务方法),解决了嵌套业务表明对象的问题。

    咱们的设计还不够优雅,好比咱们默认全部的业务表明对象的方法调用都将被包装在一个Transaction Context。可事实是不少方法也许并不须要与数据库打交道,若是咱们能配置哪些方法须要事务声明,哪些不须要事务管理就更完美了。解决办法也很简单, 一个XML配置文件来配置这些,调用时判断便可。说到这里,了解spring的大概都会意识到这不正是声明式事务控制吗?正是如此,事务控制就是AOP的 一种服务,spring的声明式事务管理是经过AOP实现的。AOP的实现方式包括:动态代理技术,字节码生成技术(如CGLIB库),java代码生成 (早期EJB采用),修改类装载器以及源代码级别的代码混合织入(aspectj)等。咱们这里就是利用了动态代理技术,只能对接口代理;对类的动态代理 可使用cglib库简单事务的概念

我不想从原理上说明什么是事务,应为那太枯燥了。我只想从一个简单的例子来讲明什么是事务。

    例如咱们有一个订单库存管理系统,每一次生成订单的同时咱们都要消减库存。一般来讲订单和库存在数据库里是分两张表来保存的:订单表,库存表。每一次咱们追加一个订单实际上须要两步操做:在订单表中插入一条数据,同时修改库存的数据。

    这样问题来了,例如咱们须要一个单位为10的订单,库存中有30件,理想的操做是咱们在订单表中插入了一条单位为10的订单,以后将库存表中的数据修 改成20。可是有些时候事情并非老是按照你的想法发生,例如:在你修改库存的时候,数据库忽然因为莫名其妙的缘由没法链接上了。也就是说库存更新失败 了。可是订单已经产生了,那么怎么办呢?没办法,只有手动的修改。因此最好的方式是将订单插入的操做和库存修改的操做绑定在一块儿,必须同时成功或者什么都 不作。这就是事务。

    Java如何处理事务呢?   咱们从java.sql.Connection提及,Connection表示了一个和数据库的连接,能够经过Connection来对数据库操做。 在一般状况是Connection的属性是自动提交的,也就是说每次的操做真的更新了数据库,真的没法回退了。针对上述的例子,一旦库存更新失败了,订单 没法回退,由于订单真的插入到了数据库中。这并非咱们但愿的。

  咱们但愿的是:看起来成功了,可是没有真的操做数据库,知道我想让他真的发生。能够经过Connection的setAutoCommit (false)让Connection不自动提交你的数据,除非你真的想提交。那么如何让操做真的发生呢?可使用Connection的commit方 法。如何让操做回退呢?使用rollback方法。

例如: 

  

try{

  Connection conn = getConnection(); // 无论如何咱们获得了连接

  conn.setAutoCommit(false); 

  // 插入订单 // 修改库存

   conn.commit(); // 成功的状况下,提交更新。

  } catch(SQLException ex) {

  conn.rollback(); // 失败的状况下,回滚全部的操做

  } 

  finally {

  conn.close(); 

  }

 

    这里有一点很是重要,事务是基于数据库连接的。因此在但数据库的状况下,事务操做很简单。

    那么若是表分布在两个不一样的数据库中呢?

  例如订单表在订单库中,库存表在库存库中,那么咱们如何处理这样的事务呢?

  须要注意,提交也能够遇到错误呀! 

 

try{

Connection conn1 = getConnection1(); 

Connection conn2 = getConnection2(); 

// 基于conn1作插入操做

// 基于conn2作更新操做

try{

conn1.commit()

} catch(SQLExcetion ) {

conn1.rollback(); 

} 

try { 

conn2.commit(); 

} catch(SQLException ) {

conn2.rollbakc(); 

// 保证确定删除刚才插入的订单。  }

} catch(SQLException ex) {

// 若是插入失败,

conn1.rollback

// 若是更新失败,

conn1.rollback && conn2.rollback 

} finally {

conn1.close(); 

conn2.close(); 

} 

 

 

  看看上述的代码就知道,其实操做很是的复杂,甚至:保证确定删除刚才插入的订单根本没法保证。

  在上述状况下的事务能够称之为分布式事务,经过上述的代码中事务同时提交处理的部分咱们能够得出,要想处理分布式事务,必须有独立于数据库的第三方的事务处理组件。

    幸运的是一般状况下,JavaEE兼容的应用服务器,例如:Weblogic,Websphere,JBoss,Glassfish等都有这种分布式事务处理的组件。

    如何使用应用服务器的分布式事务管理器处理分布式事务?

  以galssfish为例

  1 创建对应两个数据库的XA(javax.sql.XADataSource)类型的数据源。

  2 使用UserTransaction来保证分布式事务。 

try{

Connection conn1 = datasource1.getConnection(); 

Connection conn2 = datasource2.getConnection(); 

UserTransaction ut = getUserTransaction(); 

ut.begin(); 

// 插入订单 // 修改库存

ut.commit(); // 成功的状况下,提交更新。

} catch(SQLException ex) {

ut.rollback(); // 失败的状况下,回滚全部的操做

 

} finally {

conn.close(); 

}    

如何获取UserTransaction呢?可使用以下方法

UserTransaction tx = (UserTransaction)

ctx.lookup("jndi/UserTransaction");

 

 

J2EE开发人员使用数据访问对象(DAO)设计模式把底层的数据访问逻辑和高层的商务逻辑分开。实现DAO模式可以更加专一于编写数据访问代码。这篇文章中,Java开发人员Sean C. Sullivan从三个方面讨论DAO编程的结构特征:事务划分,异常处理,日志记录。

  在最近的18个月,我和一个优秀的软件开发团队一块儿工做,开发定制基于WEB的供应链管理应用程序.咱们的应用程序访问普遍的持久层数据,包括出货状态,供应链制度,库存,货物发运,项目管理数据,和用户属性等.咱们使用JDBC API链接咱们公司的各类数据库平台,而且在整个应用程序中应用了DAO设计模式.   经过在整个应用程序中应用数据访问对象(DAO)设计模式使咱们可以把底层的数据访问逻辑和上层的商务逻辑分开.咱们为每一个数据源建立了提供CRUD(建立,读取,更新,删除)操做的DAO类.   在本文中,我将向你介绍DAO的实现策略以及建立更好的DAO类的技术.我会明确的介绍日志记录,异常处理,和事务划分三项技术.你将学在你的DAO类中怎样把这三种技术结合在一块儿.这篇文章假设你熟悉JDBC API,SQL和关系性数据库编程.

  咱们先来回顾一下DAO设计模式和数据访问对象.

  DAO基础

  DAO模式是标准的J2EE设计模式之一.开发人员使用这个模式把底层的数据访问操做和上层的商务逻辑分开.一个典型的DAO实现有下列几个组件:

  1. 一个DAO工厂类;

  2. 一个DAO接口;

  3. 一个实现DAO接口的具体类;

  4. 数据传递对象(有些时候叫作值对象).

  具体的DAO类包含了从特定的数据源访问数据的逻辑。在下面的这段中你将学到设计和实现数据访问对象的技术。

  事务划分:

  关于DAO要记住的一件重要事情是它们是事务性对象。每一个被DAO执行的操做(象建立,更新、或删除数据)都是和事务相关联的。一样的,事务划分(transaction demarcation)的概念是特别重要的。

  事务划分是在事务界定定义中的方式。J2EE规范为事务划分描述了两种模式:编程性事务(programmatic)和声明性事务(declarative)。下表是对这两种模式的拆分:

声明性事务划分

编程性事务划分

程序员使用EJB的布署描述符声明事务属性

程序员担负编写事务逻辑代码的责任。

运行时环境(EJB容器)使用这些属性来自动的管理事务。

应用程序经过一个API接口来控制事务。

  我将把注意力集中的编程性事务划分上。

  象前面的介绍同样,DAOs是一些事务对象。一个典型的DAO要执行象建立、更新、和删除这的事务性操做。在设计一个DAO时,首先要问本身以下问题:

  一、 事务将怎样开始?

    二、 事务将怎样结束?

  三、 那个对象将承担起动一个事务的责任?

  四、 那个对象将承担结束一个事务的责任?

  五、 DAO应该承担起动和结束事务的责任?

  六、 应用程序须要交叉访问多个DAO吗?

  七、 一个事务包含一个DAO仍是多个DAO?

  八、 一个DAO包含其它的DAO中的方法吗?

  回答这些问题将有助于你为DAO对象选择最好的事务划分策略。对ADO中的事务划分有两个主要的策略。一种方法是使用DAO承担事务划分的责任;另外一 种是延期性事务,它把事务划分到调用DAO对象的方法中。若是你选择前者,你将要在DAO类中嵌入事务代码。若是你选择后者,事务代码将被写在DAO类的 外部。咱们将使用简单的代码实例来更好的理解这两种方法是怎样工做的。

  实例1展现了一个带有两种数据操做的DAO:建立(create)和更新(update):

public void createWarehouseProfile(WHProfile profile);

public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);

 

  实例2展现了一个简单的事务,事务划分代码是在DAO类的外部。注意:在这个例子中的调用者把多个DOA操做组合到这个事务中。

tx.begin(); // start the transaction

dao.createWarehouseProfile(profile);

dao.updateWarehouseStatus(id1, status1);

dao.updateWarehouseStatus(id2, status2);

tx.commit(); // end the transaction


 

  这种事务事务划分策略对在一个单一事务中访问多个DAO的应用程序来讲尤其重要。

  你便可使用JDBC API也可使用Java 事务API(JTA)来实现事务的划分。JDBC事务划分比JTA事务划分简单,可是JTA提供了更好的灵活性。在下面的这段中,咱们会进一步的看事务划分机制。

  使用JDBC的事务划分

  JDBC事务是使用Connection对象来控制的。JDBC的链接接口(java.sql.Connection)提供了两种事务模式:自动提交和手动提交。Java.sql.Connection为控制事务提供了下列方法:

.public void setAutoCommit(Boolean)

.public Boolean getAutoCommit()

.public void commit()

.public void rollback()

 

  实例3展现怎样使用JDBC API来划分事务:

import java.sql.*import javax.sql.*// ... DataSource ds = obtainDataSource();

Connection conn = ds.getConnection();

conn.setAutoCommit(false);

// ...

pstmt = conn.prepareStatement(";UPDATE MOVIES ...";);

pstmt.setString(1, ";The Great Escape";);

pstmt.executeUpdate();

// ...

conn.commit();

// ...

 

 

  使用JDBC事务划分,你可以把多个SQL语句组合到一个单一事务中。JDBC事务的缺点之一就是事务范围被限定在一个单一的数据库链接中。一个 JDBC事务不可以跨越多个数据库。接下来,咱们会看到怎样使用JTA来作事务划分的。由于JTA不象JDBC那样被普遍的了解,因此我首先概要的介绍一 下JTA。

  JTA概要介绍

  Java事务API(JTA;Java Transaction API)和它的同胞Java事务服务(JTS;Java Transaction Service),为J2EE平台提供了分布式事务服务。一个分布式事务(distributed transaction)包括一个事务管理器(transaction manager)和一个或多个资源管理器(resource manager)。一个资源管理器(resource manager)是任意类型的持久化数据存储。事务管理器(transaction manager)承担着全部事务参与单元者的相互通信的责任。下车站显示了事务管理器和资源管理的间的关系。

  JTA事务比JDBC事务更强大。一个JTA事务能够有多个参与者,而一个JDBC事务则被限定在一个单一的数据库链接。下列任一个Java平台的组件均可以参与到一个JTA事务中:

  .JDBC链接

  .JDO PersistenceManager 对象

  .JMS 队列

  .JMS 主题

  .企业JavaBeans(EJB)

  .一个用J2EE Connector Architecture 规范编译的资源分配器。

  使用JTA的事务划分

    要用JTA来划分一个事务,应用程序调用javax.transaction.UserTransaction接口中的方法。示例4显示了一个典型的JNDI搜索的UseTransaction对象。

import javax.transaction.*import javax.naming.*// ...

InitialContext ctx = new InitialContext();

Object txObj = ctx.lookup(";java:comp/UserTransaction";);

UserTransaction utx = (UserTransaction) txObj;

 

 

  应用程序有了UserTransaction对象的引用以后,就能够象示例5那样来起动事务。

utx.begin();

// ...

DataSource ds = obtainXADataSource();

Connection conn = ds.getConnection();

pstmt = conn.prepareStatement(";UPDATE MOVIES ...";);

pstmt.setString(1, ";Spinal Tap";);

pstmt.executeUpdate();

// ...

utx.commit();

// ...

 

 

  当应用程序调用commit()时,事务管理器使用两段提交协议来结束事务。JTA事务控制的方法:

  .javax.transaction.UserTransaction接口提供了下列事务控制方法:

.public void begin()

.public void commit()

.public void rollback()

.public void getStatus()

.public void setRollbackOnly()

.public void setTransactionTimeout(int)

 

 

  应用程序调用begin()来起动事务,便可调用commit()也能够调用rollback()来结束事务。

  使用JTA和JDBC

  开发人员常用JDBC来做为DAO类中的底层数据操做。若是计划使用JTA来划分事务,你将须要一个实现了javax.sql.XADataSource,javax.sql.XAConnection和javax.sql.XAResource接口JDBC的驱动。实现了这些接口的驱动将有能力参与到JTA事务中。一个XADataSource对象是一个XAConnection对象的工厂。XAConnections是参与到JTA事务中的链接。

  你须要使用应用程序服务器管理工具来创建XADataSource对象。对于特殊的指令请参考应用程序服务器文档和JDBC驱动文档。

  J2EE应用程序使用JNDI来查找数据源。一旦应用程序有了一个数据源对象的引用,这会调用javax.sql.DataSource.getConnection()来得到数据库的链接。

  XA链接区别于非XA链接。要记住的是XA链接是一个JTA事务中的参与者。这就意味着XA链接不支持JDBC的自动提交特性。也就是说应用程序没必要 在XA链接上调用java.sql.Connection.commit()或java.sql.Connection.rollback()。相反,应 用程序应该使用UserTransaction.begin()、UserTransaction.commit()和UserTransaction.rollback().

  选择最好的方法

  咱们已经讨论了JDBC和JTA是怎样划分事务的。每一种方法都有它的优势,回此你须要决定为你的应用程序选择一个最适应的方法。 在咱们团队许多最近的对于事务划分的项目中使用JDBC API来建立DAO类。这DAO类总结以下:

  .事务划分代码被嵌入到DAO类内部

  .DAO类使用JDBC API来进行事务划分

  .调用者没有划分事务的方法

  .事务范围被限定在一个单一的JDBC链接

  JDBC事务对复杂的企业应用程序不老是有效的。若是你的事务将跨越多个DAO对象或多个数据库,那么下面的实现策略可能会更恰当:

  .用JTA对事务进行划分

  .事务划分代码被DAO分开

  .调用者承担划分事务的责任

  .DAO参与一个全局的事务中

  JDBC方法因为它的简易性而具备吸引力,JTA方法提供了更多灵活性。你选择什么样的实现将依赖于你的应用程序的特定需求。

  日志记录和DAO

  一个好的DAO实现类将使用日志记录来捕获有关它在运行时的行为细节。你能够选择记录异常、配置信息、链接状态、JDBC驱动程序的元数据或查询参数。日志对开发整个阶段都是有益的。我常常检查应用程序在开发期间、测试期间和产品中的日志记录。

  在这段中,咱们将展示一段如何把Jakarta Commaons Logging结合中一个DAO中的例子。在咱们开始以前,让咱们先回顾一些基础知识。

  选择一个日志例库

  许多开发人员使用的基本日志形式是:System.out.println和System.err.println.Println语句。这种形式快捷方便,但它们不能提供一个完整的日志系统的的能力。下表列出了Java平台的日志类库:

日志类库

开源吗?

URL

Java.util.logging

http://java.sun.com/j2ee

Jakarta Log4j

http://hajarta.apache.org/log4j/

Jakarta Commons Logging

http:/Jakarta.apache.org/commons/logging.html

  Java.util.logging是J2SE1.4平台上的标准的API。可是,大多数开发人员都认为Jakarta Log4j提供了更大的功能性和灵活性。Log4j超越java.util.logging的优势之一就是它支持J2SE1.3和J2SE1.4平台。

  Jakarta Commons Logging可以被用于和java.util.loggin或Jakarta Log4j一块儿工做。Commons Logging是一个把你的应用程序独立于日志实现的提取层。使用Commons Logging你可以经过改变一个配置文件来与下面的日志实现来交换数据。Commons Logging被用于JAKARTA Struts1.1和Jakarta HttpClient2.0中。

  一个日志示例   示例7显示了在一个DOA类中怎样使用Jakarta Commons Logging

import org.apache.commons.logging.*class DocumentDAOImpl implements DocumentDAO

{

static private final Log log = LogFactory.getLog(DocumentDAOImpl.class);

public void deleteDocument(String id)

{

// ...

log.debug(";deleting document: "; + id);

// ...

Try

{

// ... data operations ...

}

catch (SomeException ex)

{

log.error(";Unable to delete document"; ex);

// ... handle the exception ...

}

}

}

 

  日志是评估应用程序的基本部分。若是你在一个DAO中遇到了失败,日志常常会为理解发生的什么错误提供最好的信息。把日志结合到你的DAO中,确保获得调试和解决问题的有效手段。

  DAO中的异常处理

  咱们已经看了事务划分和日志记录,而且如今对于它们是怎样应用于数据访问对象的有一个深刻的理解。咱们第三部分也是最后要讨论的是异常处理。下面的一些简单的异常处理方针使用你的DAO更容易使用,更加健壮和更具备可维护性。

  在实现DAO模式的时候,要考滤下面的问题:

  .在DAO的public接口中的方法将抛出被检查的异常吗?

  .若是是,将抛出什么样的检查性异常?

  .在DAO实现类中怎能样处理异常。

  在用DAO模式工做的过程当中,咱们的团队为异常处理开发了一组方针。下面的这些方针会很大程度的改善你的DAO:

  .DAO方法应该抛出有意义的异常。

  .DAO方法不该该抛出java.lang.Exception异常。由于java.lang.Exception太通常化,它不能包含有关潜在问题的全部信息。

  .DAO方法不该该抛出java.sql.SQLException异常。SQLException是一个底层的JDBC异常,DAO应用努力封装JDBC异常而不该该把JDBC异常留给应用程序的其它部分。

  .在DAO接口中的方法应该只抛出调用者指望处理的检查性异常。若是调用者不能用适当的方法来处理异常,考滤抛出不检查性(运行时run-time)异常。   .若是你的数据访问代码捕获了一个异常,不可要忽略它。忽略捕获异常的DAO是很处理的。

  .使用异常链把底层的异常传递给高层的某个处理器。

  .考滤定义一个标准的DAO异常类。Spring框架提供了一个优秀的预约义的DAO异常类的集合。

  看Resources,查看有异常和异常处理技术的更详细信息。

  实现示例:MovieDAO

  MoveDAO是一个示范了在这篇文章中所讨论的全部技术,包括事务划分、日志记录和异常处理。你会在Resources段找到MovieDAO的源代码。它被分下面的三个包:

.daoexamples.exception

.daoexamples.move

.daoexamples.moviedemo

 

  这个DAO模式的实现由下面的类和接口组成:

.daoexamples.movie.MovieDAOFactory

.daoexamples.movie.MovieDAO

.daoexamples.movie.MovieDAOImpl

.daoexamples.movie.MovieDAOImplJTA

.daoexamples.movie.Movie

.daoexamples.movie.MovieImple

.daoexamples.movie.MovieNotFoundException

.daoexamples.movie.MovieUtil

 

  MovieDAO接口定义了DAO的数据操做。这个接口有以下五个方法:

.public Movie findMovieById(String id)

.public java.util.Collection findMoviesByYear(String year)

.public void deleteMovie(String id)

.public Movie createMovie(String rating,String year,String title)

.public void updateMovie(String id,String rating,String year,String title)


 

  daoexamples.movie包包含了两个MovieDAO接口的实现。每一个实现使用了一个同的事务划分方法,以下表所示:

 

MovieDAOImpl

MovieDAOImplJTA

实现了MovieDAO接口吗?

Yes

Yes

经过JNDI得到DataSource吗?

Yes

Yes

从一个DataSource得到java.sql.Connection对象吗?

Yes

Yes

DAO界定内部的事务吗?

Yes

No

使用JDBC事务吗?

Yes

No

使用一个XA DataSource吗?

No

Yes

分担JTA事务吗?

No

Yes

  MovieDAO 示范应用程序

  这个示范应用程序是一个叫作daoexamples.moviedemo.DemoServlet.DemoServlet的servlet类,它使用Movie DAO来查询和更新一个表中的movie数据。

  这个servlet示范了把JTA感知的MovieDAO和Java消息服务组合到一个单一的事务中,如示例8所示:

UserTransaction utx = MovieUtil.getUserTransaction();

utx.begin();

batman = dao.createMovie(";R"";2008"";Batman Reloaded";);

publisher = new MessagePublisher();

publisher.publishTextMessage(";I’ll be back";);

dao.updateMovie(topgun.getId(),

";PG-13";

topgun.getReleaseYear(),

topgun.getTitle());

dao.deleteMovie(legallyblonde.getId());

utx.commit();

 

  要运行这个范例应用程序,在你的应用程序服务器中配置一个XA 数据源和一个非XA数据源。而后布署daoexamples.ear文件。这个应用程序将运行在任何与J2EE兼容的应用程序服务器。

事务处理

  信息是任何企事业单位的重要资产,任何企业部门都包含着信息的流入、流出,任何企业部门都控制着某些信息。同时,信息必须在适当的时机传播给须要的 人。并且,信息还须要安全约束,一般根据信息的类型和内容实施访问控制。为了保证数据的安全有效和正确可靠,数据库管理系统(DBMS)必须提供统一的数 据保护功能。

  事务是现代数据库理论中的核心概念之一。若是一组处理步骤或者所有发生或者一步也不执行,咱们称该组处理步骤为一个事务。当全部的步骤像一个操做同样 被完整地执行,咱们称该事务被提交。因为其中的一部分或多步执行失败,致使没有步骤被提交,则事务必须回滚(回到最初的系统状态)。事务必须服从 ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久 性(durability)的缩写。事务的原子性表示事务执行过程当中的任何失败都将致使事务所作的任何修改失效。一致性表示当事务执行失败时,全部被该事 务影响的数据都应该恢复到事务执行前的状态。隔离性表示在事务执行过程当中对数据的修改,在事务提交以前对其余事务不可见。持久性表示已提交的数据在事务执 行失败时,数据的状态都应该正确。

  在下面咱们列举一个使用SQL Server数据库进行事务处理的例子。主表是一个规章制度信息表(bylaw),主要字段有记录编号、标题、做者、书写日期等。两个子表分别是附件表 (bylaw_affix)和文本信息表(bylaw_content)。表结构见图1所示。bylaw表的记录编号与bylaw_affix表的记录编 号、bylaw_content表的记录编号是对应的,每次对规章制度信息的操做也就是对这三个表的联合操做。例如要删除规章制度中的一条记录,若是不使 用事务,就可能会出现这样的状况:第一个表中成功删除后,数据库忽然出现意外情况,而第2、三个表中的操做没有完成,这样,删除操做并无完成,甚至已经 破坏数据库中的数据。要避免这种状况,就应该使用事务,它的做用是:要么三个表都操做成功,要么都失败。换句话说,就是保持数据的一致性。因此,为了确保 对数据操做的完整和一致,在程序设计时要充分考虑到事务处理方面的问题。 图1 示例表结构    Java中的事务处理

  通常状况下,J2EE应用服务器支持JDBC事务、JTA(Java Transaction API)事务、容器管理事务。通常状况下,最好不要在程序中同时使用上述三种事务类型,好比在JTA事务中嵌套JDBC事务。第二方面,事务要在尽量短 的时间内完成,不要在不一样方法中实现事务的使用。下面咱们列举两种事务处理方式。 一、JavaBean中使用JDBC方式进行事务处理 在JDBC中怎样将多个SQL语句组合成一个事务呢?在JDBC中,打开一个链接对象Connection时,缺省是auto-commit模式,每一个 SQL语句都被看成一个事务,即每次执行一个语句,都会自动的获得事务确认。为了能将多个SQL语句组合成一个事务,要将auto-commit模式屏蔽 掉。在auto-commit模式屏蔽掉以后,若是不调用commit()方法,SQL语句不会获得事务确认。在最近一次commit()方法调用以后的 全部SQL会在方法commit()调用时获得确认。

public int delete(int sID) {

dbc = new DataBaseConnection();

Connection con = dbc.getConnection();

try {

con.setAutoCommit(false);// 更改JDBC事务的默认提交方式

dbc.executeUpdate("delete from bylaw where ID=" + sID);

dbc.executeUpdate("delete from bylaw _content where ID=" + sID);

dbc.executeUpdate("delete from bylaw _affix where bylawid=" + sID);

con.commit();//提交JDBC事务

con.setAutoCommit(true);// 恢复JDBC事务的默认提交方式

dbc.close();

return 1;

}

catch (Exception exc) {

con.rollBack();//回滚JDBC事务

exc.printStackTrace();

dbc.close();

return -1;

}

}

 

二、SessionBean中的JTA事务 JTA 是事务服务的 J2EE 解决方案。本质上,它是描述事务接口(好比 UserTransaction 接口,开发人员直接使用该接口或者经过 J2EE 容器使用该接口来确保业务逻辑可以可靠地运行)的 J2EE 模型的一部分。JTA 具备的三个主要的接口分别是 UserTransaction 接口、TransactionManager 接口和 Transaction 接口。这些接口共享公共的事务操做,例如commit() 和 rollback(), 可是也包含特殊的事务操做,例如 suspend(),resume() 和enlist(),它们只出如今特定的接口上,以便在实现中容许必定程度的访问控制。例如,UserTransaction 可以执行事务划分和基本的事务操做,而 TransactionManager 可以执行上下文管理。 应用程序能够调用UserTransaction.begin()方法开始一个事务,该事务与应用程序正在其中运行的当前线程相关联。底层的事务管理器实 际处理线程与事务之间的关联。UserTransaction.commit()方法终止与当前线程关联的事务。UserTransaction.rollback()方法将放弃与当前线程关联的当前事务。

public int delete(int sID) {

DataBaseConnection dbc = null;

dbc = new DataBaseConnection();

dbc.getConnection();

UserTransaction transaction = sessionContext.getUserTransaction();//得到

JTA事务

try {

transaction.begin(); //开始JTA事务

dbc.executeUpdate("delete from bylaw where ID=" + sID);

dbc.executeUpdate("delete from bylaw _content where ID=" + sID);

dbc.executeUpdate("delete from bylaw _affix where bylawid=" + sID);

transaction.commit(); //提交JTA事务

dbc.close();

return 1;

}

catch (Exception exc) {

try {

transaction.rollback();//JTA事务回滚

}

catch (Exception ex) {

//JTA事务回滚出错处理

ex.printStackTrace();

}

exc.printStackTrace();

dbc.close();

return -1;

}

}

 

Can't start a cloned connection while in manual transaction mode错误2008-03-13 20:30出现Can't start a cloned connection while in manual transaction mode错误,从网上找到缘由及解决办法以下:

  缘由通常是当你在一个SQL SERVERJDBC链接上执行多个STATEMENTS的操做,或者是手动事务状态(AutoCommit=false) 而且使用默认的模式. direct (SelectMethod=direct) 模式.

解决办法 当你使用手动事务模式时,必须把SelectMethod 属性的值设置为 Cursor, 或者是确保在你的链接只有一个STATEMENT操做。

修改url

加入SelectMethod=cursor便可

 

:

jdbc:microsoft:sqlserver://localhost:1433;

DatabaseName=ys;

SelectMethod=Cursor;Us

er=ys;Password=ys");

package _class;

import java.sql.*;

import java.util.StringTokenizer;

public class connDB{

    String sDBDriver = "com.microsoft.jdbc.sqlserver.SQLServerDriver";

    String sConnStr = 

"jdbc:microsoft:sqlserver://127.0.0.1:1433;SelectMethod=cursor;DatabaseName=myDB;u

ser=sa;password=river";

    Connection cn = null;

    Statement stmt;

    boolean autoCommit;

    private String DbType="MYSQL";

    //private String DbType="Oracle";

    private connDB(){

       init();

    }

    private void init(){

        try{

            Class.forName(sDBDriver).newInstance();

            cn = DriverManager.getConnection(sConnStr);

        }catch(Exception e){

            System.err.println("conndb():链接异常. " + e.getMessage());

        }

    }

    public static connDB getNewInstance(){

        return new connDB();

    }

    //数据绑定的资料好像不多,有空给你们一个例子。在这里只能返回PreparedStatement。

    public PreparedStatement getPreparedStmt(String sql) throws SQLException{

        PreparedStatement preStmt=null;

        try{

            preStmt = cn.prepareStatement(sql);

        }catch(SQLException ex){

            ex.printStackTrace();

            throw ex;

        }

        return preStmt;

    }

    public void beginTrans() throws SQLException{ 

    try{

            autoCommit=cn.getAutoCommit();

            cn.setAutoCommit(false);

        }catch(SQLException ex){

            ex.printStackTrace();

            System.out.print("beginTrans Errors");

            throw ex;

        }

    }

    public void commit()throws SQLException{

        try{

            cn.commit();

            cn.setAutoCommit(autoCommit);

        }catch(SQLException ex){

            ex.printStackTrace();

            System.out.print("Commit Errors");

            throw ex;

        }

    }

    public void rollback(){

        try{

            cn.rollback();

            cn.setAutoCommit(autoCommit);

        }catch(SQLException ex){

            ex.printStackTrace();

            System.out.print("Rollback Errors");

            //throw ex;

        }

    }

    public boolean getAutoCommit() throws SQLException{

        boolean result=false;

        try{

            result=cn.getAutoCommit();

        }catch(SQLException ex){

            ex.printStackTrace();

            System.out.println("getAutoCommit fail"+ex.getMessage());

            throw ex;

        }

        return result;

    }

    //默认的状况下一次executeQuery(String sql)是一次事务。     

//可是能够调用beginTrans(),而后屡次executeQuery(String sql),

//最后commit()实现多sql的事务处理(注意在这种状况下若是发生违例,千万不要忘了在catch(){调用rollBack()})。

    //     

public ResultSet executeQuery(String sql) throws SQLException{

        ResultSet rs = null;

        try{

            stmt=cn.createStatement();

            rs = stmt.executeQuery(sql);

        }

        catch(SQLException ex)

        {

            ex.printStackTrace();

            System.out.println("conndb.executeQuery:"+ex.getMessage());

            throw ex;

        }

        return rs;

    }

    public void executeUpdate(String sql) throws SQLException{

        try{

            stmt=cn.createStatement();

            stmt.executeUpdate(sql);

        }catch(SQLException ex){

            ex.printStackTrace();

            System.out.println("conndb.executeUpdate:"+ex.getMessage());

            throw ex;

        }

    }     

//Method doBatch 的参数sql,是由一些sql语句拼起来的,用;隔开。能够将许多的sql放在一个事务中,一次执行。

    public int[] doBatch(String sql) throws SQLException{

        int[] rowResult=null;

        String a;

        try{

            //boolean autoCommit=cn.getAutoCommit();

            //cn.setAutoCommit(false);

            stmt=cn.createStatement();

            StringTokenizer st = new StringTokenizer(sql,";");

            while (st.hasMoreTokens()){

                 a=st.nextToken();

                 stmt.addBatch(a);

             }

             rowResult=stmt.executeBatch();

        }catch(SQLException ex){

            ex.printStackTrace();

            System.out.println("conndb.doBatch:"+ex.getMessage());

            throw ex;

        }

        return rowResult;

    }

    public String getDbType(){

        return DbType;

    }

    public void close() throws SQLException{

        try{

            stmt.close();

            stmt=null;

            cn.close();

            cn=null;

        }catch(SQLException ex){

            ex.printStackTrace();

            System.out.println("Closeing connection fail"+ex.getMessage());

            throw ex;

 

        }

    }

    public static void main(String[] args)throws Exception{

            connDB con=connDB.getNewInstance();

            System.out.println(con.getDbType());

            String sql2="insert into test values('0510321315','李白',80);";

            String s1="select *from test";

            con.beginTrans();

            ResultSet rs=con.executeQuery(s1);

            con.executeUpdate(sql2);System.out.println("con.executeUpdate(sql2);");

        /*try{

             int up=s.executeUpdate(sql2);

             if(up!=0)System.out.println("语句:"+sql2+"插入成功!");

           else System.out.println("语句:"+sql2+"插入失败!"); 

        }catch(SQLException e){System.out.println(e);}*/

         //ResultSet rs=s.executeQuery("select *from titles");

         con.executeUpdate("delete from test where 

sno='0510321315'");System.out.println("con.executeUpdate(\"delete from test where 

sno='0510321315'\");");

         con.commit();         

        while(rs.next()){     

             System.out.print(rs.getString(1)+"\t");

           System.out.print(rs.getString(2)+"\t");    

               System.out.print(rs.getInt(3)+"\t");

         System.out.println(" ");   

         }

 

            con.close();

                 }

}

 

 
相关文章
相关标签/搜索