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模式.
- 直接调用同类内部方法:
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);
});
}
}
结果所有的内容都会回滚,并不会产生新的数据
- 通过接口间接调用同类内部实现类方法:
@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
来实现
- 针对代码块级别的事务管理,相比注解式的事务管理它的粒度更细 ↩︎