Spring JdbcTemplate 與 事務管理

jopen 9年前發布 | 245K 次閱讀 Spring JEE框架 JDBCTemplate

Spring JdbcTemplate 事務管理

Spring的JDBC框架能夠承擔資源管理和異常處理的工作,從而簡化我們的JDBC代碼,
讓我們只需編寫從數據庫讀寫數據所必需的代碼。Spring把數據訪問的樣板代碼隱藏到模板類之下,
結合Spring的事務管理,可以大大簡化我們的代碼.

Spring提供了3個模板類:

JdbcTemplate:Spring里最基本的JDBC模板,利用JDBC和簡單的索引參數查詢提供對數據庫的簡單訪問。
NamedParameterJdbcTemplate:能夠在執行查詢時把值綁定到SQL里的命名參數,而不是使用索引參數。
SimpleJdbcTemplate:利用Java 5的特性,比如自動裝箱、通用(generic)和可變參數列表來簡化JDBC模板的使用。
具體使用哪個模板基本上取決于個人喜好。


使用Spring的JdbcTemplate來實現簡單的增刪改查,首先建立測試數據表person
create table person(
id int not null primary key auto_increment,
name varchar(20) not null
)

導入依賴的jar包,由于測試中數據源使用的是dbcp數據源,需要以下jar包支持:
commons-logging.jar
commons-pool.jar
commons-dbcp.jar
同時還必須導入數據庫驅動jar包:mysql-connector-java-3.1.8-bin.jar

建立實體bean
Person.java

package com.royzhou.jdbc; 
 
public class PersonBean { 
    private int id; 
    private String name; 
 
    public PersonBean() { 
    } 
     
    public PersonBean(String name) { 
        this.name = name; 
    } 
     
    public PersonBean(int id, String name) { 
        this.id = id; 
        this.name = name; 
    } 
      
    public int getId() { 
        return id; 
    } 
 
    public void setId(int id) { 
        this.id = id; 
    } 
 
    public String getName() { 
        return name; 
    } 
 
    public void setName(String name) { 
        this.name = name; 
    } 
     
    public String toString() { 
        return this.id + ":" + this.name; 
    } 
}

 

接口類:
PersonService.java

package com.royzhou.jdbc; 
 
import java.util.List; 
 
public interface PersonService { 
     
    public void addPerson(PersonBean person); 
     
    public void updatePerson(PersonBean person); 
     
    public void deletePerson(int id); 
     
    public PersonBean queryPerson(int id); 
     
    public List<PersonBean> queryPersons(); 
}

實現類:
PersonServiceImpl.java

package com.royzhou.jdbc; 
 
import java.util.List; 
 
import javax.sql.DataSource; 
import java.sql.Types; 
 
import org.springframework.jdbc.core.JdbcTemplate; 
 
public class PersonServiceImpl implements PersonService { 
 
    private JdbcTemplate jdbcTemplate; 
     
    /**
     * 通過Spring容器注入datasource
     * 實例化JdbcTemplate,該類為主要操作數據庫的類
     * @param ds
     */ 
    public void setDataSource(DataSource ds) { 
        this.jdbcTemplate = new JdbcTemplate(ds); 
    } 
     
    public void addPerson(PersonBean person) { 
        /**
         * 第一個參數為執行sql
         * 第二個參數為參數數據
         * 第三個參數為參數類型
         */ 
        jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR}); 
    } 
 
    public void deletePerson(int id) { 
        jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER}); 
    } 
 
    public PersonBean queryPerson(int id) { 
        /**
         * new PersonRowMapper()是一個實現RowMapper接口的類,
         * 執行回調,實現mapRow()方法將rs對象轉換成PersonBean對象返回
         */ 
        PersonBean pb = (PersonBean) jdbcTemplate.queryForObject("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper()); 
        return pb; 
    } 
 
    @SuppressWarnings("unchecked") 
    public List<PersonBean> queryPersons() { 
        List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper()); 
        return pbs; 
    } 
 
    public void updatePerson(PersonBean person) { 
        jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER}); 
    } 
}

PersonRowMapper.java

package com.royzhou.jdbc; 
 
import java.sql.ResultSet; 
import java.sql.SQLException; 
 
