Spring框架學習六:Spring對JDBC的支持


JdbcTemplate簡介

為了使JDBC更加易於使用,Spring 在 JDBC API 上定義了一個抽象層,以此建立一個 JDBC 存取框架

作為 Spring JDBC 框架的核心,JDBC 模板的設計目的是為不同類型的 JDBC 操作提供模板方法。每個模板方法都能控制整個過程,並允許覆蓋過程中的特定任務。通過這種方式,可以在盡可能保留靈活性的情況下,將數據庫存取工作量降到最低。

 

在test Schema下創建一張名為 user 的表,表結構如下

 

#添加c3p0數據庫連接池
jdbc.user=root
jdbc.password=000
jdbc.jdbcUrl=jdbc:mysql:///test
jdbc.driverClass=com.mysql.jdbc.Driver

jdbc.initPoolSize=5
jdbc.maxPoolSize=10
    <!-- 導入資源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!-- 配置 c3p0 數據源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
        
        <property name="initialPoolSize" value="${jdbc.initPoolSize}"/>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
    </bean>
    
    <!-- 配置 Spring 的 jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>    
    </bean>
    
    <!-- 還可以配置 NamedParameterJdbcTemplate,該對象可以使用具名參數,其沒有無參構造器,所以必須為其構造器指定參數 -->
    <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg ref="dataSource"></constructor-arg>
    </bean>
package com.bupt.springtest.jdbc;

public class User
{
    private int uid;
    private String name;
    private int age;
    public int getUid()
    {
        return uid;
    }
    public void setUid(int uid)
    {
        this.uid = uid;
    }
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public int getAge()
    {
        return age;
    }
    public void setAge(int age)
    {
        this.age = age;
    }
    @Override
    public String toString()
    {
        return "User [uid=" + uid + ", name=" + name + ", age=" + age + "]";
    }
}
package com.bupt.springtest.jdbc;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

public class JDBCTest
{
    ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
    
