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
相关文章
相关标签/搜索