深入學習Spring框架(四)- 事務管理
1.什麼是事務?
事務(Transaction)是一個操作序列。這些操作要麼都做,要麼都不做,是一個不可分割的工作單位,是資料庫環境中的邏輯工作單位。事務是為了保證資料庫的完整性。例如:A給B轉賬,需要先減掉A的賬戶餘額再加到B的賬戶上,這兩個操作是一個整體,不可能扣掉A的錢不給B加上,或者只給B加沒有扣掉A的錢。也就是,一個事務每項任務都必須正確執行。如果有任一任務執行失敗,則整個事務就會被終止。此前對資料所作的任何修改都將被撤銷。
2.Spring事務控制
JDBC預設有進行事務的處理,但它是在持久層(dao層)處理的,一般情況我們需要把事務處理放到業務層(service層)。因為專案中有可能是幾個功能為一個完整的事務,如轉賬的A餘額減少和B餘額增加為一個事務,而這在持久層是兩個操作。
spring框架為我們提供了一組事務控制的應用程式介面(API)。使用Spring是事務代理,可以配置一次,不用重複編寫事務處理程式碼。spring的事務控制都是基於AOP的,它既可以使用程式設計的方式實現,也可以使用配置的方式實現。我們學習的重點是使用配置的方式實現。
3.資料庫併發問題(C)
資料庫的併發問題即多個客戶端同時同時訪問資料庫中某一條資料引發的問題,例如:秒殺搶購。這些問題歸結為5類,包括3類資料讀問題(髒讀,不可重複讀,幻讀)和2類資料更新問題(第一類丟失更新,第二類丟失更新)。
第一類更新丟失:
兩個事務更新相同資料,如果一個事務提交,另一個事務回滾,第一個事務的更新會被回滾
第二類丟失更新:
多個事務同時讀取相同資料,並完成各自的事務提交,導致最後一個事務提交會覆蓋前面所有事務對資料的改變
髒讀
第二個事務查詢到第一個事務未提交的更新資料,第二個事務根據該資料執行,但第一個事務回滾,第二個事務操作髒資料
幻讀:
一個事務查詢到了另一個事務已經提交的新資料,導致多次查詢資料不一致
不可重複讀:
一個事務查詢到另一個事務已經修改的資料,導致多次查詢資料不一致
4.資料庫事務的隔離級別(C)
針對上述的事務併發問題,資料庫提供了不同的事務隔離級別來處理不同的事務併發問題,事務隔離級別定義如下:
針對不同隔離級別可以解決的的如下五類問題:
5.解決丟失更新的方案
要解決丟失更新問題可以給資料庫表資料加鎖:
1.悲觀鎖
在操作當前資料的事務開啟事務就使用for update 鎖住當前資料(在Hibernate和MyBatis中都有悲觀鎖對應的解決方案)
2.樂觀鎖
為表新增一個version欄位。當前事務操作的時候都會比對當前事務情況的多次操作的版本號是否一致,如果不一致認為資料已經被更新(在Hibernate和MyBatis中都有樂觀鎖對應的解決方案)
6.Spring對事務的支援
Spring的事務管理主要包括3個介面:
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(); }
事務傳播規則
PlatformTransactionManager事務管理器
PlatformTransactionManager:介面統一抽象處理事務操作相關的方法;
1:TransactionStatus getTransaction(TransactionDefinition definition):
根據事務定義資訊從事務環境中返回一個已存在的事務,或者建立一個新的事務,並用TransactionStatus描述該事務的狀態。
2:void commit(TransactionStatus status):
根據事務的狀態提交事務,如果事務狀態已經標識為rollback-only,該方法執行回滾事務的操作。
3:void rollback(TransactionStatus status):
將事務回滾,當commit方法丟擲異常時,rollback會被隱式呼叫
在使用spring管理事務的時候,首先得告訴spring使用哪一個事務管理器,使用不同的框架(JdbcTemplate,MyBatis,Hibernate/JPA )使用事務管理器都不同
7.Spring事務的配置
Spring支援程式設計式事務管理和宣告式事務管理。
程式設計式事務管理:事務和業務程式碼耦合度太高。
宣告式事務管理:侵入性小,把事務從業務程式碼中抽離出來,使用AOP配置到配置檔案中,提高維護性。
7.1宣告式事務管理xml方式配置
1.匯入jar包
2.建立配置檔案並引入約束和名稱空間
<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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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/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 ">
配置檔案
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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/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 "> <!-- 配置註解包掃描 --> <context:component-scan base-package="com.gjs"/> <!-- 讀取db.properties 配置檔案 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 建立druid連線池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 屬性注入 setter方法注入 --> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxActive" value="${jdbc.maxActive}"/> </bean> <!-- 配置 JdbcTemplate 模板類 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 使用構造器注入,注入 dataSource 連線池 --> <constructor-arg name="dataSource" ref="dataSource"/> </bean> <!-- 1. 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入資料來源(連線池) --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2.事務相關配置:使用tx標籤 --> <!-- 配置事務通知 id:通知唯一標識 transaction-manager:依賴的事務管理器 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- <tx:method name=""/> 需要被事務管理的方法 ,一般是都是service層的方法 name : 被事務管理的方法名 isolation :事務的隔離級別(REPEATABLE_READ) propagation : 事務的傳播規則 (REQUIRED) read-only :是否是隻讀事務,預設false(DML 非只讀事務,DQL :只讀事務-效率相對高一些) timeout:配置事務超時時間 --> <!-- 事務方法配置細節 在實際開發中service的方法必須遵循一定編寫規則 DQL最好指定 字首規則 : selectXxx,queryXxx, findXxx,getXxx 我們可以使用 * 來配置指定規則的所有方法 DQL 一般配 只讀事務 read-only="true" DML 一般配 非只讀事務 read-only="false" --> <!-- DQL --> <tx:method name="select*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED"/> <tx:method name="query*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED"/> <tx:method name="find*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED"/> <tx:method name="get*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED"/> <!-- 非DQL --> <tx:method name="*" read-only="false" isolation="REPEATABLE_READ" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 3.配置AOP把事務切到service層 --> <aop:config proxy-target-class="true"> <!-- 配置切入點 --> <aop:pointcut expression="execution(* com.gjs.service..*.*(..))" id="pt"/> <!-- 配置切面:切入點+通知 <aop:aspect></aop:aspect> :使用自定義事務管理器進行aop配置事務 <aop:advisor advice-ref="" pointcut-ref=""/>:使用spring的事務管理器 advice-ref="":通知引用 pointcut-ref="":切入點引用 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/> </aop:config> </beans>
7.2宣告式事務管理註解方式配置
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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/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 "> <!-- 配置註解包掃描 --> <context:component-scan base-package="com.gjs"/> <!-- 讀取db.properties 配置檔案 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 建立druid連線池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 屬性注入 setter方法注入 --> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxActive" value="${jdbc.maxActive}"/> </bean> <!-- 配置 JdbcTemplate 模板類 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 使用構造器注入,注入 dataSource 連線池 --> <constructor-arg name="dataSource" ref="dataSource"/> </bean> <!-- 配置事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入資料來源(連線池) --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 開啟 事務處理的註解驅動 transaction-manager:引用的事務管理器 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
@Service /* @Transactional * 貼上此當前類已經被Spring事務管理 * 注意: @Transactional 只能對當前貼的Service類有效 * 常用屬性 : * isolation=Isolation.REPEATABLE_READ, 隔離級別 propagation=Propagation.REQUIRED,傳播規則 readOnly=true 是否只讀事務 * */@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED) public class AccountServiceImpl implements AccountService{ @Autowired private AccountDao dao; public void trans(Integer transOutId, Integer transInId, Integer money) { dao.tranOut(transOutId, money); System.out.println(1 / 0);// 模擬斷電 dao.tranIn(transInId, money); } //單獨為某一個方法配置具體的事物細節:如查詢方法,事物是隻讀的 @Transactional(readOnly=true) public Object getUser() { //查詢操作 return null; } @Transactional(readOnly=true) public List<Object> getUsers() { //查詢操作 return null; } }
&n