    //在jdbcTemplate中使用具名參數
    NamedParameterJdbcTemplate namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);
    
    /*
     * 執行INSERT, UPDATE, DELETE
     */
    public void testUpdate() throws SQLException
    {
        String sql = "UPDATE user SET name = ? WHERE uid = ?";
        jdbcTemplate.update(sql, "jimmy", 6);
    }
    
    /*
     * 執行批量更新:批量的INSERT, UPDATE, DELETE
     * 最后一個參數是 Object[]的 List 類型:因為修改一條記錄需要一個Object數組,多條即需要多個Object數組
     */
    public void testBatchUpdate()
    {
        String sql = "INSERT INTO user(uid, name, age) VALUES(?,?,?)";
        
        List<Object[]> batchArgs = new ArrayList<>();
        
        batchArgs.add(new Object[]{7, "A", 28});
        batchArgs.add(new Object[]{8, "B", 20});
        batchArgs.add(new Object[]{9, "C", 30});
        
        jdbcTemplate.batchUpdate(sql, batchArgs);
    }
    
    /*
     * 從數據庫獲取一條記錄,實際得到對應的一個對象
     * 類跟表對應,對象跟記錄對應
     * 調用queryForObject(String sql, RowMapper<User> rowMapper, Object... args)方法
     * 1. 其中的RowMapper指定如何映射結果集的行,常用實現類為 BeanPropertyRowMapper
     * 2. 使用SQL中列的別名完成列名和類的屬性名完成映射
     * 如:若表中定義的name為 user_name,而在User類中定義為name,則sql語句應寫為
     * "SELECT uid, user_name name, age FROM user WHERE uid = ?"
     * 3. 不支持級聯屬性,即表中關聯元素不能查詢到其他表中的數據
     */
    public void testQuery4Object()
    {
        String sql = "SELECT * FROM user WHERE uid = ?";
        
        RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
        User user = jdbcTemplate.queryForObject(sql, rowMapper, 1);
        
        System.out.println(user);
    }

    /*
     * 查詢實體類的集合
     * 注意調用的不是 queryForList 方法
     */
    public void testQuery4List()
    {
        String sql = "SELECT uid, name, age FROM user WHERE uid > ?";
        RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
        List<User> users = jdbcTemplate.query(sql, rowMapper, 5);
        
        System.out.println(users);
    }
    
    /*
     * 獲取單個列的值或做統計查詢
     */
    public void testQueryForObject()
    {
        String sql = "SELECT count(uid) FROM user";
//        String sql = "SELECT name FROM user WHERE uid = ?";
//        String name = jdbcTemplate.queryForObject(sql, String.class, 2);
        long count = jdbcTemplate.queryForObject(sql, Long.class);
        
        System.out.println(count);
    }
    
    /*
     * 在經典的JDBC用法中, SQL參數是用占位符 ? 表示,並且受到位置的限制。定位參數的問題在於,一旦參數的順序發生變化,就必須改變參數綁定
     * 在Spring JDBC 框架中,綁定 SQL 參數的另一種選擇是使用具名參數(named parameter),它可以為參數起名字
     * 具名參數:SQL按名字(以冒號開頭)而不是按位置進行指定。具名參數更易於維護,也提升了可讀性。具名參數由框架類在運行時用占位符取代
     * 具名參數只在NamedParameterJdbcTemplate中得到支持
     * 1. 好處:若有多個參數,則不用去對應位置,直接對應參數名,便於維護
     * 2. 缺點:較為麻煩
     */
    public void testNamedParameterJdbcTemplate()
    {
        String sql = "INSERT INTO user VALUES(:id, :nm, :age)";
        
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("id", 10);
        paramMap.put("nm", "elle");
        paramMap.put("age", 30);
        
        namedParameterJdbcTemplate.update(sql, paramMap);
    }
    
    /*
     * 使用具名參數時,可以使用update(String sql, SqlParameterSource paramSource)方法進行更新操作
     * 1. SQL語句中的參數和類的屬性一致
     * 2. 使用 SqlParameterSource的 BeanPropertySqlParameterSource 實現類作為參數
     */
    @Test
    public void testNamedParameterJdbcTemplate1()
    {
        String sql = "INSERT INTO user VALUES(:uid, :name, :age)";
        
        User user = new User();
        user.setUid(11);
        user.setName("lock");
        user.setAge(28);
        
        SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);
        namedParameterJdbcTemplate.update(sql, parameterSource);
    }
}

 


 

Spring 的事務管理

作為企業級應用程序框架,Spring 在不同的事務管理 API 之上定義了一個抽象層。而應用程序開發人員不必了解底層的事務管理 API,就可以使用 Spring 的事務管理機制。

Spring 既支持編程式事務管理,也支持聲明式事務管理。

編程式事務管理:將事務管理代碼嵌入到業務方法中來控制事務的提交和回滾。在編程式管理事務時,必須在每個事物操作中包含額外的事務管理代碼。

聲明式事務管理:大多數情況下比編程式事務管理更好用。它將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理。事務管理作為一種橫切關注點,可以通過 AOP 方法模塊化。Spring 通過 Spring AOP 框架支持聲明式事務。

Spring 從不同的事務管理 API 中抽象了一整套的事務機制。開發人員不必了解底層事務 API,就可以利用這些事務機制。有了這些事務機制,事務管理代碼就能獨立於特定的事務技術了。Spring 的核心事務管理抽象是 org.springframework.transaction.PlatformTransactionManager,它為事務管理封裝了一組獨立於技術的方法。無論使用 Spring 的哪種事務管理策略(編程式或聲明式),事務管理器都是必須的。

通過代碼來說明Spring 對事務的支持,也學習下 JdbcTemplate 正真在實際中如何使用,我們先在test數據庫中新建三張表

   account表              book表          book_stock表

    

    <context:component-scan base-package="com.bupt.springtest.tx"></context:component-scan>

    <!-- 導入資源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!-- 配置c3p0數據源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
    </bean>
    
    <!-- 配置Spring的 jdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>    
    </bean>
