文章目錄
Spring事務管理
1. Spring
事務管理概述
1.1 編程式事務管理
使用原生的
JDBC API
實現事務管理是所有事務管理方式的基石,同時也是最典型的編程式事務管理。編程式事務管理需要將事務管理代碼嵌入到業務方法中來
控制事務的提交和回滾。在使用編程的方式管理事務時,必須在每個事務操作中包含額外的事務管理代碼。相對於核心業務而言,事務管理的代碼顯然屬於非核心業務,如果多個模塊都使用同樣模式的代碼進行事務管理,顯然會造成較大程度的代碼冗餘。
/**
*
* 編程式事務:
* TransactionFilter{
* try{
* // 獲取連接
* // 設置非自動提交
* chain.doFiler();
* //提交
* }catch(){
* //異常回滾
* }finally{
* //釋放資源
* }
* }
*/
1.2 聲明式事務管理
以前通過複雜的編程來編寫一個事務,替換爲只需要告訴Spring哪個方法是事務方法即可;
- Spring自動進行事務控制
- 大多數情況下聲明式事務比編程式事務管理更好:它將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理。
事務管理代碼的固定模式作爲一種橫切關注點
,可以通過AOP方法模塊化,進而藉助Spring AOP框架實現聲明式事務管理。
1.3 Spring提供的事務管理器
- Spring既支持編程式事務管理,也支持聲明式的事務管理。
- Spring從不同的事務管理API中抽象出了一整套事務管理機制,讓事務管理代碼從特定的事務技術中獨立出來。開發人員通過配置的方式進行事務管理,而不必瞭解其底層是如何實現的。
Spring的核心事務管理抽象是PlatformTransactionManager。它爲事務管理封裝了一組獨立於技術的方法。無論使用Spring的哪種事務管理策略(編程式或聲明式),事務管理器都是必須的。
- 事務管理器可以以普通的bean的形式聲明在Spring IOC容器中。
1.4 事務管理器的主要實現
DataSourceTransactionManager:在應用程序中只需要處理一個數據源,而且通過JDBC存取。
JtaTransactionManager:在JavaEE應用服務器上用JTA(Java Transaction API)進行事務管理
HibernateTransactionManager:用Hibernate框架存取數據庫
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-G9YJb1PH-1590661389710)(assets/wps1-1590656404617.jpg)]
2. 基於註解的事務配置
2.1 測試需要數據表
account
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`username` varchar(50) NOT NULL,
`balance` int(11) DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
INSERT INTO `account` VALUES ('Tom', 1000);
SET FOREIGN_KEY_CHECKS = 1;
book
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`isbn` varchar(50) NOT NULL,
`book_name` varchar(100) DEFAULT NULL,
`price` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
INSERT INTO `book` VALUES ('ISBN-001', 'book01', 100);
SET FOREIGN_KEY_CHECKS = 1;
book_stock
DROP TABLE IF EXISTS `book_stock`;
CREATE TABLE `book_stock` (
`isbn` varchar(50) NOT NULL,
`stock` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
INSERT INTO `book_stock` VALUES ('ISBN-001', 1000);
SET FOREIGN_KEY_CHECKS = 1;
2.2 添加依賴
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<!-- <scope>test</scope>-->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<scope>provided</scope>
</dependency>
<!--AOP-->
<dependency>
<groupId>net.sourceforge.cglib</groupId>
<artifactId>com.springsource.net.sf.cglib</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>com.springsource.org.aspectj.weaver</artifactId>
<version>1.6.8.RELEASE</version>
</dependency>
<!--事務-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
</dependencies>
2.3 配置文件
db.properties
jdbc.user=root
jdbc.password=******
jdbc.jdbcUrl=jdbc:mysql://cdb-o6r75r3g.bj.tencentcdb.com:10015/tx
jdbc.driverClass=com.mysql.jdbc.Driver
applicationContext.xml
<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:tx="http://www.springframework.org/schema/tx"
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.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:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--組件掃描-->
<context:component-scan base-package="cn.justweb"></context:component-scan>
<!--數據源的配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.jdbcUrl}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置JdbcTemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--1. 配置事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.開啓基於註解的書屋控制模式;-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
<!--3.給事務方法加註解-->
</beans>
2.4 BookDao
/**
* @Date 2020/5/28 14:08
* @Version 10.21
* @Author DuanChaojie
*/
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 減去消費的錢數
*/
public void updateBalance(String userName,int price){
String sql = "UPDATE account set balance=balance-? where username=?";
// 測試使用
//int a = 10/0;
jdbcTemplate.update(sql,price,userName);
}
/**
* 獲取餘額
* @return
*/
public int getPrice(String isbn){
String sql = "select price from book where isbn=?";
Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
return price;
}
/**
* 減去庫存數量
*/
public void updateStock(String isbn){
String sql = "update book_stock set stock=stock-1 where isbn=?";
jdbcTemplate.update(sql,isbn);
}
}
2.5 BookService
/**
* @Date 2020/5/28 14:22
* @Version 10.21
* @Author DuanChaojie
*/
@Service
public class BookService {
@Autowired
private BookDao bookDao;
@Transactional
public void checkout(String username,String isbn){
// 更新庫存
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
// 更新餘額
bookDao.updateBalance(username,price);
}
}
2.6 測試
/**
* @Date 2020/5/28 14:25
* @Version 10.21
* @Author DuanChaojie
*/
public class TxTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
/**
* 沒有事物的情況分析:使updateBalance()方法出錯測試其結果:
* 結果就是:庫存更新了而餘額沒有更新
* 添加過@Transactional後就解決了以上問題。
*/
@Test
public void test(){
BookService bookService = ioc.getBean(BookService.class);
System.out.println("bookService = " + bookService);
System.out.println(bookService.getClass().getClassLoader());
bookService.checkout("Tom","ISBN-001");
System.out.println("結賬完成!");
}
}
3. 基於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:tx="http://www.springframework.org/schema/tx"
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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="cn.justweb"></context:component-scan>
<!-- 0、引入外部配置文件 -->
<context:property-placeholder location="classpath:dbconfig.properties" />
<!-- 1、配置數據源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.jdbcUrl}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 2、配置JdbcTemplate操作數據庫 value="#{pooledDataSource}" ref="pooledDataSource"-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" value="#{dataSource}"></property>
</bean>
<!-- 3、配置聲明式事務
1)、Spring中提供事務管理器(事務切面),配置這個事務管理器
2)、開啓基於註解的事務式事務;依賴tx名稱空間
3)、給事務方法加註解
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
基於xml配置的事務;依賴tx名稱空間和aop名稱空間
1)、Spring中提供事務管理器(事務切面),配置這個事務管理器
2)、配置出事務方法;
3)、告訴Spring哪些方法是事務方法;
(事務切面按照我們的切入點表達式去切入事務方法)
-->
<aop:config>
<aop:pointcut expression="execution(* cn.justweb.ser*.*.*(..))" id="txPoint"/>
<!--
事務建議;事務增強
advice-ref:指向事務管理器的配置
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>
<!--
配置事務管理器;
事務建議;事務增強;事務屬性;
transaction-manager="transactionManager":指定是配置哪個事務管理器;
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事務屬性 -->
<tx:attributes>
<!-- 指明哪些方法是事務方法;
切入點表達式只是說,事務管理器要切入這些方法,
哪些方法加事務使用tx:method指定的 -->
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED" timeout="10"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 都用;重要的用配置,不重要的用註解 -->
</beans>
4. Spring
事務管理細節
@Transactional
中的屬性,比如說隔離級別,事務傳播行爲,超時時間,是否只讀等。
/**
* 事務細節:
*
* 異常分類:
* 運行時異常:可以不用處理默認都回滾
* 編譯時異常: 要麼try要麼throws 默認不回滾。
*
* noRollbackForClassName String[] 哪些異常事務可以不回滾
* noRollbackForClassName String[]
*
* rollbackFor String[] 哪些異常事務需要回滾
* rollbackForClassName String[]
*
*/
4.1 事務的隔離級別
- isolation 事務的隔離級別
- 數據庫事務詳解
4.2 事務的傳播行爲
- 事務傳播屬性可以在
@Transactional
註解的propagation
屬性中定義。- 當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在自己的事務中運行。事務的傳播行爲可以由傳播屬性指定。Spring定義了7種類傳播行爲。
4.2.1 REQUIRED傳播行爲
@Transactional(propagation = Propagation.REQUIRED)
required
4.2.2 REQUIRES_NEW傳播行爲
@Transactional(propagation = Propagation.REQUIRES_NEW)
requires_new
/**
* 有事務的業務邏輯,容器中保存的是這個業務邏輯的代理對象
* @throws FileNotFoundException
*
*
* multx(){
* //REQUIRED
* A(){
* //REQUIRES_NEW
* B(){}
* //REQUIRED
* c(){}
* }
*
* //REQUIRES_NEW
* D(){
* DDDD()// REQUIRES_NEW不崩,REQUIRED崩
* //REQUIRED
* E(){
* //REQUIRES_NEW
* F(){
* //int num = 10/0(E崩,G崩,D崩,A,C崩)
* }
* }
* //REQUIRES_NEW
* G(){}
* }
*
*
* 10/0(B成功,D整個分支下全部成功)
* 任何處崩,已經執行的REQUIRES_NEW都會成功;
*
* 如果是REQUIRED;事務的屬性都是繼承於大事務的;
* 而propagation=Propagation.REQUIRES_NEW可以調整
* 默認:REQUIRED;
*
* REQUIRED:將之前事務用的connection傳遞給這個方法使用;
* REQUIRES_NEW:這個方法直接使用新的connection;
* }
*
*/
4.3 事務的超時和只讀屬性
@Transactional(timeout = 10,readOnly = true)
由於事務可以在行和表上獲得鎖,因此長事務會佔用資源,並對整體性能產生影響。如果一個事物只讀取數據但不做修改,數據庫引擎可以對這個事務進行優化。
- 超時事務屬性:事務在強制回滾之前可以保持多久。這樣可以防止長期運行的事務佔用資源。
- 只讀事務屬性 : 表示這個事務只讀取數據但不更新數據, 這樣可以幫助數據庫引擎優化事務。