事務是什么?
在操作數據庫時(增刪改),如果同時操作多次數據,我們從業務希望,要不全部成功,要不全部失敗。這種情況稱為事務處理。
A轉賬給B。
第一步,扣除A君賬號要轉的金額
第二步,增加B君賬號的金額
事務:指單個邏輯操作單元的集合
Spring事務控制我們要明確的
1.JavaEE體系進行分層開發,事務處理位於業務層,所以,一般情況下我們使用事務代理,一般放在分層設計業務層。
2.spring框架為我們提供了一組事務控制的應用程序接口(API)。
3.spring的事務控制都是基於AOP的,它既可以使用編程的方式實現,也可以使用配置的方式實現。我們學習的重點是使用配置的方式實現
案例引出問題:
A轉賬給B。
需求:從A君賬戶給B賬戶轉賬1000元錢。
第一步,扣除A君賬號要轉的金額
第二步,增加B君賬號的金額
案列分析:按正常來說,完成這兩步才算成功,但假如第一步完成,開始第二步時系統出錯了,也就是整個轉賬只扣了A的錢,然后出了錯,卻沒給B加錢,1000元就在系統錯誤中消失了,這要怎么辦了?
這就需要事務管理了,要么轉賬兩步成功執行,要么都不執行
結論: 根據上述案例分析, 事務管理應該是在Service層處理
模擬轉賬代碼:(事務在Dao層的代碼)
Dao層:接口和實現類
package cn.sxt.mapper; public interface UserMapper { /** * 轉出方法 * @param outId * @param money */ void updateIn(Integer inId,Integer money); /** * 轉入方法 * @param inId * @param money */ void updateOut(Integer outId,Integer money); }
package cn.sxt.mapper.Impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import cn.sxt.mapper.UserMapper; @Repository public class UserMapperImpl implements UserMapper { @Autowired private JdbcTemplate template; @Override public void updateIn(Integer inId, Integer money) { String sql = "update t_account set balance = balance - ? where id = ?"; template.update(sql, money, inId); } @Override public void updateOut(Integer outId, Integer money) { String sql = "update t_account set balance = balance + ? where id = ?"; template.update(sql, money, outId); } }
service層接口和實現類
package cn.sxt.service; public interface UserService { /** * 轉賬業務方法 * @param outId 轉出賬號 * @param inId 轉入賬號 * @param money 金額 */ void trans(Integer outId,Integer inId,Integer money); }
package cn.sxt.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import cn.sxt.mapper.UserMapper; import cn.sxt.service.UserService; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper mapper; /* * 真正的業務方法 * 一個業務方法有多個 功能(多個dao層方法)組成 */ @Override public void trans(Integer outId, Integer inId, Integer money) { mapper.updateIn(inId, money); System.out.println(1/0); mapper.updateOut(outId, money); } }
測試類:
package cn.sxt.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import cn.sxt.service.UserService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class test01 { @Autowired private UserService Service; @Test public void testName() throws Exception { Service.trans(2, 1, 500); } }
配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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 "> <!-- 配置組件包掃描的位置 --> <context:component-scan base-package="cn.sxt" /> <!-- 讀取db.properties配置文件到Spring容器中 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 配置 阿里巴巴的 druid 數據源(連接池) --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- SpringEL 語法 ${key} --> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <!-- ${username}如果key是username,name 默認spring框架調用的當前操作系統的賬號 解決方案:可以統一給key加一個前綴 --> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxActive" value="${jdbc.maxActive}"/> </bean> <!-- 配置jdbcTemplate| --> <bean id="template" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入數據源(連接池) --> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT jdbc.username=root jdbc.password=gzsxt jdbc.maxActive=10
導入的包:

