Spring JDBC事務管理

JDBC事務管理

    Spring提供編程式的事務管理(Programmatic transaction manage- ment)與聲明式的事務管理(Declarative transaction management),爲不同的事務實現提供了一致的編程模型,這節以JDBC事務爲例,介紹Spring的事務管理。

    5.3.1 Spring對事務的支持

    事務是一組原子(Atomic)操作的工作單元,以數據庫存取的實例來說,就是一組SQL指令,這一組SQL指令必須全部執行成功,若因爲某個原因未全部執行成功(例如其中一行SQL有錯誤),則先前所有執行過的SQL指令都會被撤消。

    舉個簡單的例子,一個客戶從A銀行轉賬至B銀行,要作的動作爲從A銀行的賬戶扣款、在B銀行的賬戶加上轉賬的金額,兩個動作必須成功,如果有一個動作失敗,則此次轉賬失敗。

    事務還必須保持所參與資源的一致性(Consistent),例如在銀行賬戶的例子中,兩個賬戶的轉賬金額,B賬戶取款的金額不能大於A賬戶的存款金額。 每個事務彼此之間必須是隔離的(Isolated),例如在A賬戶中可能有兩筆事務,同時進行存款與提款的動作,兩個事務基本上不需意識到彼此的存在。事 務還必須是可持續的(Durable),在某一筆事務之後,這筆事務必須是被記錄下來的。

    在這裏將介紹JDBC如何使用事務管理。首先來看看事務的原子性實現,在JDBC中,可以操作Connection的setAutoCommit() 方法,給定false參數,在下達一連串的SQL語句後,自行執行Connection的commit()來送出變更,如果中間發生錯誤,則執行 rollback() 來撤消所有的執行,例如:

 

try {
  
    . . . . .
  
    connection . setAutoCommit ( false) ;
  
    . . . . .
  
    
// 一連串SQL操作

  
    connection . commit ( ) ;
  
} catch ( SQLException ) {
  
    
// 發生錯誤,撤消所有變更

  
    connection . rollback ( ) ;
  
}

    在Spring中對JDBC的事務管理加以封裝,Spring事務管理的抽象關鍵在於org.springframework.transaction.PlatformTransactionManager接口的實現:

 

public interface PlatformTransactionManager {
  
    TransactionStatus getTransaction( TransactionDefinition
  
                    definition) throws TransactionException;
  
    void commit ( TransactionStatus status )
  
                                   throws TransactionException;
  
    void rollback ( TransactionStatus status )
  
                                   throws TransactionException;
  
}

    PlatformTransactionManager 接口有許多具體的事務實現類,例如DataSourceTransactionManager、 HibernateTransactionManager、JdoTransaction- Manager、JtaTransactionManager等,通過依賴於PlatformTransactionManager接口及各種的技術實 現,Spring在事務管理上可以讓開發人員使用一致的編程模型,即使所使用的是不同的事務管理技術。

    TransactionException是Unchecked Exception.事務的失敗通常都是致命的錯誤,Spring不強迫您一定要處理,而是讓您自行選擇是否要捕捉異常。

    getTransaction() 方法根據一個TransactionDefinition對象來回傳一個TransactionStatus對 象,TransactionDefinition接口的實例定義了事務的隔離程度(Isolation level)、傳播行爲(Propagation behavior)、超時(Timeout)、只讀(Read-only)等,TransactionStatus代表着一個新的事務發起或已經存在的事 務,您可以通過它來控制事務的執行或調查的狀態:

 

public interface TransactionStatus {
  
    boolean isNewTransaction( ) ;
  
    void setRollbackOnly( ) ;
  
    boolean isRollbackOnly( ) ;
  
}

 

