spring 事务管理之事务传播行为之实践REQUIRED(一)

纸上得来终觉浅,绝知此事要躬行

为了深入了解spring的事务传播行为,只是知道概念还是不行。需要自己亲自动手来实践下才能学到细节,而且网络上的一些结论也有被以讹传讹的。自己做的实验自己也就放心很多,我愿意做多数人不愿意做的事。

我们知道spring给我们提供7中传播属性,加上不使用事务注解的一种情况就是8种。按照A方法中调用B方法的最简模型。进行排列组合就是 8 * 8 = 64 种组合方式

寻找可以感知事务传播的方法

如果需要验证事务的传播行为,就需要通过一定的手段来感知事务传播。我首先想到的是使用 异常回滚的方式,后来发现不可行。因为操作没有回滚其实存在多种情况,有许多不可控因素。如 申明了特定的传播行为导致没有创建事务导致的不回滚、注解使用错误导致不回滚等。。

然后在网上找到了打印当前事务名的方式

System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());

后来发现只要是加了事务注解,它都会有返回值。其实有些传播行为如@Transactional(propagation=Propagation.SUPPORTS)根本没有创建事务,该方法也打印出了值。。。后来就使用mysql自带的查询语句来打印事务id才搞定,如

 List<Map<String, Object>> maps = jdbcTemplate.queryForList("SELECT TRX_ID FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID( );");
        System.out.println(maps);

注意这种方式需要在对数据库发出sql语句之后才能打印出值,所以需要将之放到数据库操作之后;
这种方式经过我使用或发现仍然有问题,如果两个方法返回相同的事务id,那么如何判断该事务是谁创建的?事务是如何传播的?
打印当前事务名的那个方法刚好可以区分~所以将二者结合起来用最好互相弥补缺陷

 //打印事务名
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("SELECT TRX_ID FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID( );");
        System.out.println(maps + TransactionSynchronizationManager.getCurrentTransactionName());

实验准备

分别有UserService 和 CuserService 两个service,他们之内都有实现各自的insert方法。且在UserService 接口里有调用CuserService 的接口。我们通过在方法上加上@Transactional注解并指定特定的propagation属性,设置不同的事务传播行为。观察运行结果,验证结论~

image.png

创建Users和Cuser实体类

package com.springboot.study.demo1.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

/**
 *@description: User 实体类
 *@author: yinkai
 *@create: 2020/2/25 9:21
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Users {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

package com.springboot.study.demo1.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

/**
 *@description: Cuser 实体类
 *@author: yinkai
 *@create: 2020/2/25 9:21
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Cuser {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

创建 CuserService 和UsersService接口

package com.springboot.study.demo1.service;
import com.springboot.study.demo1.entity.Cuser;
/**
 * @author ceshi
 * @Title:
 * @Package
 * @Description:
 * @date 2020/3/217:11
 */
public interface CuserService {

     void InsertCuser(Cuser cuser);
}


package com.springboot.study.demo1.service;
import com.springboot.study.demo1.entity.Cuser;
import com.springboot.study.demo1.entity.User;
import com.springboot.study.demo1.entity.Users;

/**

/**
 *@program: springboot_study
 *@description:
 *@author: yinkai
 *@create: 2020-03-02 15:30
 */
public interface UsersService {
    void InsertUsers(Users users);


}

创建CuserServiceImpl和UsersServiceImpl实现类

  • 分别实现InsertUsers () 和 InsertCuser()方法 ; 一个功能为添加User,一个功能为添加Cuser
  • InsertUsers ()方法内部调用了InsertCuser()方法,制造事务冲突环境
package com.springboot.study.demo1.service.impl;
import com.springboot.study.demo1.entity.Cuser;
import com.springboot.study.demo1.entity.Users;
import com.springboot.study.demo1.service.CuserService;
import com.springboot.study.demo1.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.List;
import java.util.Map;
/**
 *@program: springboot_study
 *@description:
 *@author: yinkai
 *@create: 2020-03-02 15:31
 */
@Service
public class UsersServiceImpl implements UsersService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private CuserService cuserService;

    @Transactional(propagation= Propagation.NOT_SUPPORTED)
    @Override
    public void InsertUsers(Users users) {
        jdbcTemplate.update("INSERT INTO users(id,name, age, email) VALUES (?, ?, ?, ?);",users.getId(), users.getName(), users.getAge(), users.getEmail());
        //调用service中另一个方法
        Cuser cuser = new Cuser(users.getId(), users.getName(), users.getAge(), users.getEmail());
        //打印事务名
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("SELECT TRX_ID FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID( );");
        System.out.println(maps + TransactionSynchronizationManager.getCurrentTransactionName());
        cuserService.InsertCuser(cuser);
    }
}

package com.springboot.study.demo1.service.impl;
import com.springboot.study.demo1.entity.Cuser;
import com.springboot.study.demo1.service.CuserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.List;
import java.util.Map;
/**
 * @program: springboot_study
 * @description:
 * @author: yinkai
 * @create: 2020-03-02 17:12
 */