import org.springframework.jdbc.core.RowMapper; 
 
public class PersonRowMapper implements RowMapper { 
    //默認已經執行rs.next(),可以直接取數據 
    public Object mapRow(ResultSet rs, int index) throws SQLException { 
        PersonBean pb = new PersonBean(rs.getInt("id"),rs.getString("name")); 
        return pb; 
    } 
}

我們需要在bean.xml中配置DataSource,并且將datasource注入到我們的業務類中

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:context="http://www.springframework.org/schema/context"  
       xmlns:aop="http://www.springframework.org/schema/aop" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd 
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> 
 
     <context:property-placeholder location="classpath:jdbc.properties"/> 
     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
        <property name="driverClassName" value="${driverClassName}"/> 
        <property name="url" value="${url}"/> 
        <property name="username" value="${username}"/> 
        <property name="password" value="${password}"/> 
         <!-- 連接池啟動時的初始值 --> 
         <property name="initialSize" value="${initialSize}"/> 
         <!-- 連接池的最大值 --> 
         <property name="maxActive" value="${maxActive}"/> 
         <!-- 最大空閑值.當經過一個高峰時間后,連接池可以慢慢將已經用不到的連接慢慢釋放一部分,一直減少到maxIdle為止 --> 
         <property name="maxIdle" value="${maxIdle}"/> 
         <!--  最小空閑值.當空閑的連接數少于閥值時,連接池就會預申請去一些連接,以免洪峰來時來不及申請 --> 
         <property name="minIdle" value="${minIdle}"/> 
     </bean> 
     
</beans>

jdbc.properties

driverClassName=org.gjt.mm.mysql.Driver 
url=jdbc:mysql://localhost:3306/royzhou?useUnicode=true&characterEncoding=UTF-8 
username=root 
password=123456 
initialSize=1 
maxActive=500 
maxIdle=2 
minIdle=1

 

編寫我們的測試類:TestJdbcTemplate.java

package com.royzhou.jdbc; 

import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContext; 
 
public class TestJdbcTemplate { 
    public static void main(String[] args) { 
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml"); 
        PersonService ps = (PersonService)ctx.getBean("personService"); 
        ps.addPerson(new PersonBean("royzhou")); 
        PersonBean pb = ps.queryPerson(1); 
        System.out.println(pb); 
        pb.setName("haha"); 
        ps.updatePerson(pb); 
        pb = ps.queryPerson(1); 
        System.out.println(pb); 
        ps.deletePerson(1); 
        pb = ps.queryPerson(1); 
        System.out.println(pb); 
    } 
}

上面代碼先插入一條記錄,然后修改,之后刪除,運行之后出現異常,異常信息:
EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

難道Spring的queryForObject在查找不到記錄的時候會拋出異常,看了一下Spring的源代碼 發現確實如此:

public Object queryForObject(String sql, Object[] args, int[] argTypes, RowMapper rowMapper) throws DataAccessException { 
        List results = (List) query(sql, args, argTypes, new RowMapperResultSetExtractor(rowMapper, 1)); 
        return DataAccessUtils.requiredUniqueResult(results); 
    } 
 
    public Object queryForObject(String sql, Object[] args, RowMapper rowMapper) throws DataAccessException { 
        List results = (List) query(sql, args, new RowMapperResultSetExtractor(rowMapper, 1)); 
        return DataAccessUtils.requiredUniqueResult(results); 
    } 
 
    public Object queryForObject(String sql, RowMapper rowMapper) throws DataAccessException { 
        List results = query(sql, rowMapper); 
        return DataAccessUtils.requiredUniqueResult(results); 
    } 
 
    public static Object requiredUniqueResult(Collection results) throws IncorrectResultSizeDataAccessException { 
        int size = (results != null ? results.size() : 0); 
        if (size == 0) { 
            throw new EmptyResultDataAccessException(1); // 問題在這里 
        } 
        if (!CollectionUtils.hasUniqueObject(results)) { 
            throw new IncorrectResultSizeDataAccessException(1, size); 
        } 
        return results.iterator().next(); 
    }

 

發現當查找不到記錄是,requiredUniqueResult方法做了判斷,拋出異常, 想不明白為什么Spring要在這里做這樣的判斷,為啥不返回null????