數據庫並發問題
什么是數據庫並發問題?
並發: 多個客戶端同時同時訪問數據庫中某一條數據(秒殺)
數據庫可以擁有多個訪問客戶端,若多個客戶端並發地訪問數據庫中相同的資源,如果沒有采取必要的隔離措施,則會導致各種並發問題,破壞數據的完整性。
這些問題歸結為5類
包括3類數據讀問題(臟讀,不可重復讀,幻讀)
和2類數據更新問題(第一類丟失更新,第二類丟失更新)。 看圖
數據庫事務的隔離級別
問題:上述問題理論上如果出現了應該如何解決?
答:一般情況,數據庫都會處理一些事務並發的問題,數據庫提供了不同的事務隔離級別來處理不同的事務並發問題,事務隔離級別定義如下:
隔離級別 |
說明 |
READ_UNCOMMITED |
允許你讀取還未提交的改變了的數據。可能導致臟、幻、不可重復讀(相當於沒有做任何事務隔離) |
READ_COMMITTED |
允許在並發事務已經提交后讀取。可防止臟讀,但幻讀和 不可重復讀仍可發生(ORACLE默認級別) |
REPEATABLE_READ |
對相同字段的多次讀取是一致的,除非數據被事務本身改變。可防止臟、不可重復讀,但幻讀仍可能發生。(MYSQL默認級別) |
SERIALIZABLE |
完全服從ACID的隔離級別,確保不發生臟、幻、不可重復讀。這在所有的隔離級別中是最慢的,它是典型的通過完全鎖定在事務中涉及的數據表來完成的。(ORACLE支持)
|
針對不同隔離級別可以解決的的如下五類問題
解決丟失更新的方案
數據庫表數據加鎖
1,悲觀鎖
(1) 在操作當前數據的事務開啟事務就使用for update 鎖住當前數據
(2) Hibernate和MyBatis都有悲觀鎖對應的解決方案
2,樂觀鎖
(1) 為表添加一個version字段。當前事務操作的時候都會比對當前事務情況的多次操作的版本號是否一致,如果不一致認為數據已經被更新
(2) Hibernate和MyBatis都有樂觀鎖對應的解決方案
Spring對事務的支持
- 為什么需要使用Spring是事務
答:使用Spring是事務代理,可以配置一次,不用重復編寫事務處理代碼!!
Spring框架針對事務處理提供專門的解決方案
Spring的事務管理主要包括3個接口
Spring事務處理接口 |
描述 |
TransactionDefinition |
封裝事務的隔離級別超時時間,是否為只讀事務和事務的隔離級別和傳播規則等事務屬性,可通過XML配置具體信息 |
PlatformTransactionManager |
根據TransactionDefinition提供的事務屬性配置信息,創建事務。事物管理器 |
TransactionStatus |
封裝了事務的具體運行狀態。比如,是否是新開啟事務,是否已經提交事務,設置當前事務為rollback-only等 |
TransactionDefinition
該接口主要定義了:事務的傳播行為(規則),事務的隔離級別,獲得事務信息的方法。所以在配置事務的傳播行為,事務的隔離級別已經需要獲得事務信息時,可以通過查閱該類的代碼獲得相關信息。
TransactionDefinition源碼
public interface TransactionDefinition {
//事務的傳播行為 int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; //事務的隔離級別 int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; //事務超時管理 int TIMEOUT_DEFAULT = -1;
//獲得事務信息 int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
String getName();
}
|
事務傳播規則
Spring在TransactionDefinition接口中定義了七種事務傳播規則,規定了事務方法和事務方法發生嵌套調用時事務該如何進行傳播
事務傳播規則類型 |
描述 |
PROPAGATION_REQUIRED |
如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務(最常用操作)-Spring默認使用的就是此規則 |
PROPAGATION_REQUIRES_NEW |
創建一個新的事務,如果當前存在事務,則把當前事務掛起 |
PROPAGATION_SUPPORTS |
如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行 |
PROPAGATION_NOT_SUPPORTED |
以非事務方式運行,如果當前存在事務,則把當前事務掛起 |
PROPAGATION_NEVER |
以非事務方式運行,如果當前存在事務,則拋出異常 |
PROPAGATION_MANDATORY |
如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常 |
PROPAGATION_NESTED |
如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED |
PlatformTransactionManager事務管理器
Spring的事務管理:
1,PlatformTransactionManager:接口統一抽象處理事務操作相關的方法;
1):TransactionStatus getTransaction(TransactionDefinition definition):
根據事務定義信息從事務環境中返回一個已存在的事務,或者創建一個新的事務,並用TransactionStatus描述該事務的狀態。
ü 2):void commit(TransactionStatus status):
根據事務的狀態提交事務,如果事務狀態已經標識為rollback-only,該方法執行回滾事務的操作。
ü 3):void rollback(TransactionStatus status):
將事務回滾,當commit方法拋出異常時,rollback會被隱式調用
2,在使用spring管理事務的時候,首先得告訴spring使用哪一個事務管理器,使用不同的框架(JdbcTemplate,MyBatis,Hibernate/JPA )使用事務管理器都不同
PlatformTransactionManager事物管理器的繼承體系圖
常用的事務管理器:
DataSourceTransactionManager:使用JDBC,MyBatis的事務管理器;
事物方法的屬性細節配置
事務方法屬性:
事務方法屬性 |
描述 |
name |
匹配到的方法模式 |
read-only |
如果為true,開啟一個只讀事務,只讀事務的性能較高,但是不能再只讀事務中,使用DML |
isolation |
代表數據庫事務隔離級別(就使用默認) DEFAULT:讓spring使用數據庫默認的事務隔離級別; |
no-rollback-for |
如果遇到的異常是匹配的異常類型,就不回滾事務 |
rollback-for |
如果遇到的異常是指定匹配的異常類型,才回滾事務;spring默認情況下,spring只會去回滾RuntimeException及其子類,不會回滾Exception和Thowable |
propagation |
事務的傳播方式(當一個方法已經在一個開啟的事務當中了,應該怎么處理自身的事務) 1,REQUIRED(默認的傳播屬性):如果當前方法運行在一個沒有事務環境的情況下,則開啟一個新的事務,如果當前方法運行在一個已經開啟了的事務里面,把自己加入到開啟的那個事務中 2,REQUIRES_NEW:不管當前方法是否運行在一個事務空間之內,都要開啟自己的事務
|
timeout |
事務處理的超時時間,默認事物管理自動處理 ,可以手動配置
|
Spring事務的配置
Spring支持編程式事務管理和聲明式事務管理。
- 編程式事務管理:事務和業務代碼耦合度太高。
- 聲明式事務管理:侵入性小,把事務從業務代碼中抽離出來,使用AOP配置到配置文件中,提高維護性。
聲明式事務管理-xml方式配置(新的xml配置解決了上面的問題)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd " > <!-- 配置組件包掃描的位置 --> <context:component-scan base-package="cn.zj.spring"/> <!-- 讀取db.properties配置文件到Spring容器中 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置 阿里巴巴的 druid 數據源(連接池) --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- SpringEL 語法 ${key} --> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <!-- ${username}如果key是username,name 默認spring框架調用的當前操作系統的賬號 解決方案:可以統一給key加一個前綴 --> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxActive" value="${jdbc.maxActive}"/> </bean> <!-- 配置jdbcTemplate| --> <bean id="template" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入數據源(連接池) --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 開始Spring的事務配置 配置思路 1. 配置事務管理器 2.配置事務細節 把事務管理器配置好事務管理中 3.把事務管理器 使用AOP切入Service層 --> <!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入數據源(連接池) --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事務細節 ,事務增強/通知 transaction-manager :要增強事務管理器 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 屬性配置 --> <tx:attributes> <!-- 具體要增強的方法 (JointPoint) 事務一般對業務方法增強 當前例子:是對service層的轉賬方法增強 method :要增強的方法名 當前場景 :trans 方法 method實際開發一般可以分為兩類 DML :增刪改 DQL :查詢 我們可以使用 * 星號 作為通配符來配置 這兩方法 isolation: 事務的隔離級別,DEFAULT 默認就是當前數據庫默認級別 propagation :事務傳播規則(行為) timeout : 設置事務超時時間 read-only :是否是只讀事務 DML :非只讀事務 DQL :只讀事務(執行效率高) --> <!-- <tx:method name="trans" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5" read-only="false" /> --> <!-- DQL : 只讀事務 --> <tx:method name="select*" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5" read-only="true"/> <tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5" read-only="true"/> <tx:method name="query*" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5" read-only="true"/> <tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5" read-only="true"/> <!-- 其他操作 :非只讀事務 --> <tx:method name="*" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="5" read-only="false"/> </tx:attributes> </tx:advice> <!-- 使用aop把事務切入到service層 --> <aop:config> <!-- 配置切入點 --> <aop:pointcut expression="execution(* cn.zj.spring.service..*.*(..))" id="pt"/> <!-- 配置切面 = 切入點 +通知 <aop:aspect></aop:aspect> 一般開發者自定義aop功能使用 <aop:advisor advice-ref=""/> spring事務管理器配置時候使用 advice-ref :通知引用 底層默認使用的環繞通知 pointcut-ref :切入點引用 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/> <!-- 織入,Spring完成 --> </aop:config> </beans>
聲明式事務管理-基於注解配置
applicationContext.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd "> <!-- 配置組件包掃描的位置 --> <context:component-scan base-package="cn.sxt" /> <!-- 讀取db.properties配置文件到Spring容器中 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 配置 阿里巴巴的 druid 數據源(連接池) --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- SpringEL 語法 ${key} --> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <!-- ${username}如果key是username,name 默認spring框架調用的當前操作系統的賬號 解決方案:可以統一給key加一個前綴 --> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxActive" value="${jdbc.maxActive}" /> </bean> <!-- 配置jdbcTemplate| --> <bean id="template" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入數據源(連接池) --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 開始Spring的事務配置 配置思路 1. 配置事務管理器 2.配置事務細節 把事務管理器配置好事務管理中 3.把事務管理器 使用AOP切入Service層 --> <!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入數據源(連接池) --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 開啟事務注解驅動 transaction-manager: 是萬物管理器 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
service類
package cn.sxt.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Propagation; import cn.sxt.mapper.UserMapper; import cn.sxt.service.UserService; /* * * @Transactional * 開啟注解事務配置,一個 @Transactional只能對當前類有效,當前類下面所有方法配置一樣 * isolation=Isolation.REPEATABLE_READ, 隔離級別 propagation=Propagation.REQUIRED, 傳播行為(規則) timeout=5, 超時時間 readOnly=true 是否是只讀事務 * */ @Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, timeout = 5, readOnly = false) @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper mapper; /* * 真正的業務方法 一個業務方法有多個 功能(多個dao層方法)組成 */ @Override public void trans(Integer outId, Integer inId, Integer money) { mapper.updateIn(inId, money); System.out.println(1 / 0); mapper.updateOut(outId, money); } @Transactional(readOnly = true) public void selectByPrimaryKey(Integer id) { } @Transactional(readOnly = true) public void selectList() { } }
事物配置的注解和XML配置的選擇
Xml配置 : 代碼清晰,配置量少,容易維護
注解配置 : 侵入代碼,每個Service層類都得配置,針對不同的方法還得配置事務細節:如查詢方法配置只讀操作,不容易維護
建議選擇xml配置
小結Spring框架:
1.1. Spring 核心作用
解耦 (降低程序代碼與代碼的直接依賴關系)
1.2. 核心功能
Spring容器-下面所有操作都在Spring 容器中完成,Spring就是項目對象的管家
- IOC :控制反轉(將對象的創建權交給Spring管理)
(1) Xml 配置
① <bean id/name=’xxx’ class = “類的全限定名” scope=’作用范圍’ init-mehtod=’初始化方法’destory-mehtod=’銷毀方法’>
② 從Spring容器中讀取bean對象
(2) 注解配置 -貼在類上即可
① @Component 通用組件掃描創建
② @Controller 表現層(SpringMVC 專用)
③ @Service 業務層專用
④ @Repository 持久層/DAO/Mapper 專用
⑤ @Scope 作用范圍
⑥ @PostConstrcut 初始化方法
⑦ @PreDestory 銷毀方法
- DI : 依賴注入(將對象的屬性賦值,對象依賴關系維護交給Spring)
(1) XML配置
① <property name=’’ value/ref=’’> 屬性注入
② <constructor-arg name=’參數名’ value/ref=’’ >
③ P 命名空間
1) <bean id =’xx’ class =”Xxxx” p:屬性名=’值’ p:屬性名-ref=’引用類型’>
(2) 注解配置
① Spring框架制定
1) @Autowired 默認按照類型注入
- 可以貼在 字段(成員編寫),set方法,構造函數
2) @Qualifier 從多個相同類型中通過id指定唯一的那個bean
② JavaEE 官方指定
1) @Resource(name=’bean的id’)
- AOP :面向切面編程
(1) AOP 底層原理-Java動態代理
① JDK動態代理 : 只能有接口的類型進行代理
② CGLIB代理 :即可代理有接口類,有可以代理沒有接口的
(2) 專業術語
① JoinPoint 連接點(方法)
② PointCut 切入點 (某一類方法,規則)
③ Advice 通知/增強
④ Aspect 切面 = 切入點+通知
⑤ Weaving 織入(Spring自動完成)
- Spring事務管理
(1) 配置事務管理器
① JDBC/MyBatis ---->DataSourceTransactionManager
(2) 事務的隔離級別 4個
(3) 事務的傳播規則(行為) 7個
(4) 是否是只讀事務
(5) 使用AOP 將事務切入到 業務層
- Spring-Jdbc (了解)
(1) jdbcTemplate 模板類
① Update 更新方法(update,insert,delete)
② queryForObject : 單行查詢
③ Query : 多行查詢
- Spring-test
- SpringMVC