@Service
public class CuserServiceImpl implements CuserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public void InsertCuser(Cuser cuser) {
        jdbcTemplate.update("INSERT INTO cuser(id,name, age, email) VALUES (?, ?, ?, ?);", cuser.getId(), cuser.getName(), cuser.getAge(), cuser.getEmail());
        //打印事务名
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("SELECT TRX_ID FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID( );");
        System.out.println(maps + TransactionSynchronizationManager.getCurrentTransactionName());
    }
}

创建测试的controller

package com.springboot.study.demo1.controller;
import com.springboot.study.demo1.entity.Users;
import com.springboot.study.demo1.service.UsersService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

/**
 *@program: springboot_study
 *@description:
 *@author: yinkai
 *@create: 2020-03-01 13:00
 */
@RestController
@RequestMapping("/test")
public class TestController {
    @Resource
    private UsersService usersService;
    @RequestMapping("/test")
    public void test(){
        Users users = new Users(100L,"hello",22,"hello@qq.com");
        usersService.InsertUsers(users);
    }
}

项目启动后访问 http://localhost:8080/test/test/test即可根据控制台输出判断事务的传播

首先讲的是spring的默认传播行为REQUIRED

@Transactional(propagation= Propagation.REQUIRED)
一种优先按照调用者事务来,如果调用者没事务则直接创建一个

开始测试

1、两种方法都不加事务
    @Override
    public void InsertUsers(Users users) 
      
    @Override
    public void InsertCuser(Cuser cuser) 
   
image.png

这个是意料之中,不加事务注解当然不会开启事务

2、InsertUser 加上required,InsertCuser 不加事务
    @Transactional(propagation= Propagation.REQUIRED)
    @Override
    public void InsertUsers(Users users) 
      
    @Override
    public void InsertCuser(Cuser cuser) 
   
image.png

InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务;想象一下,调用关系 A-->B-->C--->D-->.... 若只有A上加了required事务注解,那么BCD...都会纳入A的事务中

3、InsertUser不加事务,InsertCuser 加上 required
    @Override
    public void InsertUsers(Users users) 

    @Transactional(propagation= Propagation.REQUIRED)
    @Override
    public void InsertCuser(Cuser cuser) 
image.png

InsertUser没有使用事务,InsertCuser 使用自己的事务

4、InsertUser 和 InsertCuser 都加上 required
   @Transactional(propagation= Propagation.REQUIRED)
   @Override
    public void InsertUsers(Users users) 

    @Transactional(propagation= Propagation.REQUIRED)
    @Override
    public void InsertCuser(Cuser cuser) 
image.png

InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务;

5、InsertUser 使用 required,InsertCuser 使用 supports
image.png

InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务;

6、InsertUser 使用 supports ,InsertCuser 使用 required
image.png

InsertUser 没有创建事务,InsertCuser 使用自己的事务

7、InsertUser 使用required,InsertCuser 使用 requires_new
image.png

InsertUser和 InsertCuser 各自创建了事务,互不影响

8、InsertUser 使用requires_new,InsertCuser 使用required
image.png

InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务;

9、InsertUser 使用 required,InsertCuser 使用 mandatory
image.png

InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务;

10、InsertUser 使用 mandatory ,InsertCuser 使用 requires

报异常

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

11、InsertUser 使用 requires,InsertCuser 使用 not_supported
image.png

InsertUser事务先被挂起,等到InsertCuser 非事务执行完毕后再继续执行

12、InsertUser 使用not_supported ,InsertCuser 使用 requires
image.png

InsertUser 不会创建事务,InsertCuser 使用自己的事务

13、InsertUser 使用 requires,InsertCuser 使用 never

报异常

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

14、InsertUser 使用 never,InsertCuser 使用 requires
image.png

InsertUser 不使用事务,InsertCuser 使用自己的事务

15、InsertUser 使用 requires,InsertCuser 使用 nested
image.png

InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务

16、InsertUser 使用 nested,InsertCuser 使用 requires
image.png

InsertUser 将事务传播给InsertCuser ,两者使用InsertUser 上的同一事务

大总结

实验 InsertUser 调用者 InsertCuser 被调用者 结果
1 不加 不加 两者都以无事务状态执行
2 required 不加 InsertUser 将它的事务传播给了InsertCuser
3 不加 required InsertUser 无事务,InsertCuser 创建了事务
4 required required InsertUser 将事务传播给了InsertCuser
5 requires supports InsertUser 把事务传播给了InsertCuser
6 supports requires InsertUser无事务,InsertCuser 创建了事务
7 requires mandatory InsertUser将事务传播给了InsertCuser
8 mandatory requires InsertUser报异常IllegalTransactionStateException
9 requires_new required InsertUser 把事务传播给了InsertCuser
10 required requires_new 两者使用各自的事务,互不影响
11 requires not_supported InsertUser事务先被挂起,等到InsertCuser 非事务执行完毕后再继续执行
12 not_supported requires InsertUser 不会创建事务,InsertCuser 使用自己的事务
13 requires nerver InsertCuser 报异常IllegalTransactionStateException
14 nerver requires InsertUser 无事务,InsertCuser 创建了事务
15 requires nested InsertUser 把事务传播给了InsertCuser
16 nested requires InsertUser 把事务传播给了InsertCuser

第二篇:
https://www.jianshu.com/p/a03ee8451775

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容