package com.bupt.springtest.tx;

public interface BookShopDao
{
    //根據書號獲取書的單價
    public int findBookPriceByIsbn(String isbn);
    //更新書的庫存,使書號對應的庫存減1
    public void updateBookStock(String isbn);
    //更新用戶的賬戶余額:使 username 的 balance - price
    public void updateUserAccount(String username, int price);
}
package com.bupt.springtest.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookShopDao")
public class BookShopDaoImp implements BookShopDao
{
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    public int findBookPriceByIsbn(String isbn)
    {
        String sql ="SELECT price FROM book WHERE isbn = ?";
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(String isbn)
    {
        //檢查書的庫存是否足夠,若不夠,則拋出異常
        String sql1 = "SELECT stock FROM book_stock WHERE isbn = ?";
        int stock = jdbcTemplate.queryForObject(sql1, Integer.class, isbn);
        if(stock <= 0)
        {
            throw new RuntimeException("庫存不足!");
        }
        String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
        jdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price)
    {
        //驗證余額是否足夠,若不夠,則拋出異常
        String sql1 = "SELECT balance FROM account WHERE username = ?";
        int balance = jdbcTemplate.queryForObject(sql1, Integer.class, username);
        if(balance < price)
        {
            throw new RuntimeException("余額不足!");
        }
String sql
= "UPDATE account SET balance = balance - ? WHERE username = ?"; jdbcTemplate.update(sql, price, username); } }
package com.bupt.springtest.tx;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

//對單個操作事務的測試
public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); BookShopDao bookShopDao = ctx.getBean(BookShopDao.class); @Test public void testBookShopDaoUpdateBookStock() { bookShopDao.updateBookStock("1001"); } public void testBookShopDaoFindPriceByIsbn() { System.out.println(bookShopDao.findBookPriceByIsbn("1001")); } public void testBookShopDaoUpdateUserAccount(){ bookShopDao.updateUserAccount("Tom", 100); } }

上面代碼分別測試了單個操作事務,現在我們把上面的操作組成一個完整的事務過程,即買書 —> 更新書庫存 —> 更新賬戶余額

package com.bupt.springtest.tx;

public interface BookShopService
{
    //買書事務
    public void purchase(String username, String isbn);
}
package com.bupt.springtest.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("bookShopService")
public class BookShopServiceImp implements BookShopService
{
    @Autowired
    private BookShopDao bookShopDao;
    
    @Override
    public void purchase(String username, String isbn)
    {
        //獲取書的單價
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //更新書的庫存
        bookShopDao.updateBookStock(isbn);
        //更新用戶余額
        bookShopDao.updateUserAccount(username, price);
    }
}
package com.bupt.springtest.tx;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTransactionTest
{
    ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    BookShopService bookShopService = ctx.getBean(BookShopService.class);
    
    @Test
    public void testBookShopService()
    {
     //執行此代碼前,先將account表中的balance改為60,即使用戶余額不足以購買該書 bookShopService.purchase(
"Tom", "1001"); } }

執行測試之后我們可以看到,這個操作並不滿足事務的要求,因為書的庫存更新了但賬戶里的錢沒有變,及違背了原子性。這種情況下,我們就需要 Spring 的事務管理來幫我們解決。

 

 

用事務通知聲明式地管理事務

Spring 允許簡單地使用 @Transactional 注解來標注事務方法。為了將 方法定義為支持事務處理,可以為方法添加 @Transactional 注解,根據 Spring AOP 基於代理機制,只能標注公有方法。可以在方法或者類級別上添加 @Transactional 注解,當把這個注解應用到類上時,這個類中的所有公有方法都會被定義為支持事務處理

在 bean 配置時,只需要在配置文件中啟用 <tx:annotation-driven> 元素,並為之指定事務管理器就可以了。如果事務處理器名稱是 transactionManager,就可以在 <tx:annotation-driven> 元素中省略 transaction-manager 屬性。這個元素會自動檢測該名稱的事務處理器。

 

