1. 程式人生 > >Spring 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進行註解就可以實現事務的管理和配置,減少了程式碼之間的耦合,配置也很方便,很大程度上提升了我們的開發效率.

相關推薦

Spring JdbcTemplate 事務管理

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

jdbctemplate事務管理

JdbcTemplate與事務上例中的JdbcTemplate操作採用的是JDBC預設的AutoCommit模式,也就是說我們還無法保證資料操作的原子性(要麼全部生效,要麼全部無效),如:JdbcTemplate jdbcTemplate = new JdbcTemplate

Java進階學習第二十四天(Spring框架:事務管理SpringHibernate整合)

一、事務控制 1、引入 使用者訪問 > Action > Service > Dao 如何保證: 在service中呼叫2次dao,其中一個dao執行失敗,整個操作要回滾 2、事務控制概述 ① 程式設計式事務控制:自己手動控制事務 Jdbc程式

Spring宣告式事務管理配置介紹

一、Spring宣告式事務配置的五種方式 前段時間對Spring的事務配置做了比較深入的研究,在此之間對Spring的事務配置雖說也配置過,但是一直沒有一個清楚的認識。通過這次的學習發覺Spring的事務配置只要把思路理清,還是比較好掌握的。 總結如下: Sprin

spring添加事務管理

transacti man pan ota ger ring 今天 異常 自己的 今天把項目中的事務管理配置完成,在這個過程中可謂一波三折,剛開始出現不少問題,最後自己都一一克服了。 今天在做spring配置的時候比較心急,總想著讓自己快速的配置完成,這

Spring中的事務管理

java編程 聲明式事務 body ram 屬性 per col 註解 配置數據源 配置註解 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTra

spring boot開啟事務管理,使用事務的回滾機制,使兩條插入語句一致

value nbsp tcl true 管理 配置 AI let dao spring boot 事務管理,使用事務的回滾機制 1:配置事務管理 在springboot 啟動類中添加 @EnableTransactionManagement //開啟事務管

第十二講:12,spring宣告式事務管理-註解式

1,複製專案spring404 ,改名spring404-3。修改BankServiceImpl類,添加註解,package com.cruise.service.impl;import org.springframework.transaction.annotation.Tra

第十一講:11.spring宣告式事務管理-xml方式

1,複製專案spring404 ,改名spring404-2,修改BankServiceImpl類,刪除宣告式事務的程式碼。宣告式事務管理的方式缺點是,事務程式碼嚴重嵌入邏輯程式碼中 package com.cruise.service.impl; import org.springframewor

spring宣告式事務管理方式( 基於tx和aop名字空間的xml配置[email&#

轉自:https://www.cnblogs.com/niceyoo/p/8732891.html 1. 宣告式事務管理分類 宣告式事務管理也有兩種常用的方式, 一種是基於tx和aop名字空間的xml配置檔案,另一種就是基於@Transactional註解。 顯然基於註解的方式更簡單

Spring整合MyBatis 事務管理

前言         spring事務管理包含兩種情況,程式設計式事務、宣告式事務。而宣告式事務又包括基於註解@Transactional和tx+aop的方式。那麼本文先分析程式設計式註解事務和基於註解的宣告式事務。 程式設計式事務管理使用TransactionTempla

spring service層事務管理小結

前言: 選擇spring作為開發的框架,很大一部分因素是spring框架完善的事務處理機制,spring的事務實現主要分為兩種,一種是基於Dao層,另一種是基於Service層,前者是針對單個dao的持久化操作做了事務控制,控制粒度比較小,後者則是基於業務的原則性需求,將一個原子性業務的

spring學習系列 --- 事務管理

1、事務管理 ---- 主要是保證事務執行過程中,輸入的資料不出現問題 在開發過程中,就是可能會出現程式執行中斷,原因可能是程式bug,一塊程式想實現一個完整的功能,要是無法全部完成,反倒是會造成錯誤,所以希望這塊程式碼要麼是執行完成,要麼就是全部沒有執行,即出現執行部分的

spring的一個事務管理,在controller層和dao層都可以用

import org.springframework.transaction.support.DefaultTransactionDefinition; public PlatformTransactionManager getTransactionManager() { re

Spring框架的事務管理之程式設計式的事務管理(瞭解)

1. 說明:Spring為了簡化事務管理的程式碼:提供了模板類 TransactionTemplate,所以手動程式設計的方式來管理事務,只需要使用該模板類即可! 2. 手動程式設計方式的具體步驟如下: 1. 步驟一:配置一個事務管理器,Spring使用PlatformT

Oracle資料一致性事務管理

資料一致性和事務 Oracle中的資料一致性 當從A表取一條資料新增到B表時,需先刪除A表資料,再新增B表資料, 如果第二條操作出異常時,就造成了資料不一致。 Oracle中的事務 事務是保證資料一致性的重要手段,試圖改變資料庫狀態的多個動作應該視作一個密不可分

Spring框架的事務管理的基本概念

1. 事務:指的是邏輯上一組操作,組成這個事務的各個執行單元,要麼一起成功,要麼一起失敗! 2. 事務的特性 * 原子性 * 一致性 * 隔離性 * 永續性 3. 如果不考慮隔離性,引發安全性問題 * 讀問題: * 髒讀: * 不可重複

Spring框架的事務管理之基於AspectJ的XML方式(重點掌握)

1. 步驟一:恢復轉賬開發環境(轉賬開發環境見“https://www.cnblogs.com/wyhluckdog/p/10137283.html”) 2.步驟二:引入AOP的開發包3.步驟三:引入applicationContext.xml配置檔案  * 配置檔案的基本配置為: <?xml

Spring框架的事務管理之基於AspectJ的註解方式(重點掌握,最簡單的方式)

1. 步驟一:恢復轉賬的開發環境(具體開發環境實現見:https://www.cnblogs.com/wyhluckdog/p/10137283.html)2. 步驟二:applicationContext的基本配置為: <?xml version="1.0" encoding="UTF-8"?

詳解spring中的事務管理(程式設計式的事務管理,宣告式的事務管理

spring提供的事務管理API 1. PlatformTransactionManager:平臺事務管理器. commit(TransactionStatus status) getTransaction(TransactionDefinition de