天天看點

Spring 架構(聲明式)事務學習總結Spring事務_事務簡介Spring事務_Spring事務管理方案 Spring事務_Spring事務管理器Spring事務_事務控制的APISpring事務_事務的相關配置Spring事務_事務的傳播行為 Spring事務_事務的隔離級别 Spring事務_注解配置聲明式事務

 說明 :聲明式事務部分:參考網上課程,自己寫的總結,如有侵權,聯系删除。

目錄

Spring事務_事務簡介

Spring事務_Spring事務管理方案

 Spring事務_Spring事務管理器

Spring事務_事務控制的API

PlatformTransactionManager接口

Spring事務_事務的相關配置

Spring事務_事務的傳播行為 

Spring事務_事務的隔離級别

 Spring事務_注解配置聲明式事務

Spring事務_事務簡介

事務:不可分割的原子操作。即一系列的操作要麼同時成功,要麼 同時失敗。

開發過程中,事務管理一般在service層,service層中可能會操作多次資料庫,這些操作是不可分割的。否則當程式報錯時,可能會造成資料異常。

如:張三給李四轉賬時,需要兩次操作資料庫:張三存款減少、李 四存款增加。如果這兩次資料庫操作間出現異常,則會造成資料錯誤。

  1. 準備資料庫
    CREATE DATABASE `student` ;
    USE `spring`;
    DROP TABLE IF EXISTS `account`;
    CREATE TABLE `account` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(255) DEFAULT NULL,
      `balance` double DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT
    CHARSET=utf8;
    insert  into `account`(`id`,`username`,`balance`) values (1,'張三',1000),(2,'李四',1000);
               
  2. 建立maven項目,引入依賴
    <dependencies>
            <!--mybatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.7</version>
            </dependency>
            <!--mysql驅動包-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.27</version>
            </dependency>
            <!--druid連接配接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.2.5</version>
            </dependency>
            <!--spring-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.3.21</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>5.3.13</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.3.13</version>
            </dependency>
            <!-- MyBatis與Spring的整合包,該包可以讓Spring建立MyBatis的對象 -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>2.0.6</version>
            </dependency>
            <!-- junit,如果Spring5整合junit,則junit版本至少在4.12以上 -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.3.13</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
               
  3. 建立配置檔案
    <?xml version="1.0" encoding="UTF-8"?>
    <beans  xmlns="http://www.springframework.org/schema/beans"
            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="com.dream"></context:component-scan>
        <!-- 讀取配置檔案 classpath:db.properties -->
        <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
        <!-- 建立druid資料源對象 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driverClassName}"></property>
            <property name="url" value="${jdbc.url}"></property>
            <property name="username" value="${jdbc.username}"></property>
            <property name="password" value="${jdbc.password}"></property>
        </bean>
        <!-- Spring建立封裝過的SqlSessionFactory-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- Spring建立封裝過的SqlSession -->
        <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
            <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
        </bean>
        <!-- MapperScannerConfigurer 該對象可以自動掃描持久層接口,并為接口建立代理對象 -->
        <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!-- 配置掃描的接口包 -->
            <property name="basePackage" value="com.dream.dao"></property>
        </bean>
    </beans>
               
    jdbc.driverClassName=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql:///student
    jdbc.username=root
    jdbc.password=
               
  4. 編寫Java代碼
    // 賬戶
    public class Account {
        private int id; // 賬号
        private String username; // 使用者名
        private double balance; // 餘額
        
        // 省略getter/setter/tostring/構造方法
    }
               
    @Repository
    public interface AccountDao {
        // 根據id查找使用者
        @Select("select * from account where id = #{id}")
        Account findById(int id);
        // 修改使用者
        @Update("update account set balance =#{balance} where id = #{id}")
        void update(Account account);
    }
               
    @Service
    public class AccountService {
        @Autowired
        private AccountDao accountDao;
        /**
         * 轉賬
         * @param id1 轉出人id
         * @param id2 轉入人id
         * @param price 金額
         */
        public void transfer(int id1, int id2, double price) {
            // 轉出人減少餘額
            Account account1 =accountDao.findById(id1);
            account1.setBalance(account1.getBalance()-price);
            accountDao.update(account1);
            int i = 1/0; // 模拟程式出錯
            // 轉入人增加餘額
            Account account2 = accountDao.findById(id2);
            account2.setBalance(account2.getBalance()+price);
            accountDao.update(account2);
       }
    }
               
  5. 測試
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations="classpath:applicationContext.xml")
    public class AccountServiceTest {
        @Autowired
        private AccountService accountService;
        @Test
        public void testTransfer(){
            accountService.transfer(1,2,500);
       }
    }
               
    出錯了把,第一個人的餘額減少了,中途遇到除數為0的異常,結果呢後邊不執行了。此時沒有事務管理,是以事務處理位于業務層,即一個service方法是不能分割的,要麼都成功,要麼都失敗,也就是事務復原。