默認情況下,如果被注解的數據庫操作方法發生了 Unchecked Exception 或 Error,所有的數據庫操作將回滾;如果發生的是 Checked Exception,默認情況下數據庫操作還是會提交的。

 

我們也可以來改變默認的規則:

1. 讓 Checked Exception 也回滾:在整個方法前加上 @Transactional(rollbackFor=Exception.class)

2. 讓 Unchecked Exception 不回滾:@Transactional(noRollbackFor=RuntimeException.class)

3. 不需要事務管理(只查詢)的方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)

注意:如果異常被 try-catch塊捕獲了,事物就不會回滾了,若想讓事務回滾必須再往外拋異常,即在catch模塊中繼續往外拋 Unchecked Exception。

 

下面用代碼來具體介紹用法

在ApplicationContext.xml文件中增加下面幾行代碼

    <!-- 配置事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
     
     <!-- 啟動注解事務 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

在 BookShopServiceImp 的 purchase() 方法上增加注解 @Transactional

package com.bupt.springtest.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImp implements BookShopService
{
    @Autowired
    private BookShopDao bookShopDao;
    
    //添加事務注解
 @Transactional
    @Override
    public void purchase(String username, String isbn)
    {
        //獲取書的單價
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //更新書的庫存
        bookShopDao.updateBookStock(isbn);
        //更新用戶余額
        bookShopDao.updateUserAccount(username, price);
    }
}
package com.bupt.springtest.tx;

import java.util.Arrays;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

//測試類
public class SpringTransactionTest { ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml"); BookShopService bookShopService = ctx.getBean(BookShopService.class); @Test public void testBookShopService() { bookShopService.purchase("Tom", "1001"); }

由結果可以看出,此時 purchase() 方法滿足事務的要求。

 

除了使用上述聲明事務的方法, Spring 還可以通過 tx Schema 中定義的 <tx:advice>  元素聲明事務通知。

事務管理其實是一種橫切關注點,聲明了事務通知后,就需要把它與切入點關聯起來。由於事務通知是在 <aop:config> 元素外部聲明的,所以它無法直接與切入點產生關聯。所以必須在 <aop:config> 元素中聲明一個增強器通知與切入點關聯起來。只有公有方法才能通過 Spring AOP 進行事務管理。

 

下面我們來看配置方法,將前面配置在xml文件中的 <tx:annotation-driven transaction-manager="transactionManager"/> 刪掉,增加如下配置代碼

