Spring boot 事务(Transaction)问题学习

事务的理解:

事务的四个特性原子性 【要么全部执行,要么全部不执行】、一致性【事务完成时,必须是所有相关数据保持一致(原子性保证数据库出现了问题之后还是会继续执行保证在了 一致性,隔离性 通过限制可能会出现的覆盖等问题来保证一致性)】、隔离性【一个事务的执行不能被其他事务干扰】、持久性【事务一旦提交,那么就是永久性的存储】

一致性是事务最终的目的,其他三个性质都是为了一致性而存在的。

java支持的事务类型:

  • jdbc事务 :分为手动和自动事务,可以通过对应参数设定,但是不能跨数据源【只能单个数据库使用】
  • JTA事务 :多用于分布式事务,因为此时@Transaction不能满足分布式系统的事务问题【但是java只是有JTA接口没有具体实现,具体实现可参考:多数据源时使用Atomikos_】__
  • 容器事务 :容器事务如spring事务等等 大多是基于JTA的复杂API

spring事务

一般情况下是使用在单独数据源的情况下,也就是 @Transaction 支持的范围是不跨数据源的,如果要使用跨数据源的情况就得使用实现 JTA的相关包来配合【就我目前的理解,这个是同系统内没问题,如果跨系统,比如使用http连接跨系统访问,连接异常的时候可能回出现问题】

@Transaction 需要指定哪些异常需要引起回滚

spring事务的基础

事务是由 org.springframework.transaction.PlatformTransactionManager 接口定义了一种事务策略

PlatformTransactionManager 内 getTransaction() 方法内定义了几种标准事务需要的定义内容:

Propagation【传播级别】,Isolation【隔离级别】,Timeout【超时时间】,Read-only【是否只读】

  • 五种隔离级别:
int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
  • 七种传播级别:
int PROPAGATION_REQUIRED = 0;  /** spring 事务的默认级别 0
   如果没有其他事务那么就以当前方法创建事务,如果外层有更大的事务块,那么就自动成为其中一部分 **/
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3; //如果当前有事务,则新建一个事务
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
特点->适用范围
  • 可以声明式的管理任何类
  • 提供了对回滚规则的编程和声明性支持。
  • 可以使用*AOP(切面编程)*自定义事务的行为:如在事务回滚的时候加入自定意的内容等。
  • 不支持跨远程调用事务(如 http调用行为 ,被调用方是无法受到约束)
@Transactional 注解的使用

Transactional 注解类中的不同参数,分别代表了事务的几个可配置信息

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;//事务传播的级别

    Isolation isolation() default Isolation.DEFAULT;//事务隔离级别

    int timeout() default -1; //超时时间

    boolean readOnly() default false; //可读

    Class<? extends Throwable>[] rollbackFor() default {}; //当抛出指定异常时回滚[此处class 是异常类]

    String[] rollbackForClassName() default {};//当抛出指定异常时回滚[根据className]

    Class<? extends Throwable>[] noRollbackFor() default {};//当抛出该异常时不回滚

    String[] noRollbackForClassName() default {};//当抛出该异常时不回滚
}
更改默认设置

基本如上图所示的几个属性可以修改,其中value单独解释一下:

@Transactional("xxx") //xxx 是通过xml或 注解 配置的事务管理器
注意事项
  • 默认范围:@Transactional 默认情况下,是通过切面代理实现的,所以如果在没有特殊处理的情况下,最好使用在具体实现方法上,而不是使用在接口上,如果使用在接口上会导致在同类内调用内部方法的时候,被调用的内部方法不会包含在事务内 如果要在这种情况下使自调事务生效那么就需要使用AspectJ模式.
  1. 直接调用同类内部方法:
class A{
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public MessageResult insertOne() throws Exception{
        ComArea comArea = new ComArea();
        comArea.setCaId(GetIdUtil.getId("testArea"));
        comArea.setCaIfuse(false);
        comArea.setOrgId("67978dbf176d41aebce1d7f787e563dc1565589063123");
        comArea.setCaOrdertime(GetIdUtil.getOrderTime(0));
        comArea.setCaName("min国"+ new Random(100));
        comArea.setCaSimpleid("2020");
        comArea.setCaOrdernum(null);
        comAreaMapper.insertSelective(comArea);
        this.insertTow();//差别在这里
        if (true)
            throw new RuntimeException();
        return MessageResult.ok();
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertTow() throws Exception{
        ComArea comArea = new ComArea();
        comArea.setCaIfuse(false);
        comArea.setOrgId("67978dbf176d41aebce1d7f787e563dc1565589063123");
        comArea.setCaName("米国"+ new Random(100));
        comArea.setCaSimpleid("2021");
        comArea.setCaOrdernum(null);
        List<ComArea> comAreaList = new ArrayList<>();
        comAreaList.add(comArea);
        ComArea comArea2 = new ComArea();
        BeanUtils.copyProperties(comArea,comArea2);
        comArea2.setCaIfuse(true);
        comAreaList.add(comArea);
        comAreaList.stream().forEach(comArea1 -> {
            comArea1.setCaOrdertime(GetIdUtil.getOrderTime(0));
            comArea1.setCaId(GetIdUtil.getId("testArea"));
            comAreaMapper.insertSelective(comArea1);
        });
    }
}

结果所有的内容都会回滚,并不会产生新的数据

  1. 通过接口间接调用同类内部实现类方法:
@Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public MessageResult insertOne() throws Exception{
        ComArea comArea = new ComArea();
        comArea.setCaId(GetIdUtil.getId("testArea"));
        comArea.setCaIfuse(false);
        comArea.setOrgId("67978dbf176d41aebce1d7f787e563dc1565589063123");
        comArea.setCaOrdertime(GetIdUtil.getOrderTime(0));
        comArea.setCaName("min国"+ new Random(100));
        comArea.setCaSimpleid("2020");
        comArea.setCaOrdernum(null);
        comAreaMapper.insertSelective(comArea);
        comAreaService.insertTow();//差别在这里
        if (true)
            throw new RuntimeException();
        return MessageResult.ok();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertTow() throws Exception{
        ComArea comArea = new ComArea();
        comArea.setCaIfuse(false);
        comArea.setOrgId("67978dbf176d41aebce1d7f787e563dc1565589063123");
        comArea.setCaName("米国"+ new Random(100));
        comArea.setCaSimpleid("2021");
        comArea.setCaOrdernum(null);
        List<ComArea> comAreaList = new ArrayList<>();
        comAreaList.add(comArea);
        ComArea comArea2 = new ComArea();
        BeanUtils.copyProperties(comArea,comArea2);
        comArea2.setCaIfuse(true);
        comAreaList.add(comArea);
        comAreaList.stream().forEach(comArea1 -> {
            comArea1.setCaOrdertime(GetIdUtil.getOrderTime(0));
            comArea1.setCaId(GetIdUtil.getId("testArea"));
            comAreaMapper.insertSelective(comArea1);
        });

    }

结果insertTow中的数据插入数据库内,而 insertOne 中其他内容都回滚了

结论@Transactional 在不手动处理的情况下

  • 同类方法调用同类内部方法时,如果直接调用那么默认被调用方法是调用方法事务的一部分,无论被调用方法是否有@Transactional 注解,以及注解相应参数,都是不会生效的。
  • 同类方法通过接口间接调用时,如果被调用方是有@Transactional 的,并有特殊参数配置的,那么此时这些参数配置是生效的[ 比如 : 被调用方使用 @Transactional(propagation = Propagation.REQUIRES_NEW) 那么他就会自己重新创建一个事务,不受调用方的影响]
  • 默认的@Transactional 下,所有的@RuntimeException 都会回滚,但是不一定是所有的Exception都会回滚【比如 检查时异常(check Exception)】
开启spring的事务日志
#在配置文件中加入如下内容 
logging:
  level:
    org.springframework.transaction.interceptor: trace # trace 是粒度最细的日志级别【追踪】
    #只有trace级别的日志会显示事务的运行
编程式事务管理1

当代码中只是很少的操作需要用到事务管理的时候,使用。

使用 TransactionTemplate 或 PlatformTransactionManager 来实现



  1. 针对代码块级别的事务管理,相比注解式的事务管理它的粒度更细 ↩︎