Spring事務_Spring事務管理方案

在service層手動添加事務可以解決該問題:

@Service(value = "accountServic2")
public class AccountServic2 {
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private SqlSessionTemplate sqlSession;
    public void transfer(int id1, int id2, double price) {
        try{
            // account1修改餘額
            Account account1 = accountDao.findById(id1);
            account1.setBalance(account1.getBalance()- price);
            accountDao.update(account1);
            int i = 1/0; // 模拟轉賬出錯
            // account2修改餘額
            Account account2 = accountDao.findById(id2);
            account2.setBalance(account2.getBalance()+price);
            accountDao.update(account2);
            sqlSession.commit();
        }catch(Exception ex){
            sqlSession.rollback();
        }
    }
}
           
java.lang.UnsupportedOperationException: Manual rollback is not allowed over a Spring managed SqlSession
           

但在Spring管理下不允許手動送出和復原事務。此時我們需要使用 Spring的事務管理方案,在Spring架構中提供了兩種事務管理方案: 

  1. 程式設計式事務:通過編寫代碼實作事務管理。
  2. 聲明式事務:基于AOP技術實作事務管理。

在Spring架構中,程式設計式事務管理很少使用,我們對聲明式事務管 理進行詳細學習。

Spring的聲明式事務管理在底層采用了AOP技術,其最大的優點在于無需通過程式設計的方式管理事務,隻需要在配置檔案中進行相關的 規則聲明,就可以将事務規則應用到業務邏輯中。

使用AOP技術為service方法添加如下通知:

Spring 架構(聲明式)事務學習總結Spring事務_事務簡介Spring事務_Spring事務管理方案 Spring事務_Spring事務管理器Spring事務_事務控制的APISpring事務_事務的相關配置Spring事務_事務的傳播行為 Spring事務_事務的隔離級别 Spring事務_注解配置聲明式事務

 Spring事務_Spring事務管理器

Spring依賴事務管理器進行事務管理,事務管理器即一個通知類,我們為該通知類設定切點為service層方法即可完成事務自動管理。 由于不同技術操作資料庫,進行事務操作的方法不同。如:JDBC提 交事務是 connection.commit() ,MyBatis送出事務是 sqlSession.commit() ,

是以 Spring提供了多個事務管理器。

事務管理器名稱 作用
org.springframework.jdbc.datasource.DataSourceTransactionManager 針對JDBC技術提供的事務管理器。适用于JDBC和MyBatis。
org.springframework.orm.hibernate3.HibernateTransactionManager 針對于Hibernate架構提供的事務管理器。适用于Hibernate架構。
org.springframework.orm.jpa.JpaTransactionManager 針對于JPA技術提供的事務管理器。适用于JPA技術。
org.springframework.transaction.jta.JtaTransactionManager 跨越了多個事務管理源。适用在兩個或者是多個不同的資料源中實作事務控制。

我們使用MyBatis操作資料庫,接下來使用 DataSourceTransactionManager 進行事務管理。

  1. 引入依賴
    <!-- 事務管理 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.13</version>
    </dependency>
    <!-- AspectJ -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
               
  2. 在配置檔案中引入限制
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    
    
    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
               
  3. 進行事務配置
    <!-- 事務管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 進行事務相關配置 -->
        <tx:advice id = "txAdvice">
            <tx:attributes>
                <tx:method name="*"/>
            </tx:attributes>
        </tx:advice>
        <!-- 配置切面 -->
        <aop:config>
            <!-- 配置切點 -->
            <aop:pointcut id="pointcut"
                          expression="execution(* com.dream.service.*.*(..))"/>
            <!-- 配置通知 -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor>
        </aop:config>
               
  4. 這就可以了

Spring事務_事務控制的API

Spring進行事務控制的功能是由三個接口提供的,這三個接口是 Spring實作的,在開發中我們很少使用到,隻需要了解他們的作用即可:

PlatformTransactionManager接口

PlatformTransactionManager是Spring提供的事務管理器接口,所 有事務管理器都實作了該接口。該接口中提供了三個事務操作方 法:

  1. TransactionStatus getTransaction(TransactionDefinition definition):擷取事務狀态資訊。
  2. void commit(TransactionStatus status):事務送出
  3. void rollback(TransactionStatus status):事務復原

TransactionDefinition接口

TransactionDefinition是事務的定義資訊對象,它有如下方法:

  1. String getName():擷取事務對象名稱。
  2. int getIsolationLevel():擷取事務的隔離級别。
  3. int getPropagationBehavior():擷取事務的傳播行為。
  4. int getTimeout():擷取事務的逾時時間。
  5. boolean isReadOnly():擷取事務是否隻讀。

TransactionStatus接口

