- 2.1,事務概述
- 2.2,Spring事務管理
- 2.3,測試數據準備
- 2.4,事務的傳播行爲
- 2.5,隔離級別
- 2.6,觸發事務回滾的異常
- 2.7,事務的超時和只讀屬性
- 2.8,基於XML文檔的聲明式事務配置
JdbcTemplate
-
爲了使JDBC更加易於使用,Spring在JDBC API上定義了一個抽象層,以此建立一個JDBC存取框架。
-
作爲Spring JDBC框架的核心,JDBC模板的設計目的是爲不同類型的JDBC操作提供模板方法,通過這種方式,可以在儘可能保留靈活性的情況下,將數據庫存取的工作量降到最低。
-
可以將Spring的JdbcTemplate看作是一個小型的輕量級持久化層框架,和我們之前使用過的DBUtils風格非常接近。
①,創建連接數據庫基本信息屬性文件db.properties
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/test
jdbc.username = root
jdbc.password = root
②,在Spring配置文件中配置相關的bean,jdbc.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 引入屬性文件 -->
<context:property-placeholder location="db.properties"/>
<!-- 創建數據源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 通過數據源配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
- 增刪改:
- 批量增刪改
/**
* Object[]封裝了SQL語句每一次執行時所需要的參數
* List集合封裝了SQL語句多次執行時的所有參數
*/
JdbcTemplate.batchUpdate(String, List<Object[]>)//
- 查詢單行
JdbcTemplate.queryForObject(String, RowMapper<Department>, Object...)
-
查詢多行
RowMapper對象依然可以使用
BeanPropertyRowMapperJdbcTemplate.query(String, RowMapper<Department>, Object...)
-
查詢單一值
JdbcTemplate.queryForObject(String, Class, Object...)
- 示例代碼:
public class JdbcTest {
ApplicationContext ac = new ClassPathXmlApplicationContext("jdbc.xml");
JdbcTemplate jdbcTemplae = ac.getBean("jdbcTemplate", JdbcTemplate.class);
/**
* 單個增刪改
*/
@Test
public void updateDemo(){
// jdbcTemplae.update("insert into user values (null,'Tommey01',123,'上海',15131315123,null)");
// String sql = "insert into user values(null,?,?,?,15131315123,null)";
String sql = "delete from user where address like ?";//"長沙"在?佔位符中自動轉成'長沙’,所以不能使用in操作
jdbcTemplae.update(sql,"%地址2%");
}
/**
* 批量增刪改
*/
@Test
public void batchUpdateDemo(){
String sql = "insert into user values (null,?,?,?,15131315123,null)";
List<Object[]> list = new ArrayList<>();
list.add(new Object[]{"名字1","密碼1","地址1"});
list.add(new Object[]{"名字2","密碼2","地址2"});
list.add(new Object[]{"名字3","密碼3","地址3"});
jdbcTemplae.batchUpdate(sql,list);
}
@Test
public void queryForObjectDemo(){
String sql = "select id,name,password,phone from user where id = ?";
RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
User user = jdbcTemplae.queryForObject(sql, new Object[]{14}, rowMapper);//將列名(字段名或字段名的別名)與屬性名進行映射
System.out.println(user);
String sql1 = "select count(1) from user";
Integer count = jdbcTemplae.queryForObject(sql1, Integer.class);
System.out.println(count);
}
@Test
public void queryDemo(){
String sql = "select id,name,password,phone from user";
RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
List<User> query = jdbcTemplae.query(sql, rowMapper);
System.out.println(Arrays.toString(query.toArray()));
}
}
聲明式事務管理
-
在JavaEE企業級開發的應用領域,爲了保證數據的完整性和一致性,必須引入數據庫事務的概念,所以事務管理是企業級應用程序開發中必不可少的技術。
-
事務就是一組由於邏輯上緊密關聯而合併成一個整體(工作單元)的多個數據庫操作,這些操作要麼都執行,要麼都不執行。
-
事務的四個關鍵屬性(ACID)
①,
原子性(atomicity)
:“原子”的本意是“不可再分”,事務的原子性表現爲一個事務中涉及到的多個操作在邏輯上缺一不可。事務的原子性要求事務中的所有操作要麼都執行,要麼都不執行。②,
一致性(consistency)
:“一致”指的是數據的一致,具體是指:所有數據都處於滿足業務規則的一致性狀態。一致性原則要求:一個事務中不管涉及到多少個操作,都必須保證事務執行之前數據是正確的,事務執行之後數據仍然是正確的。如果一個事務在執行的過程中,其中某一個或某幾個操作失敗了,則必須將其他所有操作撤銷,將數據恢復到事務執行之前的狀態,這就是回滾。③,
隔離性(isolation)
:在應用程序實際運行過程中,事務往往是併發執行的,所以很有可能有許多事務同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。隔離性原則要求多個事務在併發執行過程中不會互相干擾。④,
持久性(durability)
:持久性原則要求事務執行完成後,對數據的修改永久的保存下來,不會因各種系統錯誤或其他意外情況而受到影響。通常情況下,事務對數據的修改應該被寫入到持久化存儲器中。
-
編程式事務管理
-
使用原生的JDBC API進行事務管理
①,獲取數據庫連接Connection對象
②,取消事務的自動提交
③,執行操作
④,正常完成操作時手動提交事務
⑤,執行失敗時回滾事務
⑥,關閉相關資源
-
評價
使用原生的JDBC API實現事務管理是所有事務管理方式的基石,同時也是最典型 的編程式事務管理。編程式事務管理需要將事務管理代碼嵌入到業務方法中來控制事務 的提交和回滾。在使用編程的方式管理事務時,必須在每個事務操作中包含額外的事務 管理代碼。相對於核心業務而言,事務管理的代碼顯然屬於非核心業務,如果多個模塊 都使用同樣模式的代碼進行事務管理,顯然會造成較大程度的代碼冗餘。
-
-
聲明式事務管理
-
大多數情況下聲明式事務比編程式事務管理更好:它將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理。
-
事務管理代碼的固定模式作爲一種橫切關注點,可以通過AOP方法模塊化,進而藉助Spring AOP框架實現聲明式事務管理。
-
Spring在不同的事務管理API之上定義了一個抽象層,通過配置的方式使其生效,從而讓應用程序開發人員不必瞭解事務管理API的底層實現細節,就可以使用Spring的事務管理機制。
-
Spring既支持編程式事務管理,也支持聲明式的事務管理。
-
-
Spring提供的事務管理器
-
Spring從不同的事務管理API中抽象出了一整套事務管理機制,讓事務管理代碼從特定的事務技術中獨立出來。開發人員通過配置的方式進行事務管理,而不必瞭解其底層是如何實現的。
-
Spring的核心事務管理抽象是它爲事務管理封裝了一組獨立於技術的方法。無論使用Spring的哪種事務管理策略(編程式或聲明式),事務管理器都是必須的。事務管理器可以以普通的bean的形式聲明在Spring IOC容器中。
-
-
事務管理器的主要實現
-
DataSourceTransactionManager
:在應用程序中只需要處理一個數據源,而且通過JDBC存取。 -
JtaTransactionManager
:在JavaEE應用服務器上用JTA(Java Transaction API)進行事務管理 -
HibernateTransactionManager
:用Hibernate框架存取數據庫
-
- 示例代碼:
<!-- 配置事務管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 啓用事務註解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
- 在需要進行事務控制的方法上加註解
@Transactional
-
簡介
當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在自己的事務中運行。事務的傳播行爲可以由傳播屬性指定。Spring定義了7種類傳播行爲。
傳播屬性 | 描述 |
---|---|
REQUIRED | 如果有事務在運行,當前的方法就在這個事務內運行,否則,就啓功一個新的事務,並在自己的事務內運行 |
REQUIRED_ NEW | 當前的方法必須啓動新事務,並在它自己的事務內運行.如果有事務正在運行,應該將它掛起 |
SUPPORTS | 如果有事務在運行,當前的方法就在這個事務內運行.否則它可以不運行在事務中 |
NOT_ SUPPORTE | 當前的方法不應該運行在事務中.如果有運行的事務,將它掛起 |
MANDATORY | 當前的方法必須運行在事務內部, 如果沒有正在運行的事務,就拋出異常 |
NEVER | 當前的方法不應該運行在事務中.如果有運行的事務,就拋出異常 |
NESTED | 如果有事務在運行,當前的方法就應該在這個事務的嵌套事務內運行,否則,就啓動一個新的事務,並在它自己的事務內運行. |
- 事務傳播屬性可以在@Transactional註解的propagation屬性中定義。
①,數據庫事務併發問題
-
假設現在有兩個事務:Transaction01和Transaction02併發執行
-
髒讀
- Transaction01將某條記錄的AGE值從20修改爲30;Transaction02讀取了Transaction01更新後的值:30;Transaction01回滾,AGE值恢復到了20;Transaction02讀取到的30就是一個無效的值。
-
不可重複讀
- Transaction01讀取了AGE值爲20;Transaction02將AGE值修改爲30;Transaction01再次讀取AGE值爲30,和第一次讀取不一致。
-
幻讀
- Transaction01讀取了STUDENT表中的一部分數據;Transaction02向STUDENT表中插入了新的行;Transaction01讀取了STUDENT表時,多出了一些行
②,隔離級別
- 數據庫系統必須具有隔離併發運行各個事務的能力,使它們不會相互影響,避免各種併發問題。一個事務與其他事務隔離的程度稱爲隔離級別。SQL標準中規定了多種事務隔離級別,不同隔離級別對應不同的干擾程度,隔離級別越高,數據一致性就越好,但併發性越弱。
隔離級別 | 說明 |
---|---|
讀未提交:READ UNCOMMITTED | 允許Transaction01讀取Transaction02未提交的修改 |
讀已提交:READ COMMITTED | 要求Transaction01只能讀取Transaction02已提交的修改 |
可重複讀:REPEATABLE READ | 確保Transaction01可以多次從一個字段中讀取到相同的值,即Transaction01執行期間禁止其它事務對這個字段進行更新 |
串行化:SERIALIZABLE | 確保Transaction01可以多次從一個表中讀取到相同的行,在Transaction01執行期間,禁止其它事務對這個表進行添加、更新、刪除操作。可以避免任何併發問題,但性能十分低下 |
③,在Spring中指定事務隔離級別
- 註解:用
@Transactional
註解聲明式地管理事務時可以在@Transactional
的isolation
屬性中設置隔離級別
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
public void buyShop(String sid,String uid){
}
- XML:在Spring 2.x事務通知中,可以在
<tx:method>
元素中指定隔離級別
-
默認情況
捕獲到RuntimeException或Error時回滾,而捕獲到編譯時異常不回滾。
-
設置途經
-
註解
@Transactional
註解rollbackFor
屬性:指定遇到時必須進行回滾的異常類型,可以爲多個noRollbackFor
屬性:指定遇到時不回滾的異常類型,可以爲多個
-
@Transactional(isolation = Isolation.READ_COMMITTED,rollbackFor = {IOException.class, SQLException.class},noRollbackFor = ArithmeticException.class)
public void buyShop(String sid,String uid){
}
-
XML
在Spring 2.x事務通知中,可以在
<tx:method>
元素中指定回滾規則。如果有不止
-
readOnly
:指定當前事務中的一系列的操作是否爲只讀,若設置爲只讀,不管事務中有沒有寫的操作,mysql都會在請求訪問數據的時候,不加鎖,提高性能,但是如果有寫操作的情況,建議一定不能設置只讀 -
簡介:
由於事務可以在行和表上獲得鎖,因此長事務會佔用資源,並對整體性能產生影響。
如果一個事務只讀取數據但不做修改,數據庫引擎可以對這個事務進行優化。
超時事務屬性:事務在強制回滾之前可以保持多久。這樣可以防止長期運行的事務佔用資源。
只讀事務屬性: 表示這個事務只讀取數據但不更新數據, 這樣可以幫助數據庫引擎優化事務。
-
設置:
- 註解
@Transactional(readOnly = true,timeout = 30,propagation = Propagation.REQUIRES_NEW)
public void buyShop(String sid,String uid){
}
-
XML
在Spring 2.x事務通知中,超時和只讀屬性可以在
<tx:method>
元素中進行指定
<!--配置事務管理器,不管是用註解方式或xml方式配置事務,一定要有DataSourceTransactionManager事務管理器支持 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 開啓註解驅動,即對事務相關的註解進行掃描,解析含義並執行功能-->
<!-- <tx:annotation-driven />-->
<!-- 配置事務切面 -->
<aop:config>
<aop:pointcut
expression="execution(* com.service.BookShopServiceImpl.purchase(..))"
id="txPointCut"/>
<!-- 將切入點表達式和事務屬性配置關聯到一起 -->
<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
</aop:config>
<!-- 配置基於XML的聲明式事務 -->
<tx:advice id="myTx">
<tx:attributes>
<!-- 設置具體方法的事務屬性 -->
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="purchase"
isolation="READ_COMMITTED"
no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"
propagation="REQUIRES_NEW"
read-only="false"
timeout="10"/>
</tx:attributes>
</tx:advice>