重新修改PersonServiceImple類,把queryPerson方法改為使用列表查詢的方式再去根據index取
PersonServiceImpl.java

package com.royzhou.jdbc; 
 
import java.util.List; 
 
import javax.sql.DataSource; 
import java.sql.Types; 
 
import org.springframework.jdbc.core.JdbcTemplate; 
 
public class PersonServiceImpl implements PersonService { 
 
    private JdbcTemplate jdbcTemplate; 
     
    /**
     * 通過Spring容器注入datasource
     * 實例化JdbcTemplate,該類為主要操作數據庫的類
     * @param ds
     */ 
    public void setDataSource(DataSource ds) { 
        this.jdbcTemplate = new JdbcTemplate(ds); 
    } 
     
    public void addPerson(PersonBean person) { 
        /**
         * 第一個參數為執行sql
         * 第二個參數為參數數據
         * 第三個參數為參數類型
         */ 
        jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR}); 
    } 
 
    public void deletePerson(int id) { 
        jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER}); 
    } 
 
    @SuppressWarnings("unchecked") 
    public PersonBean queryPerson(int id) { 
        /**
         * new PersonRowMapper()是一個實現RowMapper接口的類,
         * 執行回調,實現mapRow()方法將rs對象轉換成PersonBean對象返回
         */ 
        List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper()); 
        PersonBean pb = null; 
        if(pbs.size()>0) { 
            pb = pbs.get(0); 
        } 
        return pb; 
    } 
 
    @SuppressWarnings("unchecked") 
    public List<PersonBean> queryPersons() { 
        List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper()); 
        return pbs; 
    } 
 
    public void updatePerson(PersonBean person) { 
        jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER}); 
    } 
}

再次運行測試類,輸出:
1:royzhou
1:haha
null

得到預期的結果.

從上面代碼可以看出,使用Spring提供的JDBCTemplate類很大程度減少了我們的代碼量,
比起以前我們寫JDBC操作,需要先獲取Connection,然后是PreparedStatement,再到Result,
使用Spring JDBCTemplate寫出來的代碼看起來更加簡潔,開發效率也比較快.

在數據庫的操作中,事務是一個重要的概念,舉個例子:

大概每個人都有轉賬的經歷。當我們從A帳戶向B帳戶轉100元后,銀行的系統會從A帳戶上扣除100而在B帳戶上加100,這是一般的正常現象。
但是一旦系統出錯了怎么辦呢,這里我們假設可能會發生兩種情況:
(1)A帳戶上少了100元,但是B帳戶卻沒有多100元。
(2)B帳戶多了100元錢,但是A帳戶上卻沒有被扣錢。
這種錯誤一旦發生就等于出了大事,那么再假如一下,你要轉賬的是1億呢?
所以上面的兩種情況分別是你和銀行不愿意看到的,因為誰都不希望出錯。那么有沒有什么方法保證一旦A帳戶上沒有被扣錢而B帳戶上也沒有被加錢;
或者A帳戶扣了100元而B帳戶準確無誤的加上100元呢。也就是說要么轉賬順利的成功進行,要么不轉賬呢?可以,這就是數據庫事務機制所要起到的作用和做的事情。

Spring對事務的管理有豐富的支持,Spring提供了編程式配置事務和聲明式配置事務:

聲明式事務有以下兩種方式
一種是使用Annotation注解的方式(官方推薦)
一種是基于Xml的方式

采用任何一種方式我們都需要在我們的bean.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" 
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd 
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd 
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> 
 
    <context:property-placeholder location="classpath:jdbc.properties" /> 
    <bean id="dataSource" 
        class="org.apache.commons.dbcp.BasicDataSource" 
        destroy-method="close"> 
        <property name="driverClassName" value="${driverClassName}" /> 
        <property name="url" value="${url}" /> 
        <property name="username" value="${username}" /> 
        <property name="password" value="${password}" /> 
        <!-- 連接池啟動時的初始值 --> 
        <property name="initialSize" value="${initialSize}" /> 
        <!-- 連接池的最大值 --> 
        <property name="maxActive" value="${maxActive}" /> 
        <!-- 最大空閑值.當經過一個高峰時間后,連接池可以慢慢將已經用不到的連接慢慢釋放一部分,一直減少到maxIdle為止 --> 
        <property name="maxIdle" value="${maxIdle}" /> 
        <!--  最小空閑值.當空閑的連接數少于閥值時,連接池就會預申請去一些連接,以免洪峰來時來不及申請 --> 
        <property name="minIdle" value="${minIdle}" /> 
    </bean> 
 
    <bean id="personService" 
        class="com.royzhou.jdbc.PersonServiceImpl"> 
        <property name="dataSource" ref="dataSource"></property> 
    </bean> 
     
    <bean id="txManager" 
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
        <property name="dataSource" ref="dataSource" /> 
    </bean> 
     
    <tx:annotation-driven transaction-manager="txManager" />  
