Java事务&事务失效场景

简介: Java事务&事务失效场景

 事务

定义

一组操作的集合,要么同时成功,要么同时失败。

四大特征

A原子性

C一致性

I隔离性

D持久性

spring管理事务

@Transactional

只要该方法中有一个操作异常,该方法内成功执行的操作也会回滚。

该方法内都没有异常,才会提交。

@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;
    @Autowired
    private EmpMapper empMapper;
    /*
    * rollbackfor:默认为运行时异常回滚:RuntimeException.class
    *             可以赋值为Exception.class
    * */
    @Transactional(rollbackFor = Exception.class)   // 方法开启事务,方法内没有任何异常
    @Override
    @Log
    public void deleteById(Integer id) {
        try {
            // 部门删除
            deptMapper.deleteById(id);
            // 删除该部门的员工
            empMapper.deleteDeptId(id);
            int i = 3/0;
        }finally {
            // 记录日志
            deptLogService.add(id);
        }
    }
}

image.gif

Propagation

Propagation默认是:REQUIRED

a方法调用b方法,如果a方法有事务,b方法就加入a的事务,如果a方法没有事务,b方法就新建一个事务。

propagation = Propagation.REQUIRES_NEW 新事务

无论如何都会创建一个新事务。

a方法调用b方法,a方法出现异常,b方法只要不发生异常,b方法就不会回滚,因为b方法是new一个新事务,与a方法无关。

@Service
public class DeptLogServiceImpl implements DeptLogService {
    @Autowired
    private DeptLogMapper deptLogMapper;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void add(Integer id) {
        LocalDateTime createTime = LocalDateTime.now();
        String description = "执行了解散部门的操作,此时解散的是" + id + "号部门";
        deptLogMapper.add(createTime,description);
    }
}

image.gif

事务失效

1.事务方法非public修饰

由于Spring的事务是基于AOP的方式结合动态代理来实现的。因此事务方法一定要是public的,这样才能便于被Spring做事务的代理和增强。而且,在Spring内部也会有一个 org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource类,去检查事务方法的修饰

2.非事务方法调用事务方法

示例代码:

@Service
public class OrderService {
    public void createOrder(){
        // ... 准备订单数据
        // 生成订单并扣减库存
        insertOrderAndReduceStock();
    }
    @Transactional
    public void insertOrderAndReduceStock(){
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();
    }
}

image.gif

可以看到,insertOrderAndReduceStock方法是一个事务方法,肯定会被Spring事务管理。Spring会给OrderService类生成一个动态代理对象,对insertOrderAndReduceStock方法做增加,实现事务效果。

但是现在createOrder方法是一个非事务方法,在其中调用了insertOrderAndReduceStock方法,这个调用其实隐含了一个this.的前缀。也就是说,这里相当于是直接调用原始的OrderService中的普通方法,而非被Spring代理对象的代理方法。那事务肯定就失效了!

3.事务方法的异常被捕获

示例代码:

@Service
 public class OrderService {
    @Transactional
    public void createOrder(){
        // ... 准备订单数据
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();
    }
    private void reduceStock() {
        try {
            // ...扣库存
        } catch (Exception e) {
            // 处理异常
        }
    }
 }

image.gif

在这段代码中,reduceStock方法内部直接捕获了Exception类型的异常,也就是说方法执行过程中即便出现了异常也不会向外抛出。而Spring的事务管理就是要感知业务方法的异常,当捕获到异常后才会回滚事务。

现在事务被捕获,就会导致Spring无法感知事务异常,自然不会回滚,事务就失效了。

4.事务异常类型不对

示例代码:

@Service
 public class OrderService {
    @Transactional(rollbackFor = RuntimeException.class)
    public void createOrder() throws IOException {
        // ... 准备订单数据
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();
        throw new IOException();
    }
 }

image.gif

Spring的事务管理默认感知的异常类型是RuntimeException,当事务方法内部抛出了一个IOException时,不会被Spring捕获,因此就不会触发事务回滚,事务就失效了。

因此,当我们的业务中会抛出RuntimeException以外的异常时,应该通过@Transactional注解中的rollbackFor属性来指定异常类型:

@Transactional(rollbackFor = Exception.class)

image.gif

5.事务传播行为不对

示例代码:

@Service
 public class OrderService {
    @Transactional
    public void createOrder(){
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();
        throw new RuntimeException("业务异常");
    }
    @Transactional
    public void insertOrder() {
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void reduceStock() {
    }
 }

image.gif

在示例代码中,事务的入口是createOrder()方法,会开启一个事务,可以成为外部事务。在createOrder()方法内部又调用了insertOrder()方法和reduceStock()方法。这两个都是事务方法。

不过,reduceStock()方法的事务传播行为是REQUIRES_NEW,这会导致在进入reduceStock()方法时会创建一个新的事务,可以成为子事务。insertOrder()则是默认,因此会与createOrder()合并事务。

因此,当createOrder方法最后抛出异常时,只会导致insertOrder方法回滚,而不会导致reduceStock方法回滚,因为reduceStock是一个独立事务。

所以,一定要慎用传播行为,注意外部事务与内部事务之间的关系。

6.没有被Spring管理

示例代码:

//  @Service
 public class OrderService {
    @Transactional
    public void createOrder(){
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();
        throw new RuntimeException("业务异常");
    }
    @Transactional
    public void insertOrder() {
    }
    @Transactional
    public void reduceStock() {
    }
 }

image.gif

这个示例属于比较低级的错误,OrderService类没有添加@Service注解,因此就没有被Spring管理。你在方法上添加的@Transactional注解根本不会有人帮你动态代理,事务自然失效。


相关文章
|
7月前
|
缓存 Java
Java场景面试题:手机扫码登录到底是怎么实现的?
有个粉丝问到手机APP扫码登录是如何实现的,能讲讲给我讲讲吗? 扫码登录的本质,其实是通过已经登录的APP,扫描Web页面生成的二维码, 获取到一个Token作为登录凭证,然后再写入到Web端的登录认证程序。Token写入成功以后,会回调刷新Web页面跳转到登录成功的界面。
84 0
|
7月前
|
Java
Java IO流各场景详解及使用
Java IO流各场景详解及使用
53 0
|
8月前
java202304java学习笔记第六十五天-ssm-声明式控制-事务参数的配置1
java202304java学习笔记第六十五天-ssm-声明式控制-事务参数的配置1
22 0
|
8月前
java202304java学习笔记第六十五天-ssm-注解方式-声明式控制事务
java202304java学习笔记第六十五天-ssm-注解方式-声明式控制事务
43 0
|
4月前
|
设计模式 Java 数据库连接
设计模式与面向对象编程:举例说明在Java中应用工厂模式的场景,并编写一个简单的工厂模式实现。编写一个Java装饰器,用于添加日志记录功能到现有方法上。
设计模式与面向对象编程:举例说明在Java中应用工厂模式的场景,并编写一个简单的工厂模式实现。编写一个Java装饰器,用于添加日志记录功能到现有方法上。
23 0
|
4月前
|
分布式计算 安全 Java
深入理解Java GSS(含kerberos认证及在hadoop、flink案例场景举例)
深入理解Java GSS(含kerberos认证及在hadoop、flink案例场景举例)
48 0
|
6月前
|
Java 关系型数据库 MySQL
Java 最常见的面试题:说一下数据库的事务隔离?
Java 最常见的面试题:说一下数据库的事务隔离?
|
7月前
|
负载均衡 算法 Java
三面阿里,被Java面试官虐哭!现场还原真实的“被虐”场景
人人都有大厂梦,我也不例外,从大三开始,就一直想进入阿里工作,大毕竟是大厂,想想也没那么容易,不过好在自己学历还过得去,项目经验也有得讲,所以今年也斗胆尝试了一下,直接就投了阿里云计算。简历是过了,紧张激动地开始准备面试,但结果并不理想,三面过后,出门我就哭了!以下还原这次阿里云面试的真实场景和面试题!希望能够给大家一些参考和帮助。
|
7月前
|
缓存 前端开发 Java
Java场景面试题:短信验证码接口被狂刷,怎么办?
问:Tom老师,请问短信验证码接口被狂刷,搞得服务都快要崩溃了,我该怎么办? 答:我想都到云时代了,我想这个问题不应该出现吧?现在,都有非常多的短信服务提供商,应该自带防火墙功能的。
152 0
|
7月前
|
算法 Java 程序员
【Java面试】传统行业跳互联网,一定要会这道题:在秒杀场景中,常用的限流算法有哪些?
一位在传统行业工作了 5 年的程序员。去一个互联网公司面试,被问到一个秒杀的场景题。因为之前完全没接触过分布式相关的项目,单单只是问了限流算法都没有回答不上来,于是向我来求助。
77 0