Spring提供編程式的事務管理(Programmatic transaction management)與聲明式的事務管理(Declarative transaction management):

    l 編程式的事務管理

    編程式的事務管理可以清楚地控制事務的邊界,也就是讓您自行實現事務開始時間、撤消操作的時機、結束時間等,可以實現細粒度的事務控制。

    l 聲明式的事務管理

    然而多數的情況下,事務並不需要細粒度的控制,而是採用聲明式的事務管理,好處是Spring事務管理的相關API可以不用介入程序之中,從對象的角度來 看,它並不知道自己正被納入事務管理之中,在不需要事務管理的時候,只要在設置文件上修改一下設置,即可移去事務管理服務。

    5.3.2 JDBC編程事務管理Spring提供兩種方式實現編程式的事務管理,一是直接使用PlatformTransaction- Manager實現,二是使用org.springframework.transaction.support.Transaction- Template.

    先來看看如何使用PlatformTransactionManager,在這裏使用它的實現類 DataSourceTransactionManager,可以改寫一下之前5.2.1節中的JdbcTemplateDemo項目,讓它具有事務管理 功能,修改一下UserDAO類的insert() 方法來作示範:ProgrammaticTransactionDemo UserDAO.java

 

package onlyfun. caterpillar;
  
import java . util . Iterator ;
  
import java . util . List ;
  
import java . util . Map ;
  
import javax . sql . DataSource ;
  
import org . springframework. dao. DataAccessException;
  
import org . springframework. jdbc. core. JdbcTemplate;
  
import org . springframework. jdbc.
  
            datasource . DataSourceTransactionManager;
  
import org . springframework. transaction . TransactionDefinition;
  
import org . springframework. transaction . TransactionStatus;
  
import org . springframework. transaction .
  
            support. DefaultTransactionDefinition;
  
public class UserDAO implements IUserDAO {
  
    private DataSourceTransactionManager transactionManager;
  
    private DefaultTransactionDefinition def;
  
    private JdbcTemplate jdbcTemplate;
  
       
  
    public void setDataSource( DataSource dataSource ) {
  
        jdbcTemplate = new JdbcTemplate( dataSource ) ;
  
        transactionManager =
  
            new DataSourceTransactionManager( dataSource ) ;
  
        
// 建立事務的定義

  
        def = new DefaultTransactionDefinition( ) ;
  
        def. setPropagationBehavior(
  
                TransactionDefinition. PROPAGATION_REQUIRED) ;
  
    }
  
       
  
    public void insert ( User user) {
  
       String name = user. getName ( ) ;
  
       int age = user. getAge( ) . intValue ( ) ;
  
          
  
       TransactionStatus status =
  
           transactionManager. getTransaction( def) ;
  
       try {
  
           jdbcTemplate. update ( "INSERT INTO user (name,age) "
  
                   + "VALUES('" + name + "'," + age + ")" ) ;
  
           
// 下面的SQL有錯誤,用以測試 事務

  
           jdbcTemplate. update ( "INSER INTO user (name,age) "
  
                   + "VALUES('" + name + "'," + age + ")" ) ;
  
       }
  
       catch ( DataAccessException e) {
  
           transactionManager. rollback ( status ) ;
  
           throw e;
  
       }
  
       transactionManager. commit ( status ) ;
  
    }
  
    public User find ( Integer id ) {
  
        List rows = jdbcTemplate. queryForList(
  
          "SELECT * FROM user WHERE id=" + id . intValue ( ) ) ;
  
           
  
        Iterator it = rows. iterator ( ) ;
  
        if ( it. hasNext ( ) ) {
  
            Map userMap = ( Map ) it. next ( ) ;
  
            Integer i = new Integer (
  
                    userMap. get ( "id" ) . toString ( ) ) ;
  
            String name = userMap. get ( "name" ) . toString ( ) ;
  
            Integer age = new Integer (
  
                    userMap. get ( "age" ) . toString ( ) ) ;
  
            User user = new User( ) ;
  
               
  
            user. setId ( i) ;
  
            user. setName ( name ) ;
  
            user. setAge( age) ;
  
               
  
            return user;
  
        }
  
        return null ;
  
    }
  
}

在 insert()方法中使用了DataSourceTransactionManager來進行事務管理,如果發生了異常,則catch區塊中會進行事務 的Rollback,在insert() 方法中故意寫入錯誤的SQL(注意INSERT方法少寫了一個T),因此實際上數據並不會被儲存至數據庫中。

    要使用MySQL數據庫進行事務處理,必須建立支持事務的表格類型,例如InnoDB的表格類型,這裏用來建立表格的SQL如下所示:

 