</beans>

<tx:annotation-driven transaction-manager="txManager" />  這句話的作用是注冊事務注解處理器
定義好配置文件后我們只需要在我們的類上加上注解@Transactional,就可以指定這個類需要受Spring的事務管理
默認Spring為每個方法開啟一個事務,如果方法發生運行期錯誤unchecked(RuntimeException),事務會進行回滾
如果發生checked Exception,事務不進行回滾.

例如在下面的例子中,我們為PersonServiceImpl添加事務支持.

package com.royzhou.jdbc; 
 
import java.util.List; 
 
import javax.sql.DataSource; 
import java.sql.Types; 
 
import org.springframework.jdbc.core.JdbcTemplate; 
import org.springframework.transaction.annotation.Transactional; 
 
@Transactional 
public class PersonServiceImpl implements PersonService { 
 
    private JdbcTemplate jdbcTemplate; 
     
    /**
     * 通過Spring容器注入datasource
     * 實例化JdbcTemplate,該類為主要操作數據庫的類
     * @param ds
     */ 
    public void setDataSource(DataSource ds) { 
        this.jdbcTemplate = new JdbcTemplate(ds); 
    } 
     
    public void addPerson(PersonBean person) { 
        /**
         * 第一個參數為執行sql
         * 第二個參數為參數數據
         * 第三個參數為參數類型
         */ 
        jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR}); 
        throw new RuntimeException("運行期例外"); 
    } 
 
    public void deletePerson(int id) { 
        jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER}); 
    } 
 
    @SuppressWarnings("unchecked") 
    public PersonBean queryPerson(int id) { 
        /**
         * new PersonRowMapper()是一個實現RowMapper接口的類,
         * 執行回調,實現mapRow()方法將rs對象轉換成PersonBean對象返回
         */ 
        List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper()); 
        PersonBean pb = null; 
        if(pbs.size()>0) { 
            pb = pbs.get(0); 
        } 
        return pb; 
    } 
 
    @SuppressWarnings("unchecked") 
    public List<PersonBean> queryPersons() { 
        List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper()); 
        return pbs; 
    } 
 
    public void updatePerson(PersonBean person) { 
        jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER}); 
    } 
}

在addPerson方法中我們拋出了一個運行期例外,以此來檢查Spring的事務管理.

編寫測試類運行:

package com.royzhou.jdbc; 
 
import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContext; 
 
public class TestJdbcTemplate { 
    public static void main(String[] args) { 
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml"); 
        PersonService ps = (PersonService)ctx.getBean("personService"); 
        ps.addPerson(new PersonBean("royzhou")); 
    } 
}

再次測試修改為拋出checked Exception

package com.royzhou.jdbc; 
 
import java.util.List; 
 
import javax.sql.DataSource; 
import java.sql.Types; 
 
import org.springframework.jdbc.core.JdbcTemplate; 
import org.springframework.transaction.annotation.Transactional; 
 
@Transactional 
public class PersonServiceImpl implements PersonService { 
 
    private JdbcTemplate jdbcTemplate; 
     
    /**
     * 通過Spring容器注入datasource
     * 實例化JdbcTemplate,該類為主要操作數據庫的類
     * @param ds
     */ 
    public void setDataSource(DataSource ds) { 
        this.jdbcTemplate = new JdbcTemplate(ds); 
    } 
     
