(四)Spring中的事務管理
全有或全無的操作稱為事務。事務允許你將幾個操作組合成一個要麼發生要麼不發生的工作單元。我們可以用四個詞來表示事務:
> **原子性:** 原子性確保事務中的所有操作全部發生或全部不發生。(所有操作成功,事務也就成功;任意一個操作失敗,事務就失敗並回滾)。
> **一致性:** 一旦事務完成,系統必須確保它所建模的業務處於一直狀態。
> **隔離性:** 事務允許多個使用者對相同的資料進行操作,所以所有的操作應該相隔離。
> **永續性:** 一旦事務完成,事務的結果應該持久化。
<!--more-->
在介紹Spring的事務管理之前,我們首先要介紹一下Spring的JDBC模板技術。
# Spring框架的JDBC模板技術
Spring框架中提供了很多模板類來實現簡化程式設計。比如最基本的JDBC模板(Spring框架提供了`JdbcTemplate`類)
我們以以一個簡單的案例來演示如何使用Spring框架提供的JDBC模板類
1. 建立資料表結構
```
create database springlearn;
use springlearn;
create table t_account(
id int primary key auto_increment,
name varchar(100),
money double
);
```
2. 方式一:通過new `DriverManagerDataSource`來插入資料
```java
@Test
public void run(){
//建立連線池,使用Spring框架內建的連線池
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///springlearn");
dataSource.setUsername("root");
dataSource.setPassword("root");
//建立模板類
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//完成資料的新增
jdbcTemplate.update("insert into t_account values (null,?,?)","TyCoding",1000);
}
```
此時已經在表中新增了一行資料。
**注意:**
1. `jdbc:mysql:///`等價於`jdbc:mysql://localhost:3306`
2. 此時我們先不要插入中文,可能會出現亂碼情況。
## 使用Spring框架來管理模板類
上面的案例中我們使用的是new的方式來建立的jdbc模板類,下面我們用Spring來管理這些模板類
1. `spring.xml`
```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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<context:component-scan base-package="spring_2"/>
<!-- Spring的資料庫連線池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///springlearn"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- Spring的模板類 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 將連線池物件注入到模板類中 -->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
```
**注意:**
類似上面我們使用new的方式建立物件,步驟仍是先建立連線池物件(此處使用的Spring內建的連線池物件),然後建立模板類物件(並將連線池物件注入到模板類物件中)
2. `Test測試類`
```java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class Demo4Test {
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Test
public void run2(){
jdbcTemplate.update("insert into t_account values(null,?,?)","TyCoding2",1000);
}
}
```
**注意:**
1. Spring整合了JUnit測試,所以我們這裡使用了Spring的註解方法載入配置檔案`spring.xml`
* `@RunWith()`用於獲取測試類物件
* `@ContextConfiguration()`用於載入配置檔案
2. 大家還記得前面我們已經介紹了Spring的注入物件方式,其中`@Resource`和`@Autowired`註解都可以實現將一個Java物件交給Spring,通過載入Spring上下文來注入此物件。因為此時我們在Java類中注入的Bean物件名是`jdbcTemplate`,而`spring.xml`中也配置了名字是`jdbcTemplate`Bean的屬性,那麼在Java類中的`jdbcTemplate`物件就擁有了一些屬性。增加name屬性僅是為了縮小查詢Bean的範圍。
3. 綜上我們注入了Bean物件,並載入了配置檔案,就可以直接執行相關sql語句了
此時已經插入了兩條資料
# Spring框架的事務管理
上面我們簡單的介紹了Spring框架如何通過模板類執行SQL語句的,下面我們就分析一下Spring對事務的管理:
上面我們已經介紹了事務的四個特性。
> 原子性
> 一致性
> 隔離性
> 永續性
下面我們仍要了解一些名詞概念
1. Spring的事務分類:
> 編碼式事務
> 宣告式事務
2. 事務的屬性
> 傳播行為: 傳播行為定義了客戶端與被調方法之間的事務邊界。(Spring定義了7中不同的傳播行為)
> 隔離級別:定義一個事務可能受到其他併發事務的影響程度。
* 髒讀:一個事務讀取了另一個事務尚改寫但尚未提交的資料
* 不可重複讀:一個事務執行相同的查詢多次,每次得到不同資料。(併發訪問造成的)
* 幻讀:類似不可重複讀。一個事務讀取時遇到另一個事務的插入,則這個事務就會讀取到一些原本不存在的記錄。
> 只讀:事務只讀時,資料庫就可以對其進行一些特定的優化。
> 事務超時:事務執行時間過長。
> 回滾原則:定義那些異常會導致事務回滾。(預設情況只有執行時異常才事務回滾)
3. Spring框架的事務管理相關的類和API
> 1.PlatformTransactionManager介面:平臺事務管理器(真正管理事務的類)
> 該介面的實現類:
> 1.`DataSourceTransactionManager`:用在當使用Spring的JDBC模板類或Mybatis框架時
> 2.`HibernateTransactionManager`:使用Hibernate框架時
> 2.TransactionDefinition介面:事務定義資訊(事務隔離級別、傳播行為、超時、只讀)
> 3.TransactionStatus介面:事務的狀態
## 宣告式事務
Spring有兩種事務,但是我們這裡只介紹宣告式事務,編碼式事務不常用,所以這裡不再介紹。
在案例之前我們瞭解一下2種常用連線池的不同寫法:
**2種連線池**
1. **c3p0**
```xml
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--配置連結屬性-->
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.username}"/>
</bean>
```
2. **druid** 阿里的連線池
```xml
<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>
```
注意:採用`${jdbc.xx}`的寫法前提是在外邊定義了`xx.properties`檔案,並在`spring.xml`中引入了該配置檔案
<br/>
### 在XML中定義事務
下面開始我們的案例
1. 首先我們要引入Spring提供的`tx`名稱空間
```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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
```
**注意**
不但需要`tx`的名稱空間,還需要`aop`的名稱空間,以為Spring的宣告式事務式的支援是通過Spring AOP框架實現的。
通過一個XML片段瞭解一下`<tx:advice>`是如何宣告事務性策略的:
```xml
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
```
**解釋:**
`<tx:advice>`實現宣告事務性策略,所有的事務配置都在改元素下定義。`<tx:method>`元素為name屬性指定的方法定義事務引數。我們看一下`<tx:method>`的屬性:
隔離級別|含義
-|:-:|-:
isolation|指定事務的隔離級別
propagation|定義事務的傳播規則
read-only|指定事務為只讀
回滾規則:<br>rollback-for<br>no-rollback-for|rollback-for指定了事務對於那些檢查型異常應當回滾而不提交<br/>no-rollback-for指定事務對於那些異常應當繼續執行而不回滾
timeout|對於長時間執行的事務定義超時時間
當使用`<tx:advice>`來宣告事務時,我們還需要一個事務管理器(常用`DataSourceTransactionManager`),然後使用`transaction-manager`屬性指定事務管理器的id:
```xml
<tx:advice id="txAdvice" transaction-manager="txManager">
...
</tx:advice>
```
`<tx:advice>`僅定義了AOP通知,用於把事務邊界通知給方法。但這些只是事務通知,而不是完成的事務性切面。所以我們還需要使用`<aop:config>`定義一個通知器(advisor)
```xml
<aop:config>
<aop:advisor pointcut="execution(* xx.xx.xxx(..))" advice-ref="txAdvice">
</aop:config>
```
看了上面的解釋你是否有一些思路了呢?下面我們通過案例(轉賬)來體會一下:
1. 目錄結構:
初始資料:
2. `AccountService.java`
```java
package spring_2;
public interface AccountService {
void pay(String out, String in, double money);
}
```
3. `AccountServiceImp.java`
```java
package spring_2;
import javax.annotation.Resource;
public class AccountServiceImp implements AccountService {
@Autowired
private AccountDao accountDao;
public void pay(String out, String in, double money) {
//扣錢
accountDao.outMoney(out,money);
//模擬異常
//int a = 10/0;
//加錢
accountDao.inMoney(in,money);
}
}
```
4. `AccountDao.java`
```java
package spring_2;
public interface AccountDao {
void outMoney(String out,double money);
void inMoney(String in,double money);
}
```
5. `AccoundDaoImp.java`
```java
package spring_2;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDaoImp extends JdbcDaoSupport implements AccountDao {
public void outMoney(String out,double money){
this.getJdbcTemplate().update("update t_account set money = money - ? where name = ?",money,out);
}
public void inMoney(String in,double money){
this.getJdbcTemplate().update("update t_account set money = money + ? where name = ?",money,in);
}
}
```
6. `spring.xml`
```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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 使用c3p0的連線池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///springlearn"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注入Bean -->
<bean id="accountService" class="spring_2.AccountServiceImp"/>
<bean id="accountDao" class="spring_2.AccountDaoImp">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事務增強 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
name :繫結事務的方法名,可以使用萬用字元,可以配置多個
propagation :傳播行為
isolation :隔離級別
read-only :是否只讀
timeout :超時資訊
rollback-for :發生哪些異常回滾
no-rollback-for :發生哪些異常不回滾
-->
<tx:method name="pay" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置AOP切面代理 -->
<aop:config>
<!-- 如果是自己寫的切面,使用<aop:aspect>標籤;如果是系統的,用<aop:advisor> -->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* spring_2.AccountServiceImp.pay(..))"/>
</aop:config>
</beans>
```
7. `Test測試類`
```java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class AccountTest {
@Autowired
private AccountService accountService;
@Test
public void run3(){
accountService.pay("TyCoding","tutu",100);
}
}
```
### 定義註解驅動的事務
我們不但可以使用XML定義事務驅動,還可以用使用註解`@Transaction`
秩序要改動`spring.xml`和`AccountServiceImp.java`即可以大大簡化程式碼,如下:
1. `AccountServiceImp.java`
```java
@Transactional
public class AccountServiceImp implements AccountService {
@Autowired
private AccountDao accountDao;
public void pay(String out, String in, double money) {
//扣錢
accountDao.outMoney(out,money);
//int a = 10/0;
//加錢
accountDao.inMoney(in,money);
}
}
```
2. `spring.xml`
```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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:component-scan base-package="spring_2"/>
<!-- 使用c3p0的連線池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///springlearn"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注入Bean -->
<bean id="accountService" class="spring_2.AccountServiceImp"/>
<bean id="accountDao" class="spring_2.AccountDaoImp">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--<!– 配置事務增強 –>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!–
name :繫結事務的方法名,可以使用萬用字元,可以配置多個
propagation :傳播行為
isolation :隔離級別
read-only :是否只讀
timeout :超時資訊
rollback-for :發生哪些異常回滾
no-rollback-for :發生哪些異常不回滾
–>
<tx:method name="pay" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!– 配置AOP切面代理 –>
<aop:config>
<!– 如果是自己寫的切面,使用<aop:aspect>標籤;如果是系統的,用<aop:advisor> –>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* spring_2.AccountServiceImp.pay(..))"/>
</aop:config>-->
</beans>
```
如圖所示,我們只需要寫一個`<tx:annotation-driven>`即可代替如上的XML配置,它允許在最有意義的位置宣告事務規則:在事務性方法上。
`<tx:annotation-driven>`元素告訴Spring檢查上下文中所有的Bean並查詢使用`@Transaction`註解的Bean,而不管這個註解是用在類級方法上還是方法級別上。
對於每一個使用`@Transactional`註解的Bean,`<tx:annotation-driven>`會自動為它新增事務通知。通知的事務屬性是通過`@Transactional`註解的引數定義的。
如上無論哪種方式,當我們把`AccountServiceImp.java`中的`int a = 10 / 0;`以上都會報錯,事務並回滾。
<br/>
# 交流
如果大家有興趣,歡迎大家加入我的Java交流群:671017003 ,一起交流學習Java技術。博主目前一直在自學JAVA中,技術有限,如果可以,會盡力給大家提供一些幫助,或是一些學習方法,當然群裡的大佬都會積極給新手答疑的。所以,別猶豫,快來加入我們吧!
<br/>
# 聯絡
If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.
- [Blog@TyCoding's blog](http://www.tycoding.cn)
- [GitHub@TyCoding](https://github.com/TyCoding)
- [ZhiHu@TyCoding](https://www.zhihu.com/people/tomo-83-82/activities)
相關文章
- Spring 中的事務管理Spring
- Spring事務專題(四)Spring中事務的使用、抽象機制及模擬Spring事務實現Spring抽象
- Spring的事務管理Spring
- Spring的事務管理(二)宣告式事務管理Spring
- Spring 事務管理Spring
- 解析Spring Boot中的事務管理機制Spring Boot
- Spring系列.事務管理Spring
- Spring系列-事務管理Spring
- Spring中事務管理org.springframework.transactionSpringFramework
- Spring框架中配置事務管理器Spring框架
- Spring的事務管理入門:程式設計式事務管理(TransactionTemplate)Spring程式設計
- Spring事務管理總結Spring
- Spring的事務管理(一) Spring事務管理的實現,事務的屬性(隔離級別,傳播行為,只讀)Spring
- Spring中的事務提交事件Spring事件
- spring宣告式事務管理配置Spring
- 《四 spring原始碼》spring的事務註解@Transactional 原理分析Spring原始碼
- 分散式鎖和spring事務管理分散式Spring
- Spring事務管理:非常規指南 - marcobehlerSpring
- 使用Spring Boot實現事務管理Spring Boot
- Spring 程式設計式事務管理Spring程式設計
- Spring事務管理(詳解+例項)Spring
- 關於Spring+Mybatis事務管理中資料來源的思考SpringMyBatis
- spring事務管理原始碼分析(二)事務處理流程分析Spring原始碼
- Spring事務(Transaction)管理高階篇一棧式解決開發中遇到的事務問題Spring
- Spring AOP 日誌攔截器的事務管理Spring
- Spring中如何配置Hibernate事務Spring
- Spring中@Transactional事務使用陷阱Spring
- Spring Data JPA中事務ReactiveTransactionManagerSpringReact
- 說說在 Spring 中,如何程式設計實現事務管理Spring程式設計
- spring事務管理原始碼分析(一)配置和事務增強代理的生成流程Spring原始碼
- 可能是最漂亮的Spring事務管理詳解Spring
- spring事務Spring
- Spring 事務Spring
- 聊聊spring事務的propagationSpring
- spring事務的傳播Spring
- 資料庫事務以及事務的四個特性資料庫
- Spring學習筆記3(JDBC模板&事務管理)Spring筆記JDBC
- spring2 Aop與事務、許可權管理Spring