深入分析Spring事务和底层原理

举报
海风极客 发表于 2022/10/16 22:06:40 2022/10/16
【摘要】 1 知识回顾 1.1 事务特性MySQL事务特性 1.2 隔离级别MySQL隔离级别 1.3 脏读、幻读、不可重复读MySQL脏读、幻读、不可重复读 2 Spring使用事务的两种方式 2.1 编程式事务使用TransactionalTemplate@Autowiredprivate UserDAO userDAO;@Autowiredprivate TransactionTemplate...

1 知识回顾

1.1 事务特性

MySQL事务特性

1.2 隔离级别

MySQL隔离级别

1.3 脏读、幻读、不可重复读

MySQL脏读、幻读、不可重复读

2 Spring使用事务的两种方式

2.1 编程式事务

使用TransactionalTemplate

@Autowired
private UserDAO userDAO;

@Autowired
private TransactionTemplate transactionTemplate;

@Override
public void insertUser(User user) {
    transactionTemplate.execute(new TransactionCallback<Object>() {
        //异常不需要处理,否则不回滚
        @Override
        public Object doInTransaction(TransactionStatus status) {
            userDAO.insertUser(user);
            return status;
        }
    });
}

2.3 声明式事务

使用@Transactional注解

@Transactional(rollbackFor = Exception.class)
@Override
public int updateUserById(User user) throws Exception {
    int res = userDAO.updateUserById(user);
    return res;
}

2.4 事务的传播机制

配置:@Transactional(propagation = Propagation.xxx)

public enum Propagation {

   /**
    * 支持当前事务,如果不存在则创建一个新的事务(Spring默认的事务传播机制).
    */
   REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

   /**
    * 支持当前事务,如果不存在则以非事务方式执行.
    */
   SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

   /**
    * 支持当前事务,如果不存在则抛出异常.
    */
   MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

   /**
    * 创建一个新事务,如果存在当前事务,则挂起当前事务.
    */
   REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

   /**
    * 以非事务方式执行,如果存在当前事务,则挂起当前事务  
    */
   NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

   /**
    * 以非事务方式执行,如果存在事务则抛出异常  .
    */
   NEVER(TransactionDefinition.PROPAGATION_NEVER),

   /**
    * 如果存在当前事务,则在嵌套事务中执行  ,否则,行为像{@code REQUIRED}.
    */
   NESTED(TransactionDefinition.PROPAGATION_NESTED);
    
   ...
}
  • REQUIRED:默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行。
  • REQUES_NEW:该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可。
  • SUPPORT:如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务。
  • NOT_SUPPORT:该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码。
  • NEVER:该传播机制不支持外层事务,即如果外层有事务就抛出异常。
  • MANDATORY:与NEVER相反,如果外层没有事务,则抛出异常。
  • NESTED:该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。

2.5 事务的隔离级别

配置:@Transactional(isolation = Isolation.xxx)

ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的更改。可能导致脏读、幻读或不可重复读。
ISOLATION_READ_COMMITTED (Oracle 默认级别)允许从已经提交的并发事务读取。可防止脏读,但幻读和不可重复读仍可能会发生。
ISOLATION_REPEATABLE_READ (MYSQL默认级别)对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻读仍可能发生。
ISOLATION_SERIALIZABLE 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。

2.6 Spring事务回滚(声明式为例)

配置:@Transactional(rollbackFor = Exception.class)

含义:当出现rollbackFor 指定的异常类或者其派生类时才会触发事务回滚机制

2.6.1 Java异常类继承体系(部分)

在这里插入图片描述
Spring框架的事务管理默认是只在发生不受控异常(RuntimeException和Error)时才进行事务回滚。

2.6.1 事务回滚实验

(1)使用默认配置,抛出ClassNotFound异常查看是否回滚

@Transactional
@Override
public int updateUserById(User user) throws Exception {
    int res = userDAO.updateUserById(user);
    throw new ClassNotFoundException();
}

测试:

@Test
void contextLoads() {
    System.out.println("before update "+userService.selectUserById(1));
    try {
        userService.updateUserById(new User(1L, "aaa", "aaa"));
    } catch (Exception e) {
        System.err.println("---------捕获异常咯---------");
        System.out.println("after update "+userService.selectUserById(1));
    }
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ejB5u4SB-1634872884482)(大话Spring事务原理.assets/image-20211021150801045.png)]
(2)配置成Excepction.class,抛出ClassNotFound异常查看是否回滚

@Transactional(rollbackFor = Exception.class)
@Override
public int updateUserById(User user) throws Exception {
    int res = userDAO.updateUserById(user);
    throw new ClassNotFoundException();
}

测试:

@Test
void contextLoads() {
    System.out.println("before update "+userService.selectUserById(1));
    try {
        userService.updateUserById(new User(1L, "bbb", "bbb"));
    } catch (Exception e) {
        System.err.println("---------捕获异常咯---------");
        System.out.println("after update "+userService.selectUserById(1));
    }
}

结果:
在这里插入图片描述
(3)使用默认配置,抛出NullPointException异常查看是否回滚

@Transactional
@Override
public int updateUserById(User user) throws Exception {
    int res = userDAO.updateUserById(user);
    throw new NullPointerException();
}

测试:

@Test
void contextLoads() {
    System.out.println("before update "+userService.selectUserById(1));
    try {
        userService.updateUserById(new User(1L, "ccc", "ccc"));
    } catch (Exception e) {
        System.err.println("---------捕获异常咯---------");
        System.out.println("after update "+userService.selectUserById(1));
    }
}

结果:
在这里插入图片描述

3 底层原理

3.1 概述

Spring事务属于AOP范畴,是通过代理对象对数据库的操作来进行事务处理,并且Spring事务的底层也是需要数据库的支持。

下面让我们debug下:
在这里插入图片描述
由此可见,声明事务之后,IOC初始化时会利用cglib进行AOP动态代理,将userDAO转变为代理对象对事务进行操作
在这里插入图片描述
点进入看下,并没有直接调用UserDAO的相关方法,而是进入了CglibAopProxy类进行对被调用对象的代理
在这里插入图片描述
在这里插入图片描述
继续向下debug直到出现异常
在这里插入图片描述
出现事务的相关信息,该步骤叫做事务清理

protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {
   if (txInfo != null) {
      // 恢复之前的状态
      txInfo.restoreThreadLocalStatus();
   }
}

3.2 事务流程

由此,我们可以总结出Spring事务的执行流程
在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。