    public void addPerson(PersonBean person) throws Exception { 
        /**
         * 第一個參數為執行sql
         * 第二個參數為參數數據
         * 第三個參數為參數類型
         */ 
        jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR}); 
        throw new Exception("checked 例外"); 
    } 
 
    public void deletePerson(int id) { 
        jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER}); 
    } 
 
    @SuppressWarnings("unchecked") 
    public PersonBean queryPerson(int id) { 
        /**
         * new PersonRowMapper()是一個實現RowMapper接口的類,
         * 執行回調,實現mapRow()方法將rs對象轉換成PersonBean對象返回
         */ 
        List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper()); 
        PersonBean pb = null; 
        if(pbs.size()>0) { 
            pb = pbs.get(0); 
        } 
        return pb; 
    } 
 
    @SuppressWarnings("unchecked") 
    public List<PersonBean> queryPersons() { 
        List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper()); 
        return pbs; 
    } 
 
    public void updatePerson(PersonBean person) { 
        jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER}); 
    } 
}


后臺輸出異常:java.lang.Exception: checked 例外
查看數據庫發現數據插入,說明事務沒有進行了回滾.

說明了Spring的事務支持默認只對運行期異常(RuntimeException)進行回滾,這里可能有個疑問,我們執行sql操作的時候會發生sql異常,不屬于運行期異常,那Spring是怎么進行事務回滾的呢 ????
查看了一下JdbcTemplate的源代碼發現,JdbcTemplate的處理方法如下:

public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss) throws DataAccessException { 
    if (logger.isDebugEnabled()) { 
        logger.debug("Executing SQL batch update [" + sql + "]"); 
    } 
    return (int[]) execute(sql, new PreparedStatementCallback() { 
        public Object doInPreparedStatement(PreparedStatement ps) throws SQLException { 
            try { 
                int batchSize = pss.getBatchSize(); 
                if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) { 
                    for (int i = 0; i < batchSize; i++) { 
                        pss.setValues(ps, i); 
                        ps.addBatch(); 
                    } 
                    return ps.executeBatch(); 
                } 
                else { 
                    int[] rowsAffected = new int[batchSize]; 
                    for (int i = 0; i < batchSize; i++) { 
                        pss.setValues(ps, i); 
                        rowsAffected[i] = ps.executeUpdate(); 
                    } 
                    return rowsAffected; 
                } 
            } 
            finally { 
                if (pss instanceof ParameterDisposer) { 
                    ((ParameterDisposer) pss).cleanupParameters(); 
                } 
            } 
        } 
    }); 
}

 

在代碼中捕獲了SQLException然后拋出一個org.springframework.dao.DataAcceddException,該異常繼承自org.springframework.core.NestedRuntimeException,NestedRuntimeException
是一個繼承自RuntimeException的抽象類,Spring jdbcTemplate處理發生異常處理后拋出來得異常基本上都會繼承NestedRuntimeException,看完之后才確信了Spring默認只對RuntimeException進行回滾

當然我們可可以修改Spring的默認配置,當發生RuntimeException我們也可以不讓他進行事務回滾
只需要加上一個@Transactional(noRollbackFor=RuntimeException.class)
注意@Transactional只能針對public屬性范圍內的方法添加

package com.royzhou.jdbc; 
 
import java.util.List; 
 
import javax.sql.DataSource; 
import java.sql.Types; 
 
import org.springframework.jdbc.core.JdbcTemplate; 
import org.springframework.transaction.annotation.Transactional; 
 
@Transactional 
public class PersonServiceImpl implements PersonService { 
 
    private JdbcTemplate jdbcTemplate; 
     
    /**
     * 通過Spring容器注入datasource
     * 實例化JdbcTemplate,該類為主要操作數據庫的類
     * @param ds
     */ 
    public void setDataSource(DataSource ds) { 
        this.jdbcTemplate = new JdbcTemplate(ds); 
    } 
     