CREATE TABLE user (
  
    id INT( 11) NOT NULL auto_increment PRIMARY KEY ,
  
    name VARCHAR ( 100) NOT NULL default '' ,
  
    age INT
  
) TYPE = InnoDB;

    另一個實現編程式事務管理的方法是使用TransactionTemplate,它需要一個TransactionManager實例,如下所示:

 

TransactionTemplate transactionTemplate =
  
        new TransactionTemplate( transactionManager) ;
  
. . .
  
transactionTemplate. execute ( new TransactionCallback( ) {
  
    public Object doInTransaction( TransactionStatus status ) {
  
         return jdbcTemplate. update ( "INSERT INTO user (name,age) "
  
               + "VALUES('" + name + "'," + age + ")" ) ;
  
    }
  
} ) ;

    如果發生了異常,則會進行Rollback,否則提交事務,如果沒有回傳值,則也可以使用TransactionCallbackWithoutResult:

 

transactionTemplate. execute (
  
        new TransactionCallbackWithoutResult( ) {
  
                public void doInTransactionWithoutResult(
  
                                TransactionStatus status ) {
  
            . . . .
  
                }
  
            } ) ;

    5.3.3 JDBC聲明事務管理

    Spring聲明式的事務管理依賴它的AOP框架來完成。使用聲明事務管理的好處是,事務管理不能侵入您所開發的組件,具體來說,DAO對象不會意識到正 在事務管理之中,事實上也應當如此,因爲事務管理是屬於系統層面的服務,而不是業務邏輯的一部分,如果想要改變事務管理策略的話,也只需要在定義文件中重 新配置。

    舉個例子來說,可以將5.2.1節中的JdbcTemplateDemo 項目修改一下,在不修改UserDAO類的情況下,可以爲它加入事務管理的服務,一個簡單的方法是使用 TransactionProxyFactoryBean,指定要介入的事務管理對象及其方法,這需要在定義文件中修改,如下所示:

    DeclarativeTransactionDemo beans-config.xml

 

< ? xml version = "1.0" encoding = "UTF-8" ? >
  
< beans xmlns= "http://www.springframework.org/schema/beans"
  
  xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  
  xsi:schemaLocation=
"http://www.springframework.org/schema/beans
  
  http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
>
  
     
  
    < bean id= "dataSource"
  
          class=
"org.springframework.jdbc.
  
                   → datasource.DriverManagerDataSource"

  
          destroy-method= "close" >
  
        < property name= "driverClassName"
  
                  value= "com.mysql.jdbc.Driver" / >
  
        < property name= "url"
  
                  value= "jdbc:mysql://localhost:3306/demo" / >
  
        < property name= "username" value= "caterpillar" / >
  
        < property name= "password" value= "123456" / >
  
    < / bean>
  
       
  
    < bean id= "transactionManager"
  
          class=
"org.springframework.jdbc.
  
                   → datasource.DataSourceTransactionManager"
>
  
        < property name= "dataSource" ref= "dataSource" / >
  
    < / bean>
  
       
  
    < bean id= "userDAO"
  
          class= "onlyfun.caterpillar.UserDAO" >
  
        < property name= "dataSource" ref= "dataSource" / >
  
    < / bean>
  
       
  
    < bean id= "userDAOProxy"
  
          class=
"org.springframework.transaction.
  
                   → interceptor.TransactionProxyFactoryBean"
>
  
        < property name= "proxyInterfaces" >
  
            < list>
  
                < value> onlyfun. caterpillar. IUserDAO< / value>
  
            < / list>
  
        < / property>
  
        < property name= "target" ref= "userDAO" / >
  
        < property name= "transactionManager"
  
                  ref= "transactionManager" / >
  
        < property name= "transactionAttributes" >
  
            < props>
  
                < prop key= "insert*" > PROPAGATION_REQUIRED< / prop>
  
            < / props>
  
        < / property>
  
    < / bean>
  
< / beans>

    TransactionProxyFactoryBean需要一個TransactionManager,由於這裏使用的是JDBC,所以使用 DataSourceTransactionManager,TransactionProxyFactoryBean是個代理對象,"target" 屬性指定要代理的對象,事務管理會自動介入指定的方法前後,這裏使用 "transactionAttributes" 屬性指定,"insert*" 表示指定方法名稱以insert開頭的都要納入事務管理,您也可以指定方法全名,如果在方法執行過程中發生錯誤,則所有先前的操作自動撤回,否則正常提 交。

