Spring + mybatis + mysql使用事物的幾種方法總結(jié)
前言
本文主要記錄下spring是如何支持事物的,以及在Spring結(jié)合mybatis時(shí),可以怎么簡(jiǎn)單的實(shí)現(xiàn)數(shù)據(jù)庫(kù)的事物功能,下面話不多說了,來一起看看詳細(xì)的介紹吧。
I. 前提
case1:兩張表的的事物支持情況
首先準(zhǔn)備兩張表,一個(gè)user表,一個(gè)story表,結(jié)構(gòu)如下
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 '故事內(nèi)容', `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;
我們的事物場(chǎng)景在于用戶修改name時(shí),要求兩張表的name都需要一起修改,不允許出現(xiàn)不一致的情況
case2:?jiǎn)伪淼氖挛镏С?/strong>
轉(zhuǎn)賬,一個(gè)用戶減錢,另一個(gè)用戶加錢
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;
相比上面那個(gè)case,這個(gè)更加簡(jiǎn)單了,下面的實(shí)例則主要根據(jù)這個(gè)進(jìn)行說明,至于case1,則留待擴(kuò)展里面進(jìn)行
首先是實(shí)現(xiàn)對(duì)應(yīng)的dao和entity
@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); // 加錢,負(fù)數(shù)時(shí)表示減錢 int incrementMoney(@Param("id") int userId, @Param("addMoney") int addMoney); }
對(duì)應(yīng)的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>
對(duì)應(yīng)的mybatis連接數(shù)據(jù)源的相關(guān)配置
<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. 實(shí)例演示
通過網(wǎng)上查詢,Spring事物管理總共有四種方式,下面逐一進(jìn)行演示,每種方式是怎么玩的,然后看實(shí)際項(xiàng)目中應(yīng)該如何抉擇
1. 硬編碼方式
編程式事物管理,既通過TransactionTemplate來實(shí)現(xiàn)多個(gè)db操作的事物管理
a. 實(shí)現(xiàn)
那么,我們的轉(zhuǎn)賬case可以如下實(shí)現(xiàn)
@Repository public class CodeDemo1 { @Autowired private MoneyDao moneyDao; @Autowired private TransactionTemplate transactionTemplate; /** * 轉(zhuǎn)賬 * * @param inUserId * @param outUserId * @param payMoney * @param status 0 表示正常轉(zhuǎn)賬, 1 表示內(nèi)部拋出一個(gè)異常, 2 表示新開一個(gè)線程,修改inUserId的錢 +200, 3 表示新開一個(gè)線程,修改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) { // 可以轉(zhuǎn)賬 // 先減錢 moneyDao.incrementMoney(outUserId, -payMoney); testCase(inUserId, outUserId, status); // 再加錢 moneyDao.incrementMoney(inUserId, payMoney); System.out.println("轉(zhuǎn)賬完成! now: " + System.currentTimeMillis()); } } }); } // 下面都是測(cè)試用例相關(guān) private void testCase(final int inUserId, final int outUserId, final int status) { if (status == 1) { throw new IllegalArgumentException("轉(zhuǎn)賬異常!!!"); } 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("內(nèi)部加錢: " + System.currentTimeMillis()); new Thread(new Runnable() { public void run() { moneyDao.incrementMoney(userId, 200); System.out.println(" sub modify success! now: " + System.currentTimeMillis()); } }).start(); } }
主要看上面的transfor方法,內(nèi)部通過 transactionTemplate 來實(shí)現(xiàn)事物的封裝,內(nèi)部有三個(gè)db操作,一個(gè)查詢,兩個(gè)更新,具體分析后面說明
上面的代碼比較簡(jiǎn)單了,唯一需要關(guān)注的就是transactionTemplate這個(gè)bean如何定義的,xml文件中與前面重復(fù)的就不貼了,直接貼上關(guān)鍵代碼, 一個(gè)是根據(jù)DataSource創(chuàng)建的TransactionManager,一個(gè)則是根據(jù)TransactionManager創(chuàng)建的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. 測(cè)試用例
正常演示情況, 演示沒有任何異常,不考慮并發(fā)的情況
@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()); } }
輸出如下,兩個(gè)賬號(hào)的錢都沒有問題
---------before----------
id: 1 money = 10000
id: 2 money = 50000
轉(zhuǎn)賬完成! now: 1526130394266
---------after----------
id: 1 money = 10010
id: 2 money = 49990
轉(zhuǎn)賬過程中出現(xiàn)異常,特別是轉(zhuǎn)賬方錢已扣,收款方還沒收到錢時(shí),也就是case中的status為1的場(chǎng)景
// 內(nèi)部拋異常的情況 @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()); }
對(duì)此,我們希望把轉(zhuǎn)賬方的錢還回去, 輸出如下,發(fā)現(xiàn)兩個(gè)的錢都沒有變化
---------before----------
id: 1 money = 10010
id: 2 money = 49990
---------after----------
id: 1 money = 10010
java.lang.IllegalArgumentException: 轉(zhuǎn)賬異常!!!
... // 省略異常信息
id: 2 money = 49990
當(dāng)status為2,表示在轉(zhuǎn)賬人錢已扣,收款人錢沒收到之間,又有人給收款人轉(zhuǎn)了200,此時(shí)根據(jù)mysql的鎖機(jī)制,另外人的轉(zhuǎn)賬應(yīng)該是立馬到的(因?yàn)槭湛钊速~號(hào)沒有被鎖?。?,且金額不應(yīng)該有問題
輸出結(jié)果如下:
---------before----------
id: 1 money = 10010
id: 2 money = 49990
## 右邊是注釋: 轉(zhuǎn)賬過程中,另外存錢立馬到賬,沒有被鎖住
內(nèi)部加錢: 1526130827480
sub modify success! now: 1526130827500
## 存錢結(jié)束
轉(zhuǎn)賬完成! now: 1526130830488
---------after----------
id: 1 money = 10220
id: 2 money = 49980
當(dāng)status為3, 表示在轉(zhuǎn)賬人錢已扣,收款人錢沒收到之間,又有人給轉(zhuǎn)賬人轉(zhuǎn)了200,這時(shí)因?yàn)檗D(zhuǎn)賬人的記錄以及被加了寫鎖,因此只能等待轉(zhuǎn)賬的事物提交之后,才有可能+200成功,當(dāng)然最終的金額也得一致
輸出結(jié)果如下
---------before----------
id: 1 money = 10220
id: 2 money = 49980
## 右邊是注釋:內(nèi)部存錢了,但沒有馬上成功
## 直到轉(zhuǎn)賬完成后,才立馬存成功,注意兩個(gè)時(shí)間戳
內(nèi)部加錢: 1526131101046
轉(zhuǎn)賬完成! now: 1526131104051
sub modify success! now: 1526131104053
---------after----------
id: 1 money = 10230
id: 2 money = 50170
c. 小結(jié)
至此,編程式事物已經(jīng)實(shí)例演示ok,從上面的過程,給人的感覺就和直接寫事物相關(guān)的sql一樣,
start transaction;
-- 這中間就是 TransactionTemplate#execute 方法內(nèi)部的邏輯
-- 也就是需要事物管理的一組sqlcommit;
2. 基于TransactionProxyFactoryBean方式
接下來的三個(gè)就是聲明式事物管理,這種用得也比較少,因?yàn)樾枰總€(gè)事物管理類,添加一個(gè)TransactionProxyFactoryBean
a. 實(shí)現(xiàn)
除了將 TransactionTemplate 干掉,并將內(nèi)部的sql邏輯移除之外,對(duì)比前面的,發(fā)現(xiàn)基本上沒有太多差別
public class FactoryBeanDemo2 { @Autowired private MoneyDao moneyDao; /** * 轉(zhuǎn)賬 * * @param inUserId * @param outUserId * @param payMoney * @param status 0 表示正常轉(zhuǎn)賬, 1 表示內(nèi)部拋出一個(gè)異常, 2 表示新開一個(gè)線程,修改inUserId的錢 +200, 3 表示新開一個(gè)線程,修改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) { // 可以轉(zhuǎn)賬 // 先減錢 moneyDao.incrementMoney(outUserId, -payMoney); testCase(inUserId, outUserId, status); // 再加錢 moneyDao.incrementMoney(inUserId, payMoney); System.out.println("轉(zhuǎn)賬完成! now: " + System.currentTimeMillis()); } } private void testCase(final int inUserId, final int outUserId, final int status) { if (status == 1) { throw new IllegalArgumentException("轉(zhuǎn)賬異常!!!"); } 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("內(nèi)部加錢: " + System.currentTimeMillis()); new Thread(new Runnable() { public void run() { moneyDao.incrementMoney(userId, 200); System.out.println("sub modify success! now: " + System.currentTimeMillis()); } }).start(); } }
重點(diǎn)來了,主要是需要配置一個(gè) TransactionProxyBeanFactory,我們知道BeanFactory就是我們自己來創(chuàng)建Bean的一種手段,相關(guān)的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"/> <!-- 配置業(yè)務(wù)層的代理 --> <bean id="factoryBeanDemoProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!-- 配置目標(biāo)對(duì)象 --> <property name="target" ref="factoryBeanDemo2" /> <!-- 注入事務(wù)管理器 --> <property name="transactionManager" ref="transactionManager"/> <!-- 注入事務(wù)的屬性 --> <property name="transactionAttributes"> <props> <!-- prop的格式: * PROPAGATION :事務(wù)的傳播行為 * ISOTATION :事務(wù)的隔離級(jí)別 * readOnly :只讀 * -EXCEPTION :發(fā)生哪些異?;貪L事務(wù) * +EXCEPTION :發(fā)生哪些異常不回滾事務(wù) --> <!-- 這個(gè)key對(duì)應(yīng)的就是目標(biāo)類中的方法--> <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>
通過上面的配置,大致可以了解到這個(gè)通過TransactionProxyFactoryBean就是創(chuàng)建了一個(gè)FactoryBeanDemo2的代理類,這個(gè)代理類內(nèi)部封裝好事物相關(guān)的邏輯,可以看做是前面編程式的一種簡(jiǎn)單通用抽象
b. 測(cè)試
測(cè)試代碼與前面基本相同,唯一的區(qū)別就是我們使用的應(yīng)該是上面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
轉(zhuǎn)賬完成! now: 1526132058886
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status為1,內(nèi)部異常的情況下,我們希望錢也不會(huì)有問題
@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
轉(zhuǎn)賬異常!!!
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status為2 時(shí),分析結(jié)果與上面應(yīng)該相同,輸出如下
---------before----------
id: 1 money = 10010
id: 2 money = 49950
內(nèi)部加錢: 1526133325376
sub modify success! now: 1526133325387
轉(zhuǎn)賬完成! now: 1526133328381
---------after----------
id: 1 money = 10220
id: 2 money = 49940
status為3時(shí),輸出
---------before----------
id: 1 money = 10220
id: 2 money = 49940
內(nèi)部加錢: 1526133373466
轉(zhuǎn)賬完成! now: 1526133376476
sub modify success! now: 1526133376480
---------after----------
id: 1 money = 10230
id: 2 money = 50130
c. 小結(jié)
TransactionProxyFactoryBean 的思路就是利用代理模式來實(shí)現(xiàn)事物管理,生成一個(gè)代理類,攔截目標(biāo)方法,將一組sql的操作封裝到事物中進(jìn)行;相比較于硬編碼,無(wú)侵入,而且支持靈活的配置方式
缺點(diǎn)也顯而易見,每個(gè)都要進(jìn)行配置,比較繁瑣
3. xml使用方式
Spring有兩大特點(diǎn),IoC和AOP,對(duì)于事物這種情況而言,我們可不可以使用AOP來做呢?
對(duì)于需要開啟事物的方法,攔截掉,執(zhí)行前開始事物,執(zhí)行完畢之后提交事物,出現(xiàn)異常時(shí)回滾
這樣一看,感覺還是蠻有希望的,而下面兩種姿勢(shì)正是這么玩的,因此需要加上aspect的依賴
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>
a. 實(shí)現(xiàn)
java類與第二種完全一致,變動(dòng)的只有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" <!--對(duì)應(yīng)的事物通知和切面配置--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- propagation :事務(wù)傳播行為 isolation :事務(wù)的隔離級(jí)別 read-only :只讀 rollback-for:發(fā)生哪些異?;貪L no-rollback-for :發(fā)生哪些異常不回滾 timeout :過期信息 --> <tx:method name="transfor" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 配置切面 --> <aop:config> <!-- 配置切入點(diǎn) --> <aop:pointcut expression="execution(* com.git.hui.demo.mybatis.repository.transaction.XmlDemo3.*(..))" id="pointcut1"/> <!-- 配置切面 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/> </aop:config>
觀察上面的配置,再想想第二種方式,思路都差不多了,但是這種方式明顯更加通用,通過切面和切點(diǎn),可以減少大量的配置
b. 測(cè)試
@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()); } }
這個(gè)測(cè)試起來,和一般的寫法就沒啥兩樣了,比第二種的FactoryBean的注入方式簡(jiǎn)單點(diǎn)
正常輸出
---------before----------
id: 1 money = 10000
id: 2 money = 50000
轉(zhuǎn)賬完成! now: 1526135301273
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status=1 出現(xiàn)異常時(shí),輸出
---------before----------
id: 1 money = 10010
id: 2 money = 49990
轉(zhuǎn)賬異常!!!
---------after----------
id: 1 money = 10010
id: 2 money = 49990
status=2 轉(zhuǎn)賬過程中,又存錢的場(chǎng)景,輸出,與前面預(yù)期一致
---------before----------
id: 1 money = 10010
id: 2 money = 49990
內(nèi)部加錢: 1526135438403
sub modify success! now: 1526135438421
轉(zhuǎn)賬完成! now: 1526135441410
---------after----------
id: 1 money = 10220
id: 2 money = 49980
status=3 的輸出,與前面預(yù)期一致
---------before----------
id: 1 money = 10220
id: 2 money = 49980
內(nèi)部加錢: 1526135464341
轉(zhuǎn)賬完成! now: 1526135467349
sub modify success! now: 1526135467352
---------after----------
id: 1 money = 10230
id: 2 money = 50170
4. 注解方式
這個(gè)就是消滅xml,用注解來做的方式,就是將前面xml中的配置用 @Transactional注解替換
a. 實(shí)現(xiàn)
@Repository public class AnnoDemo4 { @Autowired private MoneyDao moneyDao; /** * 轉(zhuǎn)賬 * * @param inUserId * @param outUserId * @param payMoney * @param status 0 表示正常轉(zhuǎn)賬, 1 表示內(nèi)部拋出一個(gè)異常, 2 表示新開一個(gè)線程,修改inUserId的錢 +200, 3 表示新開一個(gè)線程,修改outUserId的錢 + 200 * * * Transactional注解中的的屬性 propagation :事務(wù)的傳播行為 isolation :事務(wù)的隔離級(jí)別 readOnly :只讀 * rollbackFor :發(fā)生哪些異?;貪L noRollbackFor :發(fā)生哪些異常不回滾 * rollbackForClassName 根據(jù)異常類名回滾 */ @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) { // 可以轉(zhuǎn)賬 // 先減錢 moneyDao.incrementMoney(outUserId, -payMoney); testCase(inUserId, outUserId, status); // 再加錢 moneyDao.incrementMoney(inUserId, payMoney); System.out.println("轉(zhuǎn)賬完成! now: " + System.currentTimeMillis()); } } private void testCase(final int inUserId, final int outUserId, final int status) { if (status == 1) { throw new IllegalArgumentException("轉(zhuǎn)賬異常!!!"); } 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("內(nèi)部加錢: " + 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"/>
這樣一看,就更加清晰了,實(shí)際項(xiàng)目中,xml和注解方式也是用得最多的場(chǎng)景了
b. 測(cè)試case
和第三種測(cè)試case完全相同, 輸出結(jié)果也一樣,直接省略
III. 小結(jié)
上面說了Spring中四種使用事物的姿勢(shì),其中硬編碼方式可能是最好理解的,就相當(dāng)于將我們寫sql中,使用事物的方式直接翻譯成對(duì)應(yīng)的java代碼了;而FactoryBean方式相當(dāng)于特殊情況特殊對(duì)待,為每個(gè)事物來一個(gè)代理類來增強(qiáng)事物功能;后面的兩個(gè)則原理差不多都是利用事物通知(AOP)來實(shí)現(xiàn),定義切點(diǎn)及相關(guān)信息
編程式:
- 注入 TransactionTemplate
- 將利用事物的邏輯封裝到
transactionTemplate#execute
方法內(nèi)
代理BeanFactory:
- 利用 TransactionProxyFactoryBean 為事物相關(guān)類生成代理
- 使用方通過FactoryBean獲取代理類,作為使用的Bean
xml配置:
- 利用 tx標(biāo)簽 + aop方式來實(shí)現(xiàn)
- <tx:advice> 標(biāo)簽定義事物通知,內(nèi)部可有較多的配置信息
- <aop:config> 配置切點(diǎn),切面
注解方式:
- 在開啟事物的方法or類上添加 @Transactional 注解即可
- 開啟事物注解 <
tx:annotation-driven transaction-manager="transactionManager"/>
IV. 其他
1. 參考
文檔
源碼
- 項(xiàng)目源碼:study-demo (本地下載)
- 主要查看包路徑: 事物demo (本地下載)
- 測(cè)試相關(guān)代碼: 測(cè)試demo (本地下載)
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- 結(jié)合mybatis-plus實(shí)現(xiàn)簡(jiǎn)單不需要寫sql的多表查詢
- MyBatisPlus 自定義sql語(yǔ)句的實(shí)現(xiàn)
- mybatis 實(shí)現(xiàn) SQL 查詢攔截修改詳解
- MyBatis動(dòng)態(tài)Sql之if標(biāo)簽的用法詳解
- mybatis動(dòng)態(tài)sql之Map參數(shù)的講解
- Mybatis模糊查詢和動(dòng)態(tài)sql語(yǔ)句的用法
- MyBatis執(zhí)行動(dòng)態(tài)SQL的方法
- spring boot配置MySQL數(shù)據(jù)庫(kù)連接、Hikari連接池和Mybatis的簡(jiǎn)單配置方法
- 簡(jiǎn)單了解Mybatis如何實(shí)現(xiàn)SQL防注入
相關(guān)文章
Java實(shí)現(xiàn)生成JSON字符串的三種方式分享
這篇文章主要來和大家分享一下Java實(shí)現(xiàn)生成JSON字符串的常見三種方式,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,需要的可以參考一下2023-05-05springboot工程jar包部署到云服務(wù)器的方法
這篇文章主要介紹了springboot工程jar包部署到云服務(wù)器的方法,本文通過實(shí)例介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2018-05-05java并發(fā)編程專題(五)----詳解(JUC)ReentrantLock
這篇文章主要介紹了java(JUC)ReentrantLock的的相關(guān)資料,文中講解非常詳細(xì),實(shí)例代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07java String[]字符串?dāng)?shù)組自動(dòng)排序的簡(jiǎn)單實(shí)現(xiàn)
下面小編就為大家?guī)硪黄猨ava String[]字符串?dāng)?shù)組自動(dòng)排序的簡(jiǎn)單實(shí)現(xiàn)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-09-09springboot項(xiàng)目實(shí)現(xiàn)配置跨域
這篇文章主要介紹了springboot項(xiàng)目實(shí)現(xiàn)配置跨域問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09Spring Boot 整合持久層之JdbcTemplate
持久層是 Java EE 中訪問數(shù)據(jù)庫(kù)的核心操作,Spring Boot 中對(duì)常見的持久層框架都提供了自動(dòng)化配置,例如 JdbcTemplate 、 JPA 等,Mybatis 的自動(dòng)化配置則是 Mybatis 官方提供的2022-08-08