Spring學習之事務的使用姿式一覽

Spring + mybatis + mysql 使用事務的幾種姿式

主要記錄下spring是如何支持事務的,以及在Spring結合mybatis時,能夠怎麼簡單的實現數據庫的事務功能php

原文查看地址:一灰灰Blogjava

I. 前提

case1:兩張表的的事務支持狀況

首先準備兩張表,一個user表,一個story表,結構以下mysql

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶名',
  `pwd` varchar(26) NOT NULL DEFAULT '' COMMENT '密碼',
  `isDeleted` tinyint(1) NOT NULL DEFAULT '0',
  `created` varchar(13) NOT NULL DEFAULT '0',
  `updated` varchar(13) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `story` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `userId` int(20) unsigned NOT NULL DEFAULT '0' COMMENT '做者的userID',
  `name` varchar(20) NOT NULL DEFAULT '' COMMENT '做者名',
  `title` varchar(26) NOT NULL DEFAULT '' COMMENT '密碼',
  `story` text COMMENT '故事內容',
  `isDeleted` tinyint(1) NOT NULL DEFAULT '0',
  `created` varchar(13) NOT NULL DEFAULT '0',
  `updated` varchar(13) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `userId` (`userId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
複製代碼

咱們的事務場景在於用戶修改name時,要求兩張表的name都須要一塊兒修改,不容許出現不一致的狀況git

case2:單表的事務支持

轉帳,一個用戶減錢,另外一個用戶加錢github

CREATE TABLE `money` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶名',
  `money` int(26) NOT NULL DEFAULT '0' COMMENT '錢',
  `isDeleted` tinyint(1) NOT NULL DEFAULT '0',
  `created` varchar(13) NOT NULL DEFAULT '0',
  `updated` varchar(13) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
複製代碼

相比上面那個case,這個更加簡單了,下面的實例則主要根據這個進行說明,至於case1,則留待擴展裏面進行spring

首先是實現對應的dao和entitysql

@Data
public class MoneyEntity implements Serializable {

    private static final long serialVersionUID = -7074788842783160025L;

    private int id;

    private String name;

    private int money;

    private int isDeleted;

    private int created;

    private int updated;
}


public interface MoneyDao {
    MoneyEntity queryMoney(@Param("id") int userId);

    // 加錢,負數時表示減錢
    int incrementMoney(@Param("id") int userId, @Param("addMoney") int addMoney);
}
複製代碼

對應的mapper文件爲數據庫

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.git.hui.demo.mybatis.mapper.MoneyDao">

    <sql id="moneyEntity">
        id, `name`, `money`, `isDeleted`, `created`, `updated`
    </sql>


    <select id="queryMoney" resultType="com.git.hui.demo.mybatis.entity.MoneyEntity">
        select
        <include refid="moneyEntity"/>
        from money
        where id=#{id}

    </select>

    <update id="incrementMoney">
        update money
        set money=money + #{addMoney}
        where id=#{id}
    </update>
</mapper>
複製代碼

對應的mybatis鏈接數據源的相關配置express

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath*:jdbc.properties</value>
    </property>
</bean>


<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="driverClassName" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>

    <property name="filters" value="stat"/>

    <property name="maxActive" value="20"/>
    <property name="initialSize" value="1"/>
    <property name="maxWait" value="60000"/>
    <property name="minIdle" value="1"/>

    <property name="timeBetweenEvictionRunsMillis" value="60000"/>
    <property name="minEvictableIdleTimeMillis" value="300000"/>

    <property name="validationQuery" value="SELECT 'x'"/>
    <property name="testWhileIdle" value="true"/>
    <property name="testOnBorrow" value="false"/>
    <property name="testOnReturn" value="false"/>

    <property name="poolPreparedStatements" value="true"/>
    <property name="maxPoolPreparedStatementPerConnectionSize" value="50"/>
</bean>


<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!-- 指定mapper文件 -->
    <property name="mapperLocations" value="classpath*:mapper/*.xml"/>
</bean>


<!-- 指定掃描dao -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.git.hui.demo.mybatis"/>
</bean>
複製代碼

II. 實例演示

經過網上查詢,Spring事務管理總共有四種方式,下面逐一進行演示,每種方式是怎麼玩的,而後看實際項目中應該如何抉擇編程

1. 硬編碼方式

編程式事務管理,既經過TransactionTemplate來實現多個db操做的事務管理

a. 實現

那麼,咱們的轉帳case能夠以下實現

@Repository
public class CodeDemo1 {

    @Autowired
    private MoneyDao moneyDao;


    @Autowired
    private TransactionTemplate transactionTemplate;


    /** * 轉帳 * * @param inUserId * @param outUserId * @param payMoney * @param status 0 表示正常轉帳, 1 表示內部拋出一個異常, 2 表示新開一個線程,修改inUserId的錢 +200, 3 表示新開一個線程,修改outUserId的錢 + 200 */
    public void transfor(final int inUserId, final int outUserId, final int payMoney, final int status) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                MoneyEntity entity = moneyDao.queryMoney(outUserId);
                if (entity.getMoney() > payMoney) { // 能夠轉帳

                    // 先減錢
                    moneyDao.incrementMoney(outUserId, -payMoney);

                    
                    testCase(inUserId, outUserId, status);

                    // 再加錢
                    moneyDao.incrementMoney(inUserId, payMoney);
                    System.out.println("轉帳完成! now: " + System.currentTimeMillis());
                }
            }
        });
    }
    
    
    // 下面都是測試用例相關
    private void testCase(final int inUserId, final int outUserId, final int status) {
        if (status == 1) {
            throw new IllegalArgumentException("轉帳異常!!!");
        } else if(status == 2) {
            addMoney(inUserId);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else if (status == 3) {
            addMoney(outUserId);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public void addMoney(final int userId) {
        System.out.printf("內部加錢: " + System.currentTimeMillis());
        new Thread(new Runnable() {
            public void run() {
                moneyDao.incrementMoney(userId, 200);
                System.out.println(" sub modify success! now: " + System.currentTimeMillis());
            }
        }).start();
    }
}
複製代碼

主要看上面的transfor方法,內部經過 transactionTemplate 來實現事務的封裝,內部有三個db操做,一個查詢,兩個更新,具體分析後面說明

上面的代碼比較簡單了,惟一須要關注的就是transactionTemplate這個bean如何定義的,xml文件中與前面重複的就不貼了,直接貼上關鍵代碼, 一個是根據DataSource建立的TransactionManager,一個則是根據TransactionManager建立的TransactionTemplate

<!--編程式事務-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
</bean>
複製代碼

b. 測試用例

正常演示狀況, 演示沒有任何異常,不考慮併發的狀況

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath*:spring/service.xml", "classpath*:test-datasource1.xml"})
public class CodeDemo1Test {
    @Autowired
    private CodeDemo1 codeDemo1;

    @Autowired
    private MoneyDao moneyDao;

    @Test
    public void testTransfor() {

        System.out.println("---------before----------");
        System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney());
        System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());


        codeDemo1.transfor(1, 2, 10, 0);

        System.out.println("---------after----------");
        System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney());
        System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());
    }
}
複製代碼

輸出以下,兩個帳號的錢都沒有問題

---------before----------
id: 1 money = 10000
id: 2 money = 50000
轉帳完成! now: 1526130394266
---------after----------
id: 1 money = 10010
id: 2 money = 49990
複製代碼

轉帳過程當中出現異常,特別是轉帳方錢已扣,收款方還沒收到錢時,也就是case中的status爲1的場景

// 內部拋異常的狀況
@Test
public void testTransforException() {

    System.out.println("---------before----------");
    System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney());
    System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());


    try {
        codeDemo1.transfor(1, 2, 10, 1);
    } catch (Exception e) {
        e.printStackTrace();
    }

    System.out.println("---------after----------");
    System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney());
    System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());
}
複製代碼

對此,咱們但願把轉帳方的錢還回去, 輸出以下,發現兩個的錢都沒有變化

---------before----------
id: 1 money = 10010
id: 2 money = 49990
---------after----------
id: 1 money = 10010
java.lang.IllegalArgumentException: 轉帳異常!!!
	... // 省略異常信息
id: 2 money = 49990
複製代碼

當status爲2,表示在轉帳人錢已扣,收款人錢沒收到之間,又有人給收款人轉了200,此時根據mysql的鎖機制,另外人的轉帳應該是立馬到的(由於收款人帳號沒有被鎖住),且金額不該該有問題

輸出結果以下:

---------before----------
id: 1 money = 10010
id: 2 money = 49990
## 右邊是註釋: 轉帳過程當中,另外存錢立馬到帳,沒有被鎖住
內部加錢: 1526130827480
sub modify success! now: 1526130827500 
## 存錢結束
轉帳完成! now: 1526130830488
---------after----------
id: 1 money = 10220
id: 2 money = 49980
複製代碼

當status爲3, 表示在轉帳人錢已扣,收款人錢沒收到之間,又有人給轉帳人轉了200,這時由於轉帳人的記錄以及被加了寫鎖,所以只能等待轉帳的事務提交以後,纔有可能+200成功,固然最終的金額也得一致

輸出結果以下

---------before----------
id: 1 money = 10220
id: 2 money = 49980
## 右邊是註釋:內部存錢了,但沒有立刻成功
## 直到轉帳完成後,才立馬存成功,注意兩個時間戳
內部加錢: 1526131101046
轉帳完成! now: 1526131104051
sub modify success! now: 1526131104053
---------after----------
id: 1 money = 10230
id: 2 money = 50170
複製代碼

c. 小結

至此,編程式事務已經實例演示ok,從上面的過程,給人的感受就和直接寫事務相關的sql同樣,

start transaction;

-- 這中間就是 TransactionTemplate#execute 方法內部的邏輯
-- 也就是須要事務管理的一組sql

commit;
複製代碼

2. 基於TransactionProxyFactoryBean方式

接下來的三個就是聲明式事務管理,這種用得也比較少,由於須要每一個事務管理類,添加一個TransactionProxyFactoryBean

a. 實現

除了將 TransactionTemplate 幹掉,並將內部的sql邏輯移除以外,對比前面的,發現基本上沒有太多差異

public class FactoryBeanDemo2 {

    @Autowired
    private MoneyDao moneyDao;

    /** * 轉帳 * * @param inUserId * @param outUserId * @param payMoney * @param status 0 表示正常轉帳, 1 表示內部拋出一個異常, 2 表示新開一個線程,修改inUserId的錢 +200, 3 表示新開一個線程,修改outUserId的錢 + 200 */
    public void transfor(final int inUserId, final int outUserId, final int payMoney, final int status) {

        MoneyEntity entity = moneyDao.queryMoney(outUserId);
        if (entity.getMoney() > payMoney) { // 能夠轉帳

            // 先減錢
            moneyDao.incrementMoney(outUserId, -payMoney);


            testCase(inUserId, outUserId, status);


            // 再加錢
            moneyDao.incrementMoney(inUserId, payMoney);
            System.out.println("轉帳完成! now: " + System.currentTimeMillis());
        }


    }


    private void testCase(final int inUserId, final int outUserId, final int status) {
        if (status == 1) {
            throw new IllegalArgumentException("轉帳異常!!!");
        } else if (status == 2) {
            addMoney(inUserId);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else if (status == 3) {
            addMoney(outUserId);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public void addMoney(final int userId) {
        System.out.println("內部加錢: " + System.currentTimeMillis());
        new Thread(new Runnable() {
            public void run() {
                moneyDao.incrementMoney(userId, 200);
                System.out.println("sub modify success! now: " + System.currentTimeMillis());
            }
        }).start();
    }
}
複製代碼

重點來了,主要是須要配置一個 TransactionProxyBeanFactory,咱們知道BeanFactory就是咱們本身來建立Bean的一種手段,相關的xml配置以下

<!--編程式事務-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="factoryBeanDemo2" class="com.git.hui.demo.mybatis.repository.transaction.FactoryBeanDemo2"/>

<!-- 配置業務層的代理 -->
<bean id="factoryBeanDemoProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <!-- 配置目標對象 -->
    <property name="target" ref="factoryBeanDemo2" />
    <!-- 注入事務管理器 -->
    <property name="transactionManager" ref="transactionManager"/>
    <!-- 注入事務的屬性 -->
    <property name="transactionAttributes">
        <props>
            <!-- prop的格式: * PROPAGATION :事務的傳播行爲 * ISOTATION :事務的隔離級別 * readOnly :只讀 * -EXCEPTION :發生哪些異常回滾事務 * +EXCEPTION :發生哪些異常不回滾事務 -->
             <!-- 這個key對應的就是目標類中的方法-->
            <prop key="transfor">PROPAGATION_REQUIRED</prop>
            <!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> -->
            <!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> -->
        </props>
    </property>
</bean>
複製代碼

經過上面的配置,大體能夠了解到這個經過TransactionProxyFactoryBean就是建立了一個FactoryBeanDemo2的代理類,這個代理類內部封裝好事務相關的邏輯,能夠看作是前面編程式的一種簡單通用抽象

b. 測試

測試代碼與前面基本相同,惟一的區別就是咱們使用的應該是上面BeanFactory生成的Bean,而不是直接使用FactoryBeanDemo2

正常演示case:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath*:spring/service.xml", "classpath*:test-datasource2.xml"})
public class FactoryBeanDemo1Test {

    @Resource(name = "factoryBeanDemoProxy")
    private FactoryBeanDemo2 factoryBeanDemo2;

    @Autowired
    private MoneyDao moneyDao;


    @Test
    public void testTransfor() {

        System.out.println("---------before----------");
        System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney());
        System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());


        factoryBeanDemo2.transfor(1, 2, 10, 0);

        System.out.println("---------after----------");
        System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney());
        System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());
    }
}
複製代碼

輸出

---------before----------
id: 1 money = 10000
id: 2 money = 50000
轉帳完成! now: 1526132058886
---------after----------
id: 1 money = 10010
id: 2 money = 49990
複製代碼

status爲1,內部異常的狀況下,咱們但願錢也不會有問題

@Test
public void testTransforException() {

    System.out.println("---------before----------");
    System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney());
    System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());


    try {
        factoryBeanDemo2.transfor(1, 2, 10, 1);
    } catch (Exception e) {
        System.out.println(e.getMessage());;
    }

    System.out.println("---------after----------");
    System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney());
    System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());
}
複製代碼

輸出爲

---------before----------
id: 1 money = 10010
id: 2 money = 49990
轉帳異常!!!
---------after----------
id: 1 money = 10010
id: 2 money = 49990
複製代碼

status爲2 時,分析結果與上面應該相同,輸出以下

---------before----------
id: 1 money = 10010
id: 2 money = 49950
內部加錢: 1526133325376
sub modify success! now: 1526133325387
轉帳完成! now: 1526133328381
---------after----------
id: 1 money = 10220
id: 2 money = 49940
複製代碼

status爲3時,輸出

---------before----------
id: 1 money = 10220
id: 2 money = 49940
內部加錢: 1526133373466
轉帳完成! now: 1526133376476
sub modify success! now: 1526133376480
---------after----------
id: 1 money = 10230
id: 2 money = 50130
複製代碼

c. 小結

TransactionProxyFactoryBean 的思路就是利用代理模式來實現事務管理,生成一個代理類,攔截目標方法,將一組sql的操做封裝到事務中進行;相比較於硬編碼,無侵入,並且支持靈活的配置方式

缺點也顯而易見,每一個都要進行配置,比較繁瑣

3. xml使用方式

Spring有兩大特色,IoC和AOP,對於事務這種狀況而言,咱們可不可使用AOP來作呢?

對於須要開啓事務的方法,攔截掉,執行前開始事務,執行完畢以後提交事務,出現異常時回滾

這樣一看,感受仍是蠻有但願的,而下面兩種姿式正是這麼玩的,所以須要加上aspect的依賴

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.7</version>
</dependency>
複製代碼

a. 實現

java類與第二種徹底一致,變更的只有xml

<!-- 首先添加命名空間 -->
xmlns:tx="http://www.springframework.org/schema/tx" 
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="...
  http://www.springframework.org/schema/tx
 http://www.springframework.org/schema/tx/spring-tx.xsd"



<!--對應的事務通知和切面配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- propagation :事務傳播行爲 isolation :事務的隔離級別 read-only :只讀 rollback-for:發生哪些異常回滾 no-rollback-for :發生哪些異常不回滾 timeout :過時信息 -->
        <tx:method name="transfor" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>



<!-- 配置切面 -->
<aop:config>
    <!-- 配置切入點 -->
    <aop:pointcut expression="execution(* com.git.hui.demo.mybatis.repository.transaction.XmlDemo3.*(..))" id="pointcut1"/>
    <!-- 配置切面 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
複製代碼

觀察上面的配置,再想一想第二種方式,思路都差很少了,可是這種方式明顯更加通用,經過切面和切點,能夠減小大量的配置

b. 測試

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath*:spring/service.xml", "classpath*:test-datasource3.xml"})
public class XmlBeanTest {
    @Autowired
    private XmlDemo3 xmlDemo;

    @Autowired
    private MoneyDao moneyDao;


    @Test
    public void testTransfor() {

        System.out.println("---------before----------");
        System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney());
        System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());


        xmlDemo.transfor(1, 2, 10, 0);

        System.out.println("---------after----------");
        System.out.println("id: 1 money = " + moneyDao.queryMoney(1).getMoney());
        System.out.println("id: 2 money = " + moneyDao.queryMoney(2).getMoney());
    }
}
複製代碼

這個測試起來,和通常的寫法就沒啥兩樣了,比第二種的FactoryBean的注入方式簡單點

正常輸出

---------before----------
id: 1 money = 10000
id: 2 money = 50000
轉帳完成! now: 1526135301273
---------after----------
id: 1 money = 10010
id: 2 money = 49990
複製代碼

status=1 出現異常時,輸出

---------before----------
id: 1 money = 10010
id: 2 money = 49990
轉帳異常!!!
---------after----------
id: 1 money = 10010
id: 2 money = 49990
複製代碼

status=2 轉帳過程當中,又存錢的場景,輸出,與前面預期一致

---------before----------
id: 1 money = 10010
id: 2 money = 49990
內部加錢: 1526135438403
sub modify success! now: 1526135438421
轉帳完成! now: 1526135441410
---------after----------
id: 1 money = 10220
id: 2 money = 49980
複製代碼

status=3 的輸出,與前面預期一致

---------before----------
id: 1 money = 10220
id: 2 money = 49980
內部加錢: 1526135464341
轉帳完成! now: 1526135467349
sub modify success! now: 1526135467352
---------after----------
id: 1 money = 10230
id: 2 money = 50170
複製代碼

4. 註解方式

這個就是消滅xml,用註解來作的方式,就是將前面xml中的配置用 @Transactional註解替換

a. 實現

@Repository
public class AnnoDemo4 {

    @Autowired
    private MoneyDao moneyDao;


    /** * 轉帳 * * @param inUserId * @param outUserId * @param payMoney * @param status 0 表示正常轉帳, 1 表示內部拋出一個異常, 2 表示新開一個線程,修改inUserId的錢 +200, 3 表示新開一個線程,修改outUserId的錢 + 200 * * * Transactional註解中的的屬性 propagation :事務的傳播行爲 isolation :事務的隔離級別 readOnly :只讀 * rollbackFor :發生哪些異常回滾 noRollbackFor :發生哪些異常不回滾 * rollbackForClassName 根據異常類名回滾 */
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
    public void transfor(final int inUserId, final int outUserId, final int payMoney, final int status) {

        MoneyEntity entity = moneyDao.queryMoney(outUserId);
        if (entity.getMoney() > payMoney) { // 能夠轉帳

            // 先減錢
            moneyDao.incrementMoney(outUserId, -payMoney);


            testCase(inUserId, outUserId, status);


            // 再加錢
            moneyDao.incrementMoney(inUserId, payMoney);
            System.out.println("轉帳完成! now: " + System.currentTimeMillis());
        }


    }


    private void testCase(final int inUserId, final int outUserId, final int status) {
        if (status == 1) {
            throw new IllegalArgumentException("轉帳異常!!!");
        } else if (status == 2) {
            addMoney(inUserId);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else if (status == 3) {
            addMoney(outUserId);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    private void addMoney(final int userId) {
        System.out.println("內部加錢: " + System.currentTimeMillis());
        new Thread(new Runnable() {
            public void run() {
                moneyDao.incrementMoney(userId, 200);
                System.out.println("sub modify success! now: " + System.currentTimeMillis());
            }
        }).start();
    }

}
複製代碼

所以須要在xml中配置,開啓事務註解

<!--編程式事務-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>


<tx:annotation-driven transaction-manager="transactionManager"/>
複製代碼

這樣一看,就更加清晰了,實際項目中,xml和註解方式也是用得最多的場景了

b. 測試case

和第三種測試case徹底相同, 輸出結果也同樣,直接省略

III. 小結

上面說了Spring中四種使用事務的姿式,其中硬編碼方式多是最好理解的,就至關於將咱們寫sql中,使用事務的方式直接翻譯成對應的java代碼了;而FactoryBean方式至關於特殊狀況特殊對待,爲每一個事務來一個代理類來加強事務功能;後面的兩個則原理差很少都是利用事務通知(AOP)來實現,定義切點及相關信息

編程式:

  • 注入 TransactionTemplate
  • 將利用事務的邏輯封裝到 transactionTemplate#execute方法內

代理BeanFactory:

  • 利用 TransactionProxyFactoryBean 爲事務相關類生成代理
  • 使用方經過FactoryBean獲取代理類,做爲使用的Bean

xml配置:

  • 利用 tx標籤 + aop方式來實現
  • <tx:advice> 標籤訂義事務通知,內部可有較多的配置信息
  • <aop:config> 配置切點,切面

註解方式:

  • 在開啓事務的方法or類上添加 @Transactional 註解便可
  • 開啓事務註解 <tx:annotation-driven transaction-manager="transactionManager"/>

IV. 其餘

1. 參考

文檔

源碼

2. 我的博客: 一灰灰Blog

基於hexo + github pages搭建的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

3. 聲明

盡信書則不如,已上內容,純屬一家之言,因本人能力通常,見識有限,如發現bug或者有更好的建議,隨時歡迎批評指正

4. 掃描關注

QrCode
相關文章
相關標籤/搜索