天天看點

基礎架構 spring事務的傳播機制與隔離級别

基礎架構 spring事務的傳播機制與隔離級别

    • 基礎架構 spring事務的傳播機制與隔離級别
      • 一、概念
        • 1. 什麼是事務
        • 2. 為什麼用事務
        • 3. 事務四個特性(ACID)
        • 4. 事務管理分類
      • 二、聲明式事務-注解方式
        • 1、在 spring 配置檔案配置事務管理器
        • 2、 在 spring 配置檔案,開啟事務注解
        • 3、在 service 類上( 或者 service 類裡面方法上面)添加事務注解
      • 三、@Transactional參數詳解
        • 注意事項
        • Transactional參數
        • 1. propagation:7事務傳播行為
        • 2. isolation:4種事務隔離級别。
        • 3. timeout:逾時時間。
        • 4. readOnly:是否隻讀。
        • 5. rollbackFor:復原。
        • 6. noRollbackFor:不會滾。
      • 四、聲明式事務-xml方式
      • 五、案例

基礎架構 spring事務的傳播機制與隔離級别

一、概念

1. 什麼是事務

事務是資料庫操作最基本單元,邏輯上一組操作,要麼都成功,如果有一個失敗所有操

作都失敗。

2. 為什麼用事務

舉個經典例子,銀行轉賬,A給B轉100塊錢,這是邏輯上一組操作,但是具體是兩個操作,第一步是A減少100塊錢,第二步是B增加100塊錢,正常情況兩部完成各自送出沒問題,問題是當A減少100塊錢,B還未增加的時候系統挂了,就會導緻資料不一緻問題,這就是事務要解決的問題。

3. 事務四個特性(ACID)

原子性,一緻性,隔離性,持久性

4. 事務管理分類

事務管理兩種方式:程式設計式事務管理 和 聲明式事務管理

聲明式事務管理:

(1)基于注解方式(使用)

(2)基于 xml 配置檔案方式

二、聲明式事務-注解方式

1、在 spring 配置檔案配置事務管理器

<!--建立事務管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入資料源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
           

2、 在 spring 配置檔案,開啟事務注解

<!-- (1)在 spring 配置檔案引入名稱空間 tx -->
<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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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.xsdhttp://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- (2)開啟事務注解 -->
<!--開啟事務注解-->
<tx:annotation-driven transactionmanager="transactionManager">< /tx:annotation-driven>
           

3、在 service 類上( 或者 service 類裡面方法上面)添加事務注解

(1)@Transactional,這個注解添加到類上面,也可以添加方法上面

(2)如果把這個注解添加類上面,這個類裡面所有的方法都添加事務

(3)如果把這個注解添加方法上面,為這個方法添加事務。

三、@Transactional參數詳解

注意事項

SpringBoot使用事物
@RestController
@EnableTransactionManagement // 開啟事務管理
public class TransactionalController {}

1. @EnableTransactionManagement注解其實在大多數情況下,不是必須的,最好在啟動類加上注解管理@EnableTransactionManagement。因為SpringBoot在 TransactionAutoConfiguration類裡為我們自動配置啟用了@EnableTransactionManagement注解。

2. 一般在service實作類添加注解@Transactional,添加注解的時候可以采用類上添加或者方法上添加,最好芳芳上,因為整個類中可能不是所有方法都需要開啟事務,比如查詢的方法不需要。

3. Transactional 注解隻能應用到 public 可見度的方法上。 如果應用在protected、private或者 package可見度的方法上,也不會報錯,不過事務設定不會起作用。

4. 預設情況下,Transactional 注解的事物所管理的方法中。
	如果方法抛出運作時異常或error,那麼會進行事務復原;
	如果方法抛出的是非運作時異常,那麼不會復原。
   
注:SQL異常屬于檢查異常(有的架構将SQL異常重寫為了運作時異常),但是有時我們寫SQL時,檢查異常并不會提示;而預設情況下,事物對檢查異常不會作出復原處理。

注:在很多時候,我們除了catch一般的異常或自定義異常外,我們還習慣于catch住Exception異常;然後再抛出Exception異常。但是Exception異常屬于非運作時異常(即:檢查異常),因為預設是運作時異常時事物才進行復原,那麼這種情況下,是不會復原的。我們可以在@Transacional注解中,通過rollbackFor = {Exception.class} 來解決這個問題。即:設定當Exception異常或Exception的所有任意子類異常時事物會進行復原。

注:被catch處理了的異常,不會被事物作為判斷依據;如果異常被catch 了,但是又在catch中抛出了新的異常,那麼事物會以這個新的異常作 為是否進行復原的判斷依據。


           

Transactional參數

Transactional參數 說明
propagation 事務的傳播行為,預設值為 REQUIRED。
isolation 事務的隔離度,預設值采用 DEFAULT
timeout 事務的逾時時間,預設值為-1,不逾時。如果設定了逾時時間(機關秒),那麼如果超過該時間限制了但事務還沒有完成,則自動復原事務。
readOnly 指定事務是否為隻讀事務,預設值為 false;為了忽略那些不需要事務的方法,比如讀取資料,可以設定 read-only 為 true。
rollbackFor 復原,用于指定能夠觸發事務復原的異常類型,如果有多個異常類型需要指定,各類型之間可以通過逗号分隔。{Exception.class, RunTimeException.class,……}
noRollbackFor 不會滾 ,抛出 no-rollback-for 指定的異常類型,不復原事務。{xxx1.class, xxx2.class,……}

1. propagation:7事務傳播行為

基礎架構 spring事務的傳播機制與隔離級别

spring7種事務傳播機制

2. isolation:4種事務隔離級别。

基礎架構 spring事務的傳播機制與隔離級别

3. timeout:逾時時間。

(1)事務需要在一定時間内送出,如果不送出進行復原

(2)預設值是-1,設定時間以秒為機關進行計算。

4. readOnly:是否隻讀。

(1)讀:查詢操作。寫:添加修改删除操作

(2)readOnly預設值是false,表示可以查詢,可以添加删除修改操作。

(3)設定readOnly值是true,表示隻能查詢操作。

5. rollbackFor:復原。

(1)設定出現哪些異常進行事務復原。

6. noRollbackFor:不會滾。

(1)設定出現哪些異常不進行事務復原。

四、聲明式事務-xml方式

1、在 spring 配置檔案中進行配置
第一步 配置事務管理器
第二步 配置通知第三步 配置切入點和切面
<!--1 建立事務管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入資料源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2 配置通知-->
<tx:advice id="txadvice">
<!--配置事務參數-->
<tx:attributes>
<!--指定哪種規則的方法上面添加事務-->
<tx:method name="accountMoney" propagation="REQUIRED"/>
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>
<!--3 配置切入點和切面-->
<aop:config>
<!--配置切入點-->
<aop:pointcut id="pt" expression="execution(*
com.atguigu.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
           

五、案例

package com.spring.dao;

/**
 * 使用者DAO
 *
 * @author zrj
 * @date 2020/12/20
 * @since V1.0
 **/
public interface UserDao {
    /**
     * 多錢
     */
    void addMoney();

    /**
     * 少錢
     */
    void reduceMoney();
}

           
package com.spring.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/**
 * 使用者DAO實作類
 *
 * @author zrj
 * @date 2020/12/20
 * @since V1.0
 **/
@Repository
public class UserDaoImpl implements UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * lucy轉賬100給mary
     * 少錢
     */
    @Override
    public void reduceMoney() {
        String sql = "update t_account set money=money-? where username=?";
        jdbcTemplate.update( sql, 100, "lucy" );
    }

    /**
     * 多錢
     */
    @Override
    public void addMoney() {
        String sql = "update t_account set money=money+? where username=?";
        jdbcTemplate.update( sql, 100, "mary" );
    }
}

           
package com.spring.service;

import com.spring.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

/**
 * 使用者service
 *
 * @author zrj
 * @date 2020/12/20
 * @since V1.0
 **/
@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    @Autowired
    private DataSourceTransactionManager transactionManager;

    /**
     * 1.程式設計式事務管理
     * 手動處理事務,異常事務復原
     */
    @Transactional
    public void transferAccounts() {
        // 開啟事務
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setName( "SomeTxName" );
        def.setPropagationBehavior( TransactionDefinition.PROPAGATION_REQUIRED );

        TransactionStatus status = transactionManager.getTransaction( def );
        try {
            userDao.reduceMoney();

            //模拟異常
            int i = 10 / 0;

            //mary多100
            userDao.addMoney();

            // 送出事務
            transactionManager.commit( status );
        } catch (Exception ex) {
            // 事務復原
            transactionManager.rollback( status );
            throw ex;
        }
    }

    /**
     * 2. 聲明式事務,注解方式
     * 配置檔案需要建立事務,開啟事務
     * 方法異常事務復原,如果捕獲異常tryCatch則不會抛出異常,不會復原
     */

    @Transactional(rollbackFor = Exception.class)
    public void accountMoney() {
        //lucy少100
        userDao.reduceMoney();

        //模拟異常
        int i = 10 / 0;

        //mary多100
        userDao.addMoney();
    }

    /**
     * 3. 聲明式事務,xml配置方式
     */
    public void accountMoneyXml() {
        //lucy少100
        userDao.reduceMoney();

        //模拟異常
        int i = 10 / 0;

        //mary多100
        userDao.addMoney();
    }

    /**
     * 4. 聲明式事務,Config配置方式
     */
    @Transactional
    public void accountMoneyConfig() {
        //lucy少100
        userDao.reduceMoney();

        //模拟異常
        //int i = 10 / 0;

        //mary多100
        userDao.addMoney();
    }
}

           
package com.spring.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
 * spring配置類
 *
 * @author zrj
 * @date 2020/12/21
 * @since V1.0
 **/
@Configuration //配置類
@ComponentScan(basePackages = "com.spring") //元件掃描
@EnableTransactionManagement //開啟事務
public class TxConfig {

    /**
     * 建立資料庫連接配接池
     * @return
     */
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName( "com.mysql.jdbc.Driver" );
        dataSource.setUrl( "jdbc:mysql:///user_db" );
        dataSource.setUsername( "root" );
        dataSource.setPassword( "123456" );
        return dataSource;
    }

    /**
     * 建立JdbcTemplate對象
     * @param dataSource
     * @return
     */
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        //到ioc容器中根據類型找到dataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入dataSource
        jdbcTemplate.setDataSource( dataSource );
        return jdbcTemplate;
    }

    /**
     * 建立事務管理器
     * @param dataSource
     * @return
     */
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource( dataSource );
        return transactionManager;
    }
}

           

bean1

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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.atguigu"></context:component-scan>

    <!-- 資料庫連接配接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="url" value="jdbc:mysql:///user_db" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>

    <!-- JdbcTemplate對象 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入dataSource-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--建立事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入資料源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--開啟事務注解-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
           

bean2

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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.spring"></context:component-scan>

    <!-- 資料庫連接配接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="url" value="jdbc:mysql:///user_db"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    </bean>

    <!-- JdbcTemplate對象 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入dataSource-->
        <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:advice id="txadvice">
        <!--配置事務參數-->
        <tx:attributes>
            <!--指定哪種規則的方法上面添加事務-->
            <tx:method name="accountMoneyXml" propagation="REQUIRED"/>
            <!--<tx:method name="account*"/>-->
        </tx:attributes>
    </tx:advice>

    <!--3 配置切入點和切面-->
    <aop:config>
        <!--配置切入點-->
        <aop:pointcut id="pt" expression="execution(* com.spring.service.UserService.*(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
    </aop:config>
</beans>
           
package com.spring.test;

import com.spring.config.TxConfig;
import com.spring.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * spring事務測試類
 *
 * @author zrj
 * @date 2020/12/21
 * @since V1.0
 **/
public class SpringTransactionalTest {

    /**
     * 程式設計式事務測試
     */
    @Test
    public void transferAccountsTest() {

        ApplicationContext context = new ClassPathXmlApplicationContext( "bean1.xml" );
        UserService userService = context.getBean( "userService", UserService.class );
        userService.transferAccounts();
    }

    /**
     * 聲明式事務測試,注解方式
     */
    @Test
    public void accountMoneyTest() {

        ApplicationContext context = new ClassPathXmlApplicationContext( "bean1.xml" );
        UserService userService = context.getBean( "userService", UserService.class );
        userService.accountMoney();
    }

    /**
     * 聲明式事務測試,xml配置方式
     */
    @Test
    public void accountMoneyXmlTest() {

        ApplicationContext context = new ClassPathXmlApplicationContext( "bean2.xml" );
        UserService userService = context.getBean( "userService", UserService.class );
        userService.accountMoneyXml();
    }

    /**
     * 聲明式事務測試,注解方式,配置類
     */
    @Test
    public void accountMoneyConfigTest() {

        ApplicationContext context = new AnnotationConfigApplicationContext( TxConfig.class );
        UserService userService = context.getBean( "userService", UserService.class );
        userService.accountMoneyConfig();
    }


}