TransactionStatus是事務的狀态接口,它描述了某一時間點上事務的狀态資訊。它有如下方法:

  1. void flush() 重新整理事務
  2. boolean hasSavepoint() 擷取是否存在儲存點
  3. boolean isCompleted() 擷取事務是否完成
  4. boolean isNewTransaction() 擷取是否是新事務
  5. boolean isRollbackOnly() 擷取是否復原
  6. void setRollbackOnly() 設定事務復原

Spring事務_事務的相關配置

在 <tx:advice>中可以進行事務的相關配置:

<tx:advice id="txAdvice">
    <tx:attributes>
        <tx:method name="*"/>
        <tx:method name="find*" read-only="true"/>
    </tx:attributes>
</tx:advice>
           
 <tx:method />中的屬性:
  • name:指定配置的方法。 * 表示所有方法,
  • find* 表示所有以find開頭的方法。
  • read-only:是否是隻讀事務,隻讀事務不存在資料的修改,資料庫将會為隻讀事務提供一些 優化手段,會對性能有一定提升,建議在查詢中開啟隻讀事務。
  • timeout:指定逾時時間,在限定的時間内不能完成所有操作就會抛異常。預設永不逾時
  • rollback-for:指定某個異常事務復原,其他異常不復原。預設所有異常復原。
  • no-rollback-for:指定某個異常不復原,其他異常復原。預設所有異常復原。
  • propagation:事務的傳播行為
  • isolation:事務的隔離級别

Spring事務_事務的傳播行為 

事務傳播行為是指多個含有事務的方法互相調用時,事務如何在這 些方法間傳播。

如果在service層的方法中調用了其他的service方法,假設每次執行 service方法都要開啟事務,此時就無法保證外層方法和内層方法處 于同一個事務當中。

// method1的所有方法在同一個事務中
public void method1(){
    // 此時會開啟一個新事務,這就無法保證method1()
中所有的代碼是在同一個事務中
    method2();
    System.out.println("method1");
}
public void method2(){
    System.out.println("method2");
}
           

事務的傳播特性就是解決這個問題的,Spring幫助我們将外層方法 和内層方法放入同一事務中。

傳播行為 介紹
REQUIRED 預設。支援目前事務,如果目前沒有事務,就建立一個事務。這是最常見的 選擇。
SUPPORTS 支援目前事務,如果目前沒有事務,就以非事務方式執行。
MANDATORY 支援目前事務,如果目前沒有事務,就抛出異常。
REQUIRES_NEW 建立事務,如果目前存在事務,把目前事務挂起。
NOT_SUPPORTED 以非事務方式執行操作,如果目前存在事務,就把目前事務挂起。
NEVER 以非事務方式執行,如果目前存在事務,則抛出異常。
NESTED 必須在事務狀态下執行,如果沒有事務則建立事務,如果目前有事務則建立 一個嵌套事務

Spring事務_事務的隔離級别

事務隔離級别反映事務送出并發通路時的處理态度,隔離級别越高,資料出問題的可能性越低,但效率也會越低。

隔離級别 髒讀 不可重複讀 幻讀
READ_UNCOMMITED(讀取未送出内容) Yes Yes Yes
READ_COMMITED(讀取送出内容) NO Yes Yes
REPEATABLE_READ(重複讀) NO NO Yes
SERIALIZABLE(可串行化) NO NO NO

如果設定為DEFAULT會使用資料庫的隔離級别。

SqlServer , Oracle預設的事務隔離級别是READ_COMMITED

Mysql的預設隔離級别是REPEATABLE_READ

 Spring事務_注解配置聲明式事務

Spring事務_注解配置聲明式事務

  1. 注冊事務注解驅動
    <!-- 注冊事務注解驅動 -->
    <tx:annotation-driven transaction-manager="transactionManager">
    </tx:annotation-driven>
               
  2. 在需要事務支援的方法或類上加@Transactional
    @Service
    // 作用于類上時,該類的所有public方法将都具有該
    類型的事務屬性
    @Transactional(propagation = Propagation.REQUIRED,isolation =Isolation.DEFAULT)
    public class AccountService {
        @Autowired
        private AccountDao accountDao;
        /**
         * 轉賬
         * @param id1 轉出人id
         * @param id2 轉入人id
         * @param price 金額
         */
        // 作用于方法上時,該方法将都具有該類型的事務屬性
        @Transactional(propagation = Propagation.REQUIRED,isolation =Isolation.DEFAULT)
        public void transfer(int id1, int id2,double price) {
            // account1修改餘額
            Account account1 = accountDao.findById(id1);
            account1.setBalance(account1.getBalance()-price);
            accountDao.update(account1);
            int i = 1/0; // 模拟轉賬出錯
            // account2修改餘額
            Account account2 = accountDao.findById(id2);
            account2.setBalance(account2.getBalance()+price);
            accountDao.update(account2);
       }
    }
               

spring 學習結束!!!!!!