     <!-- 聲明事務通知,讓所有方法都擁有事務 -->
     <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="*"/>
        <tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice> <!-- 聲明事務通知需要通知方法(即需要進行管理的方法) --> <aop:config>
     <!-- 配置事務切點,把事務切入點和事務屬性關聯起來 --> <aop:pointcut id="bookShopOperation" expression="execution(* com.bupt.springtest.tx.BookShopServiceImp.*(..))"/> <aop:advisor advice-ref="bookShopTxAdvice" pointcut-ref="bookShopOperation"/> </aop:config>

刪除 BookShopServiceImp.purchase() 方法上標注的 @Transactional 注解,測試后也能正常執行事務。

 

關於 <tx:method/> 屬性的說明

屬性 說明
name         方法名的匹配模式,通知根據該模式尋找匹配的方法。該屬性可以使用通配符(*)。也可以聲明為 (xxx*),即代表以xxx開頭的所有方法,表示符合此命名規則的方法作為一個事務(此屬性是必須要有的)
propagation 設定事務定義所用的傳播級別(默認值為REQUIRED)
isolation 設定事務的隔離級別(默認值為DEFAULT)
timeout 指定事務的超時(單位為秒),默認值為-1
read-only 指定事務是否只讀,若為true指示事務是只讀的(一般來講,對於只執行查詢的事務你會將該屬性設為true,如果出現了更新、插入或是刪除語句時只讀事務就會失敗),默認值為 false
no-rollback-for 以逗號分隔的異常類的列表,目標方法可以拋出這些異常而不會導致通知執行回滾,如:NullPointException,ArithmeticException...
rollback-for 以逗號分隔的異常類列表,當目標方法拋出這些異常時會導致通知執行回滾。默認情況下,該列表為空,因此不在 no-rollback-for列表中的任何運行時異常都會導致回滾  

 

 

 

Spring 中事務的傳播特性

當一個事務被另外一個事務調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啟一個新事物,並在自己的事務中運行

事務的傳播行為可以由傳播屬性來指定,Spring 定義了七種類型的傳播行為

傳播屬性 描述
REQUIRED 如果有事務在運行,當前的方法就在這個事務內運行,否則,就啟動一個新的事務,並在自己的事務內運行
REQUIRES_NEW 當前的方法必須啟動新的事務,並在它自己的事務內運行。如果有事務正在運行,應該將它掛起來
SUPPORTS 如果有事務在運行,當前的方法就在這個事務內運行,否則它可以不運行在事務中
NOT_SUPPORTED 當前方法不應該運行在事務中,如果有運行的事務,將它掛起來
MANDATORY 當前方法必須運行在事務內部,如果沒有正在運行的事務,就拋出異常
NEVER 當前的方法不應該運行在事務中,如果有運行的事務,就拋出異常
NESTED 如果有事務在運行,當前的方法就應該在這個事務的嵌套事務內運行。否則,就啟動一個新的事務,並在它自己的事務內運行

 

下面通過代碼來介紹傳播特性的用法

配置文件改回 <tx:annotation-driven transaction-manager="transactionManager"/>

package com.bupt.springtest.tx;

import java.util.List;

//新定義一個接口,表示用戶結算操作
public interface Cashier
{
    public void checkout(String name, List<String> isbn);
}
package com.bupt.springtest.tx;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("cashier")
public class CashierImp implements Cashier
{
    @Autowired
    private BookShopService bookShopService;
    
    //定義一個事物為 checkout
    @Transactional
    @Override
    public void checkout(String username, List<String> isbns)
    {
        for(String isbn : isbns)
        {
            bookShopService.purchase(username, isbn);
        }
    }
}

為 BookShopServiceImp.purchase 方法上的 @Transactional 注解添加屬性:propagation=Propagation.REQUIRED

@Service("bookShopService")
public class BookShopServiceImp implements BookShopService
{
    @Autowired
    private BookShopDao bookShopDao;
    
    //使用propagation指定事務的傳播行為,即當前事務方法被另外一個事物方法調用時
    //如何進行事務,默認取值為REQUIRED
    //添加事務注解
    @Transactional(propagation=Propagation.REQUIRED)
    @Override
    public void purchase(String username, String isbn)
    {
        //獲取書的單價
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //更新書的庫存
        bookShopDao.updateBookStock(isbn);
        //更新用戶余額
        bookShopDao.updateUserAccount(username, price);
    }
}
//測試代碼
public class SpringTransactionTest
{
    ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    Cashier cashier = ctx.getBean(Cashier.class);
    
    @Test
    public void testTransactionPropogation()
    {
        cashier.checkout("Tom", Arrays.asList("1001", "1002"));
    }
}

當 bookService 的 purchase() 方法被另外一個事務方法 checkout() 調用時,它默認會在現有事務內運行。這個默認的傳播行為就是 REQUIRED。因此從 checkout() 方法的開始到結束這個事務過程中,只有一個事務在執行。這個事務只在 checkout() 方法結束的時候被提交,若此時賬戶余額不夠支付兩本書的價格,就導致用戶一本書都買不了。這個流程可以用圖表示為:

 

 

 

另一種常見的傳播行為是 REQUIRES_NEW ,它表示該方法必須要啟動一個新事物,並在自己的事務內運行。如果有事務在運行,就應該先把它掛起。相應的代碼修改,只需修改 @Transactional 的屬性

