Spring + mybatis + mysql使用事物的几种方法总结

作者:一灰灰Blog 时间:2024-01-22 02:01:25 

目录
  • I. 前提

  • II. 实例演示

    • 1. 硬编码方式

    • 2. 基于TransactionProxyFactoryBean方式

    • 3. xml使用方式

    • 4. 注解方式

  • III. 小结

    • IV. 其他

      • 1. 参考

    • 总结

      前言

      本文主要记录下spring是如何支持事物的,以及在Spring结合mybatis时,可以怎么简单的实现数据库的事物功能,下面话不多说了,来一起看看详细的介绍吧。

      I. 前提

      case1:两张表的的事物支持情况

      首先准备两张表,一个user表,一个story表,结构如下


      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都需要一起修改,不允许出现不一致的情况

      case2:单表的事物支持

      转账,一个用户减钱,另一个用户加钱


      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,则留待扩展里面进行

      首先是实现对应的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);
      // 加钱,负数时表示减钱
      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连接数据源的相关配置


      <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. 参考

      文档

      Spring事务管理的四种方式

      源码

      • 项目源码:study-demo  (本地下载)

      • 主要查看包路径: 事物demo (本地下载)

      • 测试相关代码: 测试demo (本地下载)

      来源:https://liuyueyi.github.io/hexblog/2018/05/12/Spring学习之事物的使用姿势/

      标签:spring,mybatis,事物
      0
      投稿

      猜你喜欢

    • 双屏显示提升前端开发10%工作效率

      2009-03-16 18:22:00
    • Python+OpenCV实现阈值分割的方法详解

      2023-08-13 02:24:00
    • js判断数组key是否存在(不用循环)的简单实例

      2024-04-17 10:40:35
    • tensorflow tf.train.batch之数据批量读取方式

      2023-12-08 01:11:51
    • F.conv2d pytorch卷积计算方式

      2021-06-26 07:38:41
    • Django2.1.3 中间件使用详解

      2023-11-06 19:46:00
    • Python图像处理之gif动态图的解析与合成操作详解

      2022-09-27 18:19:51
    • php写入mysql中文乱码的实例解决方法

      2023-10-10 01:36:49
    • SPSS连接mysql数据库的超详细操作教程

      2024-01-21 13:49:25
    • Python实现打砖块小游戏代码实例

      2021-02-27 00:35:38
    • JS实现div模块的截图并下载功能

      2024-05-03 11:10:39
    • Jupyter notebook中5个有趣的魔法命令分享

      2021-11-28 23:26:30
    • 使用JavaScript实现网页版Pongo设计思路及源代码分享

      2024-04-10 10:48:07
    • python 实现倒计时功能(gui界面)

      2021-03-05 14:19:55
    • golang游戏等资源压缩包创建和操作方法

      2024-04-30 10:00:22
    • python中类变量与成员变量的使用注意点总结

      2022-01-08 03:39:51
    • python使用matplotlib绘制图片时x轴的刻度处理

      2022-05-05 01:38:56
    • SQL 2008的变更数据捕获——跟踪可变部分

      2009-03-20 11:47:00
    • 挑战! 纯Javascript 重现经典网游! <魔力宝贝>

      2008-10-04 10:37:00
    • Python要求O(n)复杂度求无序列表中第K的大元素实例

      2023-07-30 13:18:01
    • asp之家 网络编程 m.aspxhome.com