    @Transactional(noRollbackFor=RuntimeException.class) 
    public void addPerson(PersonBean person) { 
        /**
         * 第一個參數為執行sql
         * 第二個參數為參數數據
         * 第三個參數為參數類型
         */ 
        jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR}); 
        throw new RuntimeException("運行期例外"); 
    } 
 
    public void deletePerson(int id) { 
        jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER}); 
    } 
 
    @SuppressWarnings("unchecked") 
    public PersonBean queryPerson(int id) { 
        /**
         * new PersonRowMapper()是一個實現RowMapper接口的類,
         * 執行回調,實現mapRow()方法將rs對象轉換成PersonBean對象返回
         */ 
        List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper()); 
        PersonBean pb = null; 
        if(pbs.size()>0) { 
            pb = pbs.get(0); 
        } 
        return pb; 
    } 
 
    @SuppressWarnings("unchecked") 
    public List<PersonBean> queryPersons() { 
        List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper()); 
        return pbs; 
    } 
 
    public void updatePerson(PersonBean person) { 
        jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER}); 
    } 
}
package com.royzhou.jdbc; 
 
import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContext; 
 
public class TestJdbcTemplate { 
    public static void main(String[] args) { 
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml"); 
        PersonService ps = (PersonService)ctx.getBean("personService"); 
        ps.addPerson(new PersonBean("royzhou")); 
    } 
}

 

后臺拋出異常,查看數據庫,記錄插入進去了,說明我們配置事務不對RuntimeException回滾生效了.
既然可以配置不對RuntimeException回滾,那我們也可以配置對Exception進行回滾,主要用到的是
@Transactional(rollbackFor=Exception.class)

對于一些查詢工作,因為不需要配置事務支持,我們配置事務的傳播屬性:
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

readOnly=true表示事務中不允許存在更新操作.

關于事務的傳播屬性有下面幾種配置:
REQUIRED:業務方法需要在一個事務中運行,如果方法運行時,已經處于一個事務中,那么加入到該事務中,否則自己創建一個新的事務.(Spring默認的事務傳播屬性)
NOT_SUPPORTED:聲明方法不需要事務,如果方法沒有關聯到一個事務,容器不會為它開啟事務,如果方法在一個事務中被調用,該事務被掛起,在方法調用結束后,原先的事務便會恢復執行
REQUIRESNEW:不管是否存在事務,業務方法總會為自己發起一個新的事務,如果方法運行時已經存在一個事務,則該事務會被掛起,新的事務被創建,知道方法執行結束,新事務才結束,原先的事務才恢復執行.
MANDATORY:指定業務方法只能在一個已經存在的事務中執行,業務方法不能自己發起事務,如果業務方法沒有在事務的環境下調用,則容器會拋出異常
SUPPORTS:如果業務方法在事務中被調用,則成為事務中的一部分,如果沒有在事務中調用,則在沒有事務的環境下執行
NEVER:指定業務方法絕對不能在事務范圍內運行,否則會拋出異常.
NESTED:如果業務方法運行時已經存在一個事務,則新建一個嵌套的事務,該事務可以有多個回滾點,如果沒有事務,則按REQUIRED屬性執行. 注意:業務方法內部事務的回滾不會對外部事務造成影響,但是外部事務的回滾會影響內部事務


關于使用注解的方式來配置事務就到這里,
我們還可以使用另外一種方式實現事務的管理,通過xml文件的配置,主要通過AOP技術實現:

首先把我們的業務類的注解去掉:

package com.royzhou.jdbc; 
 
import java.util.List; 
 
import javax.sql.DataSource; 
import java.sql.Types; 
 
import org.springframework.jdbc.core.JdbcTemplate; 
 
public class PersonServiceImpl implements PersonService { 
 
    private JdbcTemplate jdbcTemplate; 
     
    /**
     * 通過Spring容器注入datasource
     * 實例化JdbcTemplate,該類為主要操作數據庫的類
     * @param ds
     */ 
    public void setDataSource(DataSource ds) { 
        this.jdbcTemplate = new JdbcTemplate(ds); 
    } 
     
    public void addPerson(PersonBean person) { 
        /**
         * 第一個參數為執行sql
         * 第二個參數為參數數據
         * 第三個參數為參數類型
         */ 
        jdbcTemplate.update("insert into person values(null,?)", new Object[]{person.getName()}, new int[]{Types.VARCHAR}); 
        throw new RuntimeException("運行期例外"); 
    } 
 
    public void deletePerson(int id) { 
        jdbcTemplate.update("delete from person where id = ?", new Object[]{id}, new int[]{Types.INTEGER}); 
    } 
     