 @Transactional(propagation=Propagation.REQUIRES_NEW)
    @Override
    public void purchase(String username, String isbn){

 

此時的事務流程可以表示為如下圖,此時若賬戶金錢能買一本但不夠兩本時,會執行能購買的行為,而對不能購買的行為不執行

 

 

同樣在xml配置文件中,我們也可以來設置傳播屬性

     <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="purchase" propagation="REQUIRES_NEW"/>
         </tx:attributes>
     </tx:advice>

 

 

事務的隔離級別

Spring 支持的事務隔離別如下表所示

 

隔離級別 描述
DEFAULT 使用地城數據庫的默認隔離級別,對大多數數據庫來說,默認的隔離級別都是 READ_COMMITED
READ_UNCOMMITTED     允許事務讀取未被其他事務提交的變更。臟讀、不可重復讀和幻讀的問題都會出現
READ_COMMITTED 只允許事務讀取已經被其他事務提交的變更。可以避免臟讀,但不可重復讀和幻讀問題仍然可能出現
REPEATABLE_READ 確保事務可以多次從一個字段中讀取相同的值,在這個事務持續期間,禁止其他事務對這個字段進行更新。可以避免臟讀和不可重復讀,但幻讀的問題仍然存在
SERIALIZABLE 確保事務可以沖一個表中讀取相同的行。在這個事務持續期間,禁止其他事務對該表執行插入、更新和刪除操作。所有並發問題都可以避免,但性能低下

Oracle 支持 2 種事務隔離級別:READ_COMMITED,SERIALIZABLE

MySql 支持 4 種

 

用法如下代碼所示

    @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED) public void purchase(String username, String isbn){
     <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="purchase" 
             propagation="REQUIRES_NEW" isolation="READ_COMMITTED""/>
         </tx:attributes>
     </tx:advice>

 

 

設置回滾事務屬性

默認情況下只有未檢查異常( RuntimeException 和 Error 類型的異常)會導致事務回滾。而受檢查異常則不會。(通常情況下我們都是取默認值即可)

事務的回滾規則可以通過 @Transactional 注解的 rollbackFornorollbackFor 屬性來定義。這兩個屬性被聲明為 Class[] 類型的,因此可以為這兩個屬性指定多個異常類,也可以使用屬性 rollbackFor=ClassName 和 norollbackFor=ClassName 通過異常類的名字直接指定。

    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED, rollbackFor={IOException.class, SQLException.class}, noRollbackFor=ArithmeticException.class) public void purchase(String username, String isbn){

我們還可以在 <tx:method/> 元素中指定回滾規則,此時屬性值要填類全名

     <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="purchase" 
             propagation="REQUIRED" 
             isolation="DEFAULT" rollback-for="java.io.IOException, java.sql.SQLExcpetion" no-rollback-for="java.lang.ArithmeticExcpeion"/>
         </tx:attributes>
     </tx:advice>

 

 

超時和只讀屬性

超時事務屬性:表示事務在強制回滾之前可以保持多久,這樣可以防止長期運行的事務占用資源。由於事務可以在行和表上獲得鎖,因此長事務會占用資源,對整體性能產生影響。

只讀事務屬性:表示這個事務只讀取數據但不更新數據,這樣可以幫助數據庫引擎優化事務。

超時和只讀屬性可以在 @Transactional 注解中定義。超時屬性以秒為單位計算。

    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            rollbackFor={IOException.class, SQLException.class},
            noRollbackFor=ArithmeticException.class,
            readOnly=true, timeout=30)
    public void purchase(String username, String isbn){

也可以在 <tx:method/>元素中進行指定

     <tx:advice id="bookShopTxAdvice" transaction-manager="transactionManager">
         <tx:attributes>
             <tx:method name="purchase" 
             propagation="REQUIRES_NEW" 
             isolation="READ_COMMITTED"
             rollback-for="java.io.IOException, java.sql.SQLExcpetion"
             no-rollback-for="java.lang.ArithmeticExcpeion" timeout="30" read-only="false"/>
         </tx:attributes>
     </tx:advice>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM