Spring Transaction 聲明式事務管理

LOGO

參考資料:https://lfvepclr.gitbooks.io/spring-framework-5-doc-cn/content/

1. Spring 事務特性

事務 : 是邏輯上一組操作,要麼全都成功,要麼全都失敗。
事務特性 : ACID

  • 原子性 : 事務不可分割
  • 一致性 : 事務執行的前後,數據完整性保持一致
  • 隔離性 : 一個事務執行的時候,不應該受到其他事務的打擾
  • 持久性 : 一旦結束,數據就永久的保存到數據庫

如果不考慮隔離性:
髒讀 : 一個事務讀到另一個事務未提交數據
不可重複讀 : 一個事務讀到另一個事務已經提交數據(update)導致一個事務多次查詢結果不一致
虛讀 : 一個事務讀到另一個事務已經提交數據(insert)導致一個事務多次查詢結果不一致

事務的隔離級別:

  • 未提交讀 : 以上情況都有可能發生。
  • 已提交讀 : 避免髒讀,但不可重複讀,虛讀是有可能發生。
  • 可重複讀 : 避免髒讀,不可重複讀,但是虛讀有可能發生。
  • 串行的 : 避免以上所有情況。

mySQL 數據庫存儲引擎:innoDB(支持事務)、MyISAM(不支持事務)

2. Spring 事務API

  • PlatformTransactionManager : 平臺事務管理器

    • getTransaction(TransactionDefinition definition)
    • rollback(TransactionStatus status)
    • commit(TransactionStatus status)
  • TransactionDefinition : 事務定義

    • ISOLation_XXX:事務隔離級別(isolation)
      • ISOLATION_DEFAULT : 默認級別,Mysql --> repeatable_read | Oracle -->> read_commited
      • ISOLATION_READ_UNCOMMITTED
      • ISOLATION_READ_COMMITTED
      • ISOLATION_REPEATABLE_READ
      • ISOLATION_SERIALIZABLE
    • PROPAGATION_XXX:事務的傳播行爲
  • TransactionStatus : 事務狀態
    是否有保存點
    是否是一個新的事務
    事務是否已經提交

關係 :

PlatformTransactionManager 通過 TransactionDefinition 設置事務相關信息管理事務,管理事務過程中,產生一些事務狀態,狀態由 TransactionStatus 記錄。

API詳解:

PlatformTransactionManager : 接口
Spring爲不同的持久化框架提供了不同 PlatformTransactionManager 接口實現。

使用Spring JDBC或iBatis 進行持久化數據時使用(★):

  • org.springframework.jdbc.datasource.DataSourceTransactionManager

使用Hibernate進行持久化數據時使用:

  • org.springframework.orm.hibernate.HibernateTransactionManager

使用JPA進行持久化時使用:

  • org.springframework.orm.jpa.JpaTransactionManager

當持久化機制是Jdo時使用:

  • org.springframework.jdo.JdoTransactionManager

使用一個JTA實現來管理事務,在一個事務跨越多個資源時必須使用:

  • org.springframework.transaction.jta.JtaTransactionManager

3. Spring 事務傳播行爲

不是JDBC事務管理,用來解決實際開發的問題。

傳播行爲:解決業務層之間調用的事務的關係 (ServiceA.m() 方法與 ServiceB.m() 方法)

  • PROPAGATION_REQUIRED : 支持當前事務,如果不存在 就新建一個

    A,B——如果A有事務,B使用A的事務,如果A沒有事務,B就開啓一個新的事務(A,B是在一個事務中)

  • PROPAGATION_SUPPORTS : 支持當前事務,如果不存在,就不使用事務

    A,B——如果A有事務,B使用A的事務,如果A沒有事務,B就不使用事務

  • PROPAGATION_MANDATORY : 支持當前事務,如果不存在,拋出異常

    A,B——如果A有事務,B使用A的事務,如果A沒有事務,拋出異常

  • PROPAGATION_REQUIRES_NEW : 如果有事務存在,掛起當前事務,創建一個新的事務

    A,B——如果A有事務,B將A的事務掛起,重新創建一個新的事務(A,B不在一個事務中,事務互不影響)

  • PROPAGATION_NOT_SUPPORTED : 以非事務方式運行,如果有事務存在,掛起當前事務

    A,B——非事務的方式運行,A有事務,就會掛起當前的事務

  • PROPAGATION_NEVER : 以非事務方式運行,如果有事務存在,拋出異常

  • PROPAGATION_NESTED : 如果當前事務存在,則嵌套事務執行。基於 SavePoint 技術

    A,B——A有事務,A執行之後,將A事務執行之後的內容保存到SavePoint;B事務有異常的話,用戶需要自己設置事務提交還是回滾。

  • 常用:
    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRES_NEW
    PROPAGATION_NESTED

4. Spring 事務分類

  • Spring 事務管理
    分層開發:事務處在Service層。

  • Spring 事務管理分成兩類

    • 編程式事務管理 : 手動編寫代碼完成事務管理 (xml配置 + transactionTemplate.execute())

    • 聲明式事務管理 : 不需要手動編寫代碼和配置 (★ @Transactional 註解)

4.1 編程式事務

  • TransactionTemplate

在 doIntransaction 裏處理邏輯。如果出異常了,就執行 isRollbackOnly 方法進行回滾。

@Autowired
TransactionTemplate transactionTemplate;

transactionTemplate.execute((TransactionStatus transactionStatus) -> {
    try {
        //...
    } catch (Exception e) {
        transactionStatus.isRollbackOnly();
        throw e;
    }
    return null;
});
  • TransactionManager

手動 commit,異常就 rollback

TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
    userRepository.save(user);
    transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    e.printStackTrace();
}

4.2 聲明式事務

Spring 事務的本質是數據庫對事務的支持。

支持方式:

  • @EnableTransactionManagement 註解方式,開啓對事務註解的解析
  • <tx:annotation-driven /> XML配置方式,開啓 spring 對註解事務的支持

5. Spring 事務操作轉賬

5.1 無事務操作

  1. 創建表
CREATE TABLE `account` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(20) NOT NULL,
    `money` double DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `account` VALUES ('1', 'aaa', '1000');
INSERT INTO `account` VALUES ('2', 'bbb', '1000');
  1. 創建項目,導入依賴
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <!-- Spring 事務依賴 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.9</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.32</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
  1. 創建接口和類 Dao & Service
// 1.AccountDao
public interface AccountDao {
    // 加錢
    void increaseMoney(Integer id, Double money);
    // 減錢
    void decreaseMoney(Integer id, Double money);
}

// 2.AccountDaoImpl
// 繼承 JdbcDaoSupport 可以直接調用父類方法(含了1個 jdbcTemplate)
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao  {
    @Override
    public void increaseMoney(Integer id, Double money) {
        getJdbcTemplate().update("update account set money = money+? where id = ? ", money,id);
    }

    @Override
    public void decreaseMoney(Integer id, Double money) {
        getJdbcTemplate().update("update account set money = money-? where id = ? ", money,id);
    }
}
    
// 3.AccountService
public interface AccountService {
    //轉賬方法
    void transfer(Integer from,Integer to,Double money);
}

// 4.AccountServiceImpl
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;
    @Override
    public void transfer(Integer from, Integer to, Double money) {
        // 減錢
        accountDao.decreaseMoney(from, money);
        // int i = 1 / 0;// 如果發生異常數據(錢)會丟失,數據不一致
        // 加錢
        accountDao.increaseMoney(to, money);
    }
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
}
  1. 創建 applicationContext.xml 引入事務 tx 約束
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="http://www.springframework.org/schema/beans" 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-4.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">

        <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="url" value="jdbc:mysql://localhost:3306/數據庫名"></property>
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="username" value="root"></property>
            <property name="password" value="root"></property>
        </bean>

        <bean name="accountDao" class="com.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>

        <bean name="accountService" class="com.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>

    </beans>

5.測試

public class Demo {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = (AccountService)context.getBean("accountService");
        accountService.transfer(1, 2, 100d);
    }
}

5.2 XML 配置事務

無事務操作時,如果在轉賬方法中出現異常後,數據前後會產生不一致,此時,我們需要用 Spring 的事務管理來解決這一問題。

手動編碼的方式完成事務管理,缺點 : 代碼量增加,代碼有侵入性

// 修改AccountServiceImpl
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;
    // TransactionTemplate 事務模板類
    private TransactionTemplate transactionTemplate;

    @Override
    public void transfer(Integer from, Integer to, Double money) {
        // 事務模板調用 execute 來自動開啓、關閉、以及異常時自動回滾操作
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                // 減錢
                accountDao.decreaseMoney(from, money);
                int i = 1 / 0; // 如果發生異常數據(錢)不會丟失,數據保持一致
                // 加錢
                accountDao.increaseMoney(to, money);
            }
        });
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }
}
<!-- applicationContext.xml -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="jdbc:mysql://localhost:3306/mydb" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="username" value="root"></property>
    <property name="password" value="root"></property>
</bean>

<!-- TransactionManager 事務核心管理器,封裝了所有事務操作,依賴於連接池 -->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- TransactionTemplate 事務模板對象,依賴於事務核心管理器 -->
<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager" />
</bean>

<bean name="accountDao" class="com.AccountDaoImpl">
    <property name="dataSource" ref="dataSource" />
</bean>

<bean name="accountService" class="com.AccountServiceImpl">
    <property name="accountDao" ref="accountDao" />
    <!-- 爲 accountService 注入事務模板對象 -->
    <property name="transactionTemplate" ref="transactionTemplate" />
</bean>

此時調用測試類測試,在轉賬出現異常時,Spring 事務會自動進行回滾,讓數據保持一致。

5.3 配置事務通知

修改 applicationContext.xml,配置事務通知是需注意:

企業中配置CRUD方法一般使用 方法名+通配符* 的形式配置通知,此時類中的方法名要和配置的方法名一致。

使用 <tx:advice> <tx:attributes> <tx:method ... /> 配置指定方法的事務通知。

  • 以方法爲單位,指定方法應用什麼事務屬性:
    • isolation:用於指定事務的隔離級別。默認值是 DEFAULT,表示使用數據庫的默認隔離級別。
    • propagation:用於指定事務的傳播行爲。默認值是 REQUIRED,表示一定會有事務,增刪改的選擇。查詢方法可以選擇 SUPPORTS。
    • read-only:用於指定事務是否只讀。只有查詢方法才能設置爲true。默認值是false,表示讀寫。
    • timeout:用於指定事務的超時時間,默認值是-1,表示永不超時。如果指定了數值,以秒爲單位。
    • rollback-for:用於指定一個異常,決定是否回滾,當產生該異常時,事務回滾,產生其他異常時,事務不回滾。沒有默認值。表示任何異常都回滾。
    • no-rollback-for:用於指定一個異常,決定是否不回滾,當產生該異常時,事務不回滾,產生其他異常時事務回滾。沒有默認值。表示任何異常都回滾。
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"></property>
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="username" value="root"></property>
    <property name="password" value="root"></property>
</bean>

<!-- 事務核心管理器,封裝了所有事務操作. 依賴於連接池 -->
<bean name="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置事務通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="save*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
        <tx:method name="persist*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
        <tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
        <tx:method name="modify*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
        <tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
        <tx:method name="remove*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
        <tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
        <tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
        <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    </tx:attributes>
</tx:advice>

<!-- 配置織入 -->
<aop:config>
    <!-- 配置切點表達式 -->
    <aop:pointcut expression="execution(* com.qf.service.*ServiceImpl.*(..))" id="txPc" />
    <!-- 配置切面 : 通知+切點 advice-ref:通知的名稱 pointcut-ref:切點的名稱 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />
</aop:config>

<bean name="accountDao" class="com.AccountDaoImpl">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<bean name="accountService" class="com.AccountServiceImpl">
    <property name="accountDao" ref="accountDao"></property>
</bean>

5.4 註解 配置事務

註解配置(aop)的方式完成事務管理,修改 applicationContext.xml :

<tx:annotation-driven /> 開啓 spring 對註解事務的支持。

<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="jdbc:mysql://localhost:3306/mydb" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="username" value="root" />
    <property name="password" value="root" />
</bean>

<!-- 事務核心管理器,封裝了所有事務操作. 依賴於連接池 -->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>

<!-- 開啓 spring 對註解事務的支持-->
<tx:annotation-driven />

<bean name="accountDao" class="com.AccountDaoImpl">
	<property name="dataSource" ref="dataSource" />
</bean>

<bean name="accountService" class="com.AccountServiceImpl">
	<property name="accountDao" ref="accountDao" />
</bean>

@Transactional 註解

屬性 類型 描述
value String 事務管理器
propagation Propagation 傳播級別
isolation Isolation 隔離級別
readOnly boolean 讀/寫與只讀事務
timeout int 事務超時(秒)
rollbackFor Class 觸發事務回滾的類,默認只對未檢查異常有效
noRollbackFor Class 設置不需要進行回滾的異常類數組
  • @Transactional 特性
    • 類上添加 @Transactional,在每個方法單開一個事務,管理方式相同。
    • @Transactional 註解只在 public 方法上起作用。
    • 默認只對未檢查異常回滾
    • 只讀事務只在事務啓動時應用,否則即使配置也會被忽略。

修改 AccountServiceImpl :

//  註解添加聲明式事務
@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED,readOnly=true)
public class AccountServiceImpl implements AccountService {
    private AccountDao accountDao;

    @Override
    //如果該方法與類名上的配置不同,可以單獨在這個方法上配置註解,單獨生效自己的
    @Transactional(isolation= Isolation.REPEATABLE_READ,propagation= Propagation.REQUIRED,readOnly=false)
    public void transfer(Integer from, Integer to, Double money) {
        // 減錢
        accountDao.decreaseMoney(from, money);
        int i = 1 / 0;// 如果發生異常數據(錢)不會丟失,數據保持一致
        // 加錢
        accountDao.increaseMoney(to, money);
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章