16. Spring之聲明式事務管理

Spring事務管理

1. Spring事務管理概述

1.1 編程式事務管理

​ 使用原生的JDBC API實現事務管理是所有事務管理方式的基石,同時也是最典型的編程式事務管理。編程式事務管理需要將事務管理代碼嵌入到業務方法中來控制事務的提交和回滾。在使用編程的方式管理事務時,必須在每個事務操作中包含額外的事務管理代碼。相對於核心業務而言,事務管理的代碼顯然屬於非核心業務,如果多個模塊都使用同樣模式的代碼進行事務管理,顯然會造成較大程度的代碼冗餘。

    /**
     *
     * 編程式事務:
     * TransactionFilter{
     *     try{
     *         // 獲取連接
     *         // 設置非自動提交
     *         chain.doFiler();
     *         //提交
     *     }catch(){
     *         //異常回滾
     *     }finally{
     *         //釋放資源
     *     }
     * }
     */

1.2 聲明式事務管理

以前通過複雜的編程來編寫一個事務,替換爲只需要告訴Spring哪個方法是事務方法即可;

  • Spring自動進行事務控制
  • 大多數情況下聲明式事務比編程式事務管理更好:它將事務管理代碼從業務方法中分離出來,以聲明的方式來實現事務管理。
  • 事務管理代碼的固定模式作爲一種橫切關注點,可以通過AOP方法模塊化,進而藉助Spring AOP框架實現聲明式事務管理。

1.3 Spring提供的事務管理器

  1. Spring既支持編程式事務管理,也支持聲明式的事務管理。
  2. Spring從不同的事務管理API中抽象出了一整套事務管理機制,讓事務管理代碼從特定的事務技術中獨立出來。開發人員通過配置的方式進行事務管理,而不必瞭解其底層是如何實現的。
  3. Spring的核心事務管理抽象是PlatformTransactionManager。它爲事務管理封裝了一組獨立於技術的方法。無論使用Spring的哪種事務管理策略(編程式或聲明式),事務管理器都是必須的。
  4. 事務管理器可以以普通的bean的形式聲明在Spring IOC容器中。

1.4 事務管理器的主要實現

  1. DataSourceTransactionManager:在應用程序中只需要處理一個數據源,而且通過JDBC存取。
  2. JtaTransactionManager:在JavaEE應用服務器上用JTA(Java Transaction API)進行事務管理
  3. 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 事務的隔離級別

  1. isolation 事務的隔離級別
  2. 數據庫事務詳解

4.2 事務的傳播行爲

  1. 事務傳播屬性可以在@Transactional註解的propagation屬性中定義。
  2. 當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在自己的事務中運行。事務的傳播行爲可以由傳播屬性指定。Spring定義了7種類傳播行爲。
  3. 在這裏插入圖片描述
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)

由於事務可以在行和表上獲得鎖,因此長事務會佔用資源,並對整體性能產生影響。如果一個事物只讀取數據但不做修改,數據庫引擎可以對這個事務進行優化。

  • 超時事務屬性:事務在強制回滾之前可以保持多久。這樣可以防止長期運行的事務佔用資源。
  • 只讀事務屬性 : 表示這個事務只讀取數據但不更新數據, 這樣可以幫助數據庫引擎優化事務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章