目錄
- 第一節 事務的隔離級别【回顧】
-
- 特性:ACID
- 隔離問題
- 隔離級别--解決問題
- mysql 事務操作--簡單
- mysql 事務操作--Savepoint
- 第二節 Spring事務管理介紹
-
- 2.1 Spring提供的事務jar包
- 2.2 Jar包中的三個頂級接口
- 2.3 PlatformTransactionManager 事務管理器
-
- 導入以下jar包
- 之前常用的兩個事務管理器
- 2.4 TransactionDefinition 事務定義
- 2.5 TransactionStatus 事務狀态
- 第三節 轉賬案例:使用Spring管理事務
-
- 3.1 環境搭建
-
- 第一步:建立資料庫表
- 第二步:導入相關jar包
- 第三步:Dao層代碼編寫
- 第四步:Service層代碼編寫
- 第五步:Spring的配置
- 第六步:測試與效果
- 3.2 手動管理事務【了解】
-
- 1. 修改Service
- 2. 修改spring的配置檔案
- 3. 測試與效果
- 3.3 工廠bean生成代理:半自動
-
- 1. service無需配置事務模闆
- 2. 修改spring的配置檔案
- 3. 測試與效果
- 3.4 使用AOP配置事務【掌握】
-
- 1. spring的配置
- 2. 測試與結果
- 3.5 使用注解配置事務
-
- 1.spring的配置
- 2. 測試與效果
第一節 事務的隔離級别【回顧】
- 為什麼有事務的隔離級别?
- 解決多線程通路資料庫的問題。
- 一組業務操作,要麼全部成功,要麼全部不成功。
特性:ACID
- 原子性:整體 【原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗】
- 一緻性:資料 【一個事務執行之前和執行之後都必須處于一緻性狀态】
- 隔離性:并發 【對于任意兩個并發的事務T1和T2,在事務T1看來,T2要麼在T1開始之前就已經結束,要麼在T1結束之後才開始,這樣每個事務都感覺不到有其他事務在并發地執行。】
- 持久性:結果 【持久性是指一個事務一旦被送出了,那麼對資料庫中的資料的改變就是永久性的】
隔離問題
-
髒讀:一個事務讀到另一個事務未送出的内容【讀取未送出内容】
在該隔離級别,所有事務都可以看到其他未送出事務的執行結果。本隔離級别很少用于實際應用,因為它的性能也不比其它級别好多少。
-
不可重複讀:一個事務讀到另一個事務已送出的内容(insert)【讀取送出内容】
這是大多數資料庫系統的預設隔離級别(但不是MySQL預設的)。它滿足了隔離的簡單定義:一個事務隻能看見已經送出事務所做的改變。
-
虛讀(幻讀):一個事務讀到另一個事務已送出的内容(update)
這是MySQL的預設事務隔離級别,它確定同一事務的多個執行個體在并發讀取資料時,會看到同樣的資料行。不過理論上,這會導緻另一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指當使用者讀取某一範圍的資料行時,另一個事務又在該範圍内插入了新行,當使用者再讀取該範圍的資料行時,會發現有新的“幻影” 行。
-
Serializable(可串行化)
這是最高的隔離級别,它通過強制事務排序,使之不可能互相沖突,進而解決幻讀問題。簡言之,它是在每個讀的資料行上加上共享鎖。在這個級别,可能導緻大量的逾時現象和鎖競争。
隔離級别–解決問題
- read uncommittd,讀未送出。存在3個問題。
- read committed,讀已送出。解決:髒讀。存在2個問題。
- repeatable read ,可重複讀。解決:髒讀、不可重複讀。存在1個問題。
- serializable,串行化。單事務。沒有問題。
mysql 事務操作–簡單
- ABCD(4個操作) 一個事務
Connection conn = null;
try{
//1 獲得連接配接
conn = ...;
//2 開啟事務
conn.setAutoCommit(false);//不要自動送出事務
A
B
C
D
//3 送出事務
conn.commit();
} catche(){
//4 復原事務
conn.rollback();
}
mysql 事務操作–Savepoint
- 需求:AB(必須),CD(可選)
Connection conn = null;
Savepoint savepoint = null; //儲存點,記錄操作的目前位置,之後可以復原到指定的位置。(可以復原一部分)
try{
//1 獲得連接配接
conn = ...;
//2 開啟事務
conn.setAutoCommit(false);
A
B
savepoint = conn.setSavepoint();//設定儲存點
C
D
//3 送出事務
conn.commit();
} catche(){
if(savepoint != null){
//CD異常
//復原到CD之前
conn.rollback(savepoint);//如果儲存點有值,復原到記錄的儲存點,CD之前
//送出了AB
conn.commit();
} else{
//如果儲存點沒有值,直接復原到A之前
//AB異常
//復原AB
conn.rollback();
}
}
第二節 Spring事務管理介紹
2.1 Spring提供的事務jar包
2.2 Jar包中的三個頂級接口
-
PlatformTransactionManager:
平台事務管理器,spring要管理事務,進行事務配置時,必須要使用事務管理器。
-
TransactionDefinition:
事務詳情(事務定義、事務屬性),spring用于确定事務具體詳情,
例如:隔離級别、是否隻讀、逾時時間等。
進行事務配置時,必須要配置詳情。spring将配置項封裝到該對象執行個體。
-
TransactionStatus:
事務狀态,spring用于記錄目前事務的運作狀态。例如:是否有儲存點,事務是否完成。
spring底層根據狀态進行相應操作。
2.3 PlatformTransactionManager 事務管理器
導入以下jar包
- 不使用hibernate可以不導,隻是了解一下
之前常用的兩個事務管理器
以後操作資料庫将使用mybatis的事務管理器
2.4 TransactionDefinition 事務定義
- 傳播行為:在兩個業務之間如何共享事務
PROPAGATION_REQUIRED required,必須 【預設值】【掌握】 | 支援目前事務,A如果有事務,B将使用該事務。 如果A沒有事務,B将建立一個新的事務。 |
PROPAGATION_SUPPORTS supports ,支援 | 支援目前事務,A如果有事務,B将使用該事務。 如果A沒有事務,B将以非事務執行。 |
PROPAGATION_MANDATORY mandatory ,強制 | 支援目前事務,A如果有事務,B将使用該事務。 如果A沒有事務,B将抛異常。 |
PROPAGATION_REQUIRES_NEW requires_new ,必須新的【掌握】 | 如果A有事務,将A的事務挂起,B建立一個新的事務。 如果A沒有事務,B建立一個新的事務。(不管A有沒有事務,B都建立一個新事務) |
PROPAGATION_NOT_SUPPORTED not_supported ,不支援 | 如果A有事務,将A的事務挂起,B将以非事務執行。 如果A沒有事務,B将以非事務執行。(不管A有沒有事務,B都以非事務執行) |
PROPAGATION_NEVER never,從不 | 如果A有事務,B将抛異常。 如果A沒有事務,B将以非事務執行 |
PROPAGATION_NESTED nested ,嵌套【掌握】 | A和B底層采用儲存點機制,形成嵌套事務。 |
2.5 TransactionStatus 事務狀态
第三節 轉賬案例:使用Spring管理事務
3.1 環境搭建
第一步:建立資料庫表
create database spring_day05;
use spring_day05;
create table account(
id int primary key auto_increment,
username varchar(50),
money int
);
insert into account(username,money) values('jack','10000');
insert into account(username,money) values('rose','10000');
第二步:導入相關jar包
- 核心:4+1(core、context、beans、expression)+logging(日志)
- aop : 4 (aop聯盟、spring aop、aspectj.weaver規範、spring aspect)
- 資料庫:2 (jdbc/tx事務)
- 驅動:mysql
- 連接配接池:c3p0
第三步:Dao層代碼編寫
- AccountDao接口代碼
package com.it.dao;
/**
* @author shuyy
* @date 2020/9/15
*/
public interface AccountDao {
//支出
void out(String outer,Integer money);
//收入
void in(String inner,Integer money);
}
- AccountDaoImpl實作類代碼
package com.it.dao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* @ClassName AccountDaoImpl
* @Author shuyy
* @Date 2020/9/15
**/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{
@Override
public void out(String outer, Integer money) {
//支出
getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer);
}
@Override
public void in(String inner, Integer money) {
//收入
getJdbcTemplate().update("update account set money = money + ? where username = ?",money,inner);
}
}
第四步:Service層代碼編寫
- AccountService接口編寫
package com.it.service;
public interface AccountService {
//(支出,收入,金額)
void transfer(String outer,String inner,Integer money);
}
- AccountServiceImpl實作類編寫
package com.it.service;
import com.it.dao.AccountDao;
/**
* @ClassName AccountServiceImpl
* @Author shuyy
* @Date 2020/9/15
**/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;//由spring注入,至少要提供set方法
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String outer, String inner, Integer money) {
//支出
accountDao.out(outer,money);
//收入
accountDao.in(inner, money);
}
}
第五步:Spring的配置
- 讀取db.properties資料配置 -> 配置c3p0資料源 -> 配置dao -> 配置service
<?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:p="http://www.springframework.org/schema/p"
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
http://www.springframework.org/schema/context/spring-context.xsd">
<!--讀取db.properties資料-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置c3p0資料源
注意:dbcp和c3p0的資料庫連接配接的參數的屬性名不一樣,注意差別
-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置dao-->
<bean class="com.it.dao.AccountDaoImpl" id="accountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Service-->
<bean class="com.it.service.AccountServiceImpl" id="accountService">
<property name="accountDao" ref="accountDao"></property>
</bean>
</beans>
第六步:測試與效果
@Test
public void test1(){
//轉賬測試
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//擷取Service
AccountService accountService = (AccountService) context.getBean("accountService");
//jack轉1000給rose
accountService.transfer("jack","rose",1000);
}
3.2 手動管理事務【了解】
- spring底層使用 TransactionTemplate 事務模闆進行操作
- 操作步驟:
- service 需要獲得 TransactionTemplate
- spring 配置模闆,并注入給service
- 模闆需要注入事務管理器
- 配置事務管理器:DataSourceTransactionManager ,需要注入DataSource
- 了解底層即可,因為以後都是通過aop來配置事務
1. 修改Service
- 配置spring事務模闆TransactionTemplate
package com.it.service;
import com.it.dao.AccountDao;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
/**
* @ClassName AccountServiceImpl
* @Author shuyy
* @Date 2020/9/15
**/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;//由spring注入,至少要提供set方法
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//spring配置事務模闆【提供set方法,由spring注入】
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transfer(String outer, String inner, Integer money) {
//通過事務模闆執行業務
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
//支出
accountDao.out(outer,money);
int i = 1/0;//如果這裡有錯誤就會復原
//收入
accountDao.in(inner, money);
}
});
}
}
2. 修改spring的配置檔案
- 逆推,要用事務模闆就需要配置事務模闆,配置事務模闆還要配置事務管理器,配置事務管理器裡面還要注入資料源dateSource
- 手動管理事務比較麻煩【了解即可】
<?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:p="http://www.springframework.org/schema/p"
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
http://www.springframework.org/schema/context/spring-context.xsd">
<!--讀取db.properties資料-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置c3p0資料源
注意:dbcp和c3p0的資料庫連接配接的參數的屬性名不一樣,注意差別
-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置dao-->
<bean class="com.it.dao.AccountDaoImpl" id="accountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事務管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<!--配置dateSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事務模闆-->
<bean class="org.springframework.transaction.support.TransactionTemplate" id="transactionTemplate">
<!--事務管理器-->
<property name="transactionManager" ref="dataSourceTransactionManager"></property>
</bean>
<!--配置Service-->
<bean class="com.it.service.AccountServiceImpl" id="accountService">
<property name="accountDao" ref="accountDao"></property>
<!--事務模闆-->
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
</beans>
3. 測試與效果
- 有異常,資料會復原,資料不變
- 注釋異常,再運作一次,測試手動配置事務後,能否運作成功
3.3 工廠bean生成代理:半自動
- Spring提供管理事務的代理工廠bean TransactionProxyFactoryBean
1. service無需配置事務模闆
- AccountServiceImpl實作類同最開始的環境配置
2. 修改spring的配置檔案
<?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:p="http://www.springframework.org/schema/p"
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
http://www.springframework.org/schema/context/spring-context.xsd">
<!--讀取db.properties資料-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置c3p0資料源
注意:dbcp和c3p0的資料庫連接配接的參數的屬性名不一樣,注意差別
-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置dao-->
<bean class="com.it.dao.AccountDaoImpl" id="accountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事務管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<!--配置dateSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Service-->
<bean class="com.it.service.AccountServiceImpl2" id="accountService">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置工廠代理-->
<bean class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" id="transactionProxyFactoryBean">
<!--接口-->
<property name="proxyInterfaces" value="com.it.service.AccountService"></property>
<!--目标對象-->
<property name="target" ref="accountService"></property>
<!--切面對象:spring做,無需寫-->
<!--事務管理器-->
<property name="transactionManager" ref="dataSourceTransactionManager"></property>
<!--transactionAttributes:配置事務的屬性或詳情(必須配置,不配置報錯)
key:寫方法名
value:寫事務配置
格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception(後面3個可不配置,可選配置)
傳播行為 隔離級别 是否隻讀 異常復原 異常送出
-->
<property name="transactionAttributes">
<props>
<!--配置預設的傳播行為和隔離級别-->
<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
</props>
</property>
</bean>
</beans>
3. 測試與效果
- 有異常,復原,資料不變
- 注釋異常,成功轉賬
- readOnly的使用(隻讀,不能修改),不設定異常也會報錯,因為裡面涉及修改資料,而readOnly是隻讀(資料不變)
- +Exception的使用(有異常也送出)
- 先設定異常
- 複制異常的類型,設定有異常也送出,再運作一下
- 有異常,但是資料會送出(jack支付了,rose沒收到)
3.4 使用AOP配置事務【掌握】
- 實作類同上
1. spring的配置
<?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:p="http://www.springframework.org/schema/p"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--讀取db.properties資料-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置c3p0資料源
注意:dbcp和c3p0的資料庫連接配接的參數的屬性名不一樣,注意差別
-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置dao-->
<bean class="com.it.dao.AccountDaoImpl" id="accountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事務管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="txManager">
<!--配置dateSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Service-->
<bean class="com.it.service.AccountServiceImpl2" id="accountService">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--使用spring的aop标簽來配置-->
<!--1.配置通知事務管理器-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!--配置事務詳情:傳播行為、隔離級别-->
<tx:attributes>
<!--傳播行為、隔離級别有預設,可以不配置,但是方法(tx:attributes)一定要配置,否則報錯-->
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--可以抽取為公共切入點,其它可以引用它-->
<!--<aop:pointcut id="myPointCut" expression="execution(* com.it.service..*.*(..))"/>-->
<!--2.事務與切入點關聯--><!--這裡的切入點表達式表示service包及其子包下的所有類和任意參數的方法都是切入點-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.it.service..*.*(..))"></aop:advisor>
</aop:config>
</beans>
- 注意要添加檔案頭
2. 測試與結果
- 由于之前測試次數過多,将資料庫資料重置一下
- 同樣先測試有異常,資料庫資料不變
- 注釋異常,事務成功配置
- readOnly的設定(true隻讀)
- 有異常也送出(no-rollback-for)
3.5 使用注解配置事務
- 使用注解配置事務,注解可以寫在一個類上也可以寫在一個方法上
- 每一個使用事務的類都要加上注解,不如在配置檔案中來得一目了然
1.spring的配置
<?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:p="http://www.springframework.org/schema/p"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--讀取db.properties資料-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置c3p0資料源
注意:dbcp和c3p0的資料庫連接配接的參數的屬性名不一樣,注意差別
-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!--配置dao-->
<bean class="com.it.dao.AccountDaoImpl" id="accountDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Service-->
<bean class="com.it.service.AccountServiceImpl2" id="accountService">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--注解配置事務-->
<!--1.配置事務管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="txManager">
<!--配置dateSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.開啟事務注解驅動-->
<tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven>
</beans>
2. 測試與效果
- 先重置資料庫資料,都為10000
- 使用注解配置事務(@Transactional)
- @Transactional注解(readOnly與異常送出條件等都在其中)
- 預設可以不寫傳播行為與隔離級别,設定異常,復原,資料不變
- 注釋異常,成功配置