在"insert*" 等方法上指定了 "PROPAGATION_REQUIRED",表示在目前的事務中執行操作,如果事務不存在就建立一個新的,相關的常數意義都可以在API文件的 TransactionDefinition接口中找到。您可以加上多個事務定義,中間使用逗號 "," 區隔,例如可以加上只讀,或者是指定某個異常發生時撤回操作:

    PROPAGATION_REQUIRED,readOnly,-MyCheckedException

    MyCheckedException前面加上 "-" 時,表示發生指定異常時撤消操作,如果前面加上 "+",表示發生異常時立即提交。

    由於"userDAO"被"userDAOProxy"代理了,所以要做的是取得"userDAOProxy",而不是"userDAO",例如:

    DeclarativeTransactionDemo SpringDAODemo.java

 

package onlyfun. caterpillar;
  
import org . springframework. context . ApplicationContext;
  
import org . springframework. context .
  
              support. ClassPathXmlApplicationContext;
  
public class SpringDAODemo {
  
    public static void main( String [ ] args) {
  
        ApplicationContext context =
  
            new ClassPathXmlApplicationContext(
  
                    "beans-config.xml" ) ;
  
           
  
        User user = new User( ) ;
  
           
  
        user. setName ( "caterpillar" ) ;
  
        user. setAge( new Integer ( 30) ) ;
  
           
  
        IUserDAO userDAO =
  
            ( IUserDAO) context . getBean( "userDAOProxy" ) ;
  
           
  
        userDAO. insert ( user) ;
  
           
  
        user = userDAO. find ( new Integer ( 1) ) ;
  
           
  
        System . out. println ( "name: " + user. getName ( ) ) ;
  
    }
  
}

    您也可以設置不同的TransactionInterceptor來得到更多的管理細節,例如:

 

< ? xml version = "1.0" encoding = "UTF-8" ? >
  
< beans xmlns= "http://www.springframework.org/schema/beans"
  
  xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
  
  xsi:schemaLocation=
"http://www.springframework.org/schema/beans
  
  http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
>
  
     
  
    < bean id= "dataSource"
  
          class=
"org.springframework.jdbc.
  
                   → datasource.DriverManagerDataSource"

  
          destroy-method= "close" >
  
        < property name= "driverClassName"
  
                  value= "com.mysql.jdbc.Driver" / >
  
        < property name= "url"
  
                  value= "jdbc:mysql://localhost:3306/demo" / >
  
        < property name= "username" value= "caterpillar" / >
  
        < property name= "password" value= "123456" / >
  
    < / bean>
  
    < bean id= "transactionManager"
  
          class=
"org.springframework.jdbc.
  
                   → datasource.DataSourceTransactionManager"
>
  
        < property name= "dataSource" ref= "dataSource" / >
  
    < / bean>
  
       
  
    < bean id= "userDAO"
  
          class= "onlyfun.caterpillar.UserDAO" >
  
        < property name= "dataSource" ref= "dataSource" / >
  
    < / bean>
  
    < bean id= "transactionInterceptor"
  
          class=
"org.springframework.transaction.
  
                   → interceptor.TransactionInterceptor"
>
  
        < property name= "transactionManager" ref= "transactionManager" / >
  
        < property name= "transactionAttributeSource"
  
                  value=
"onlyfun.caterpillar.UserDAO.insert*=
  
                            → PROPAGATION_REQUIRED "
/ >
  
    < / bean>
  
       
  
    < bean id= "userDAOProxy"
  
          class=
"org.springframework.aop.
  
                   → framework.ProxyFactoryBean"
>
  
        < property name= "proxyInterfaces" >
  
            < list>
  
                < value> onlyfun. caterpillar. IUserDAO< / value>
  
            < / list>
  
        < / property>
  
        < property name= "target" ref= "userDAO" / >
  
        < property name= "interceptorNames" >
  
            < list>
  
                < value> transactionInterceptor< / value>
  
            < / list>
  
        < / property>
  
    < / bean>

    同樣的,由於不再於設置文件中設置代理對象,所以直接取得"userDAO"實例進行操作即可。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章