Spring + mybatis + mysql 使用事务的几种姿势 主要记录下spring是如何支持事务的,以及在Spring结合mybatis时,可以怎么简单的实现数据库的事务功能
I. 前提 case1:两张表的的事务支持情况 首先准备两张表,一个user表,一个story表,结构如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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:单表的事务支持 转账,一个用户减钱,另一个用户加钱
1 2 3 4 5 6 7 8 9 10 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @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文件为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?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连接数据源的相关配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <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" />           <property  name ="mapperLocations"  value ="classpath*:mapper/*.xml" />  </bean > <bean  class ="org.mybatis.spring.mapper.MapperScannerConfigurer" >     <property  name ="basePackage"  value ="com.git.hui.demo.mybatis" />  </bean > 
II. 实例演示 通过网上查询,Spring事务管理总共有四种方式,下面逐一进行演示,每种方式是怎么玩的,然后看实际项目中应该如何抉择
1. 硬编码方式 编程式事务管理,既通过TransactionTemplate来实现多个db操作的事务管理
a. 实现 那么,我们的转账case可以如下实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 @Repository public  class  CodeDemo1      @Autowired      private  MoneyDao moneyDao;     @Autowired      private  TransactionTemplate transactionTemplate;          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
1 2 3 4 5 6 7 8 <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. 测试用例 正常演示情况, 演示没有任何异常,不考虑并发的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @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());     } } 
输出如下,两个账号的钱都没有问题
1 2 3 4 5 6 7 ---------before---------- id: 1 money = 10000 id: 2 money = 50000 转账完成! now: 1526130394266 ---------after---------- id: 1 money = 10010 id: 2 money = 49990 
转账过程中出现异常,特别是转账方钱已扣,收款方还没收到钱时,也就是case中的status为1的场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @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()); } 
对此,我们希望把转账方的钱还回去, 输出如下,发现两个的钱都没有变化
1 2 3 4 5 6 7 8 ---------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的锁机制,另外人的转账应该是立马到的(因为收款人账号没有被锁住),且金额不应该有问题
输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 ---------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成功,当然最终的金额也得一致
输出结果如下
1 2 3 4 5 6 7 8 9 10 11 ---------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一样,
1 2 3 4 5 6 start  transaction ;commit ;
2. 基于TransactionProxyFactoryBean方式 接下来的三个就是声明式事务管理,这种用得也比较少,因为需要每个事务管理类,添加一个TransactionProxyFactoryBean
a. 实现 除了将 TransactionTemplate 干掉,并将内部的sql逻辑移除之外,对比前面的,发现基本上没有太多差别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public  class  FactoryBeanDemo2      @Autowired      private  MoneyDao moneyDao;          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配置如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <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  key ="transfor" > PROPAGATION_REQUIRED</prop >                                    </props >      </property >  </bean > 
通过上面的配置,大致可以了解到这个通过TransactionProxyFactoryBean就是创建了一个FactoryBeanDemo2的代理类,这个代理类内部封装好事务相关的逻辑,可以看做是前面编程式的一种简单通用抽象
b. 测试 测试代码与前面基本相同,唯一的区别就是我们使用的应该是上面BeanFactory生成的Bean,而不是直接使用FactoryBeanDemo2
正常演示case:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @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());     } } 
输出
1 2 3 4 5 6 7 ---------before---------- id: 1 money = 10000 id: 2 money = 50000 转账完成! now: 1526132058886 ---------after---------- id: 1 money = 10010 id: 2 money = 49990 
status为1,内部异常的情况下,我们希望钱也不会有问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @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()); } 
输出为
1 2 3 4 5 6 7 ---------before---------- id: 1 money = 10010 id: 2 money = 49990 转账异常!!! ---------after---------- id: 1 money = 10010 id: 2 money = 49990 
status为2 时,分析结果与上面应该相同,输出如下
1 2 3 4 5 6 7 8 9 ---------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时,输出
1 2 3 4 5 6 7 8 9 ---------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的依赖
1 2 3 4 5 <dependency >     <groupId > org.aspectj</groupId >      <artifactId > aspectjweaver</artifactId >      <version > 1.8.7</version >  </dependency > 
a. 实现 java类与第二种完全一致,变动的只有xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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 >                   <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. 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @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的注入方式简单点
正常输出
1 2 3 4 5 6 7 ---------before---------- id: 1 money = 10000 id: 2 money = 50000 转账完成! now: 1526135301273 ---------after---------- id: 1 money = 10010 id: 2 money = 49990 
status=1 出现异常时,输出
1 2 3 4 5 6 7 ---------before---------- id: 1 money = 10010 id: 2 money = 49990 转账异常!!! ---------after---------- id: 1 money = 10010 id: 2 money = 49990 
status=2 转账过程中,又存钱的场景,输出,与前面预期一致
1 2 3 4 5 6 7 8 9 ---------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 的输出,与前面预期一致
1 2 3 4 5 6 7 8 9 ---------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. 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 @Repository public  class  AnnoDemo4      @Autowired      private  MoneyDao moneyDao;          @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中配置,开启事务注解
1 2 3 4 5 6 7 8 <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. 参考 文档 
源码 
基于hexo + github pages搭建的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
3. 声明 尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正
4. 扫描关注