    @SuppressWarnings("unchecked") 
    public PersonBean queryPerson(int id) { 
        /**
         * new PersonRowMapper()是一個實現RowMapper接口的類,
         * 執行回調,實現mapRow()方法將rs對象轉換成PersonBean對象返回
         */ 
        List<PersonBean> pbs = (List<PersonBean>)jdbcTemplate.query("select id,name from person where id = ?", new Object[]{id}, new PersonRowMapper()); 
        PersonBean pb = null; 
        if(pbs.size()>0) { 
            pb = pbs.get(0); 
        } 
        return pb; 
    } 
 
    @SuppressWarnings("unchecked") 
    public List<PersonBean> queryPersons() { 
        List<PersonBean> pbs = (List<PersonBean>) jdbcTemplate.query("select id,name from person", new PersonRowMapper()); 
        return pbs; 
    } 
 
    public void updatePerson(PersonBean person) { 
        jdbcTemplate.update("update person set name = ? where id = ?", new Object[]{person.getName(), person.getId()}, new int[]{Types.VARCHAR, Types.INTEGER}); 
    } 
}

 

然后我們需要在bean.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" 
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd 
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd 
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> 
 
    <context:property-placeholder location="classpath:jdbc.properties" /> 
    <bean id="dataSource" 
        class="org.apache.commons.dbcp.BasicDataSource" 
        destroy-method="close"> 
        <property name="driverClassName" value="${driverClassName}" /> 
        <property name="url" value="${url}" /> 
        <property name="username" value="${username}" /> 
        <property name="password" value="${password}" /> 
        <!-- 連接池啟動時的初始值 --> 
        <property name="initialSize" value="${initialSize}" /> 
        <!-- 連接池的最大值 --> 
        <property name="maxActive" value="${maxActive}" /> 
        <!-- 最大空閑值.當經過一個高峰時間后,連接池可以慢慢將已經用不到的連接慢慢釋放一部分,一直減少到maxIdle為止 --> 
        <property name="maxIdle" value="${maxIdle}" /> 
        <!--  最小空閑值.當空閑的連接數少于閥值時,連接池就會預申請去一些連接,以免洪峰來時來不及申請 --> 
        <property name="minIdle" value="${minIdle}" /> 
    </bean> 
 
    <bean id="personService" 
        class="com.royzhou.jdbc.PersonServiceImpl"> 
        <property name="dataSource" ref="dataSource"></property> 
    </bean> 
     
    <bean id="txManager" 
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
        <property name="dataSource" ref="dataSource" /> 
    </bean> 
     
    <!-- 定義事務傳播屬性 --> 
    <tx:advice id="txAdvice" transaction-manager="txManager"> 
        <tx:attributes> 
            <tx:method name="query*" propagation="NOT_SUPPORTED" read-only="true"/> 
            <tx:method name="*" propagation="REQUIRED"/> 
        </tx:attributes> 
    </tx:advice> 
     
    <aop:config> 
        <aop:pointcut id="transactionPointCut" expression="execution(* com.royzhou.jdbc..*.*(..))"/> 
        <aop:advisor pointcut-ref="transactionPointCut" advice-ref="txAdvice"/> 
    </aop:config> 
</beans>

 

從新運行測試類
后臺拋出RuntimeException異常,查看數據庫,沒有插入數據,說明事務進行回滾,XML方式的配置也生效了.


另外還有一個編程式的事務處理,但是它有些侵入性。通常我們的事務需求并沒有要求在事務的邊界上進行如此精確的控制。
我們一般采用"聲明式事務"。

總結一下:
事務是企業應用開發的重要組成部分,他使軟件更加可靠。它們確保一種要么全有要么全無的行為,防止數據不一致而導致的不可預測的錯誤發生。
它們同時也支持并發,防止并發應用線程在操作同一數據時互相影響。以前我們寫Jdbc代碼的時候,可能需要自己手動去開啟事務,然后方法執行結束之后
再去提交事務,全部都嵌套在我們的業務代碼之中,具有很強的侵入性....
使用Spring提供事務管理機制,我們只需要配置XML或使用Annotion進行注解就可以實現事務的管理和配置,減少了代碼之間的耦合,配置也很方便,很大程度上提升了我們的開發效率.


 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!