学习笔记-Spring事务

学习的文章

小姐姐非要问我:spring编程式事务是啥? (qq.com)

一文搞懂什么是事务 - 知乎 (zhihu.com)

阿里3面:Spring声明式事务连环炮,让我措手不及。。 (qq.com)

[带你读懂Spring 事务——事务的传播机制 - 知乎 (zhihu.com)]( https://zhuanlan.zhihu.com/p/148504094#:~:text=带你读懂Spring 事务——事务的传播机制 1 一、什么是事务的传播? 简单的理解就是多个事务方法相互调用时%2C事务如何在这些方法间传播。 ... 2 二、Spring事务传播类型枚举Propagation介绍 ,...%207%20REQUIRES_NEW%20...%208%20NOT_SUPPORTED%20...%20%E6%9B%B4%E5%A4%9A%E9%A1%B9%E7%9B%AE)

spring 事务失效的 12 种场景_事务什么时候失效_hanjq_code的博客-CSDN博客

什么是事务

  • 事务是并发操作的单位

  • 是用户定义的操作序列

  • 事务如果成功,则会提交

  • 事务如果失败,则会回滚

事务的四大特性

  • 原子性

    • 事务中操作,要么不做,要么都做
  • 持久性

    • 一个事务一旦提交,它对数据库的改变是永久的
  • 一致性

    • 事务让数据库从一个一致性状态转移到另一个一致性状态

    • 比如

      • 事务前,A有50,B有50,总共有100

      • 事务中,A送给B20

      • 事务后,A有30,B有70,总共还是有100

  • 隔离性

    • 一个事务的执行不能被其他事务所干扰

    • 分成不同的等级

事务并发访问导致的数据问题

脏读

读到了修改但还没有提交的数据

  • A事务修改了某条记录的字段c,但还没有提交

  • B事务在此时读取了字段c

  • A事务发生了回滚,字段c恢复了修改前的状态

  • 但B事务持有的还是修改后的状态

不可重复读

某个事务在执行过程中,两次读同一个条记录,但结果不一样

  • A事务读取了记录c,值为10

  • B事务修改了记录c,值为0

  • A事务再次读取记录c,值为0

幻读

某个事务在执行过程中,前后两次读取记录,数据总量不一样

  • A事务统计了表c中的记录总数,结果为10

  • B事务删除了表c中的5条记录

  • A事务再次统计了表c中的记录总数,结果为5

和不可重复读的区别在于,幻读针对的是记录条数,不可重复读针对的是记录内容

事务的隔离级别

针对事务并发访问时出现的问题,设置了四种事务的隔离级别

读未提交

可以的读取还没有提交的数据

没有限制,三种问题都有可能发生

读已提交

只能读取已经提交了的数据

不会发生脏读,但是会发生不可重复读和幻读

可重复读

一个事务前后读取的同一条记录的结果必须一致

不会发生脏读和不可重复读,但会发生幻读

mysql中默认的事务隔离级别

串行化

所有的事务必须依次执行

不会发生脏读、不可重读读和幻读

效率比较低

Spring事务的使用方法

Spring分为两种控制事务的方法

  • 编程式事务

    • 方法1:通过PlatformTransactionManager控制事务

    • 方法2:通过TransactionTemplate控制事务

  • 声明式事务

    • 常用

编程式事务的使用

使用PlatformTransactionManager

@Test
public void test1() throws Exception {
//定义一个数据源
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root123");
dataSource.setInitialSize(5);
//定义一个JdbcTemplate,用来方便执行数据库增删改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
//3.开启事务:调用platformTransactionManager.getTransaction开启事务操作,得到事务状态(TransactionStatus)对象
TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
//4.执行业务操作,下面就执行2个插入操作
try {
System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");
//5.提交事务:platformTransactionManager.commit
platformTransactionManager.commit(transactionStatus);
} catch (Exception e) {
//6.回滚事务:platformTransactionManager.rollback
platformTransactionManager.rollback(transactionStatus);
}
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}

使用TransactionTemplate

@Test
public void test1() throws Exception {
//定义一个数据源
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("root123");
dataSource.setInitialSize(5);
//定义一个JdbcTemplate,用来方便执行数据库增删改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setTimeout(10);//如:设置超时时间10s
//3.创建TransactionTemplate对象
TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager, transactionDefinition);
/**
* 4.通过TransactionTemplate提供的方法执行业务操作
* 主要有2个方法:
* (1).executeWithoutResult(Consumer action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作
* (2). T execute(TransactionCallback action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
* 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。
* 那么什么时候事务会回滚,有2种方式:
* (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态
* (2)execute方法或者executeWithoutResult方法内部抛出异常
* 什么时候事务会提交?
* 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();
*/
transactionTemplate.executeWithoutResult(new Consumer() {
@Override
public void accept(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");

}
});
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}

声明式事务的使用

  • 在配置类上使用@EnableTransactionManagement

    • Springboot可以加在启动类上
  • 定义事务管理器

    • @Bean
      public PlatformTransactionManager transactionManager(DataSource dataSource) {
      return new DataSourceTransactionManager(dataSource);
      }

    • springboot中有默认的事务管理器

  • 在需要事务的目标上加上@Transactional注解

    • 作用位置

      • 注意:@Transactional只对public方法有效

      • @Transactional放在接口上,接口的所有实现类中所有的public方法都自动加上事务

      • @Transactional放在类上,当前类以及其下无限级子类中的public方法都被加上事务

      • @Transactional放在public方法上,方法被加上事务

    • 属性

      • "transactionManager"或"value"

        • 指定事务管理器的bean对象

        • 为空的话,默认按类型获取

      • “propagation”

        • 指定事务的传播类型

        • 默认为REQUIRED

      • “rollbackFor”

        • 自定义回滚异常
  • 什么时候回滚

    • 事务内部抛出异常时,事务会回滚
    • 标记为回滚:如果在事务内部调用了 setRollbackOnly() 方法将事务标记为回滚状态,事务会回滚
    • 配置回滚规则:通过配置事务的回滚规则,可以根据特定的异常类型来触发事务回滚。例如,可以配置当出现特定的异常类型时触发事务回滚

事务的传播类型

  • REQUIRED

    • 如果当前有事务,则加入当前事务

    • 如果当前没有事务,则自己新建一个事务

  • SUPPORTS

    • 如果当前有事务,则加入当前事务

    • 如果当前没有事务,则以非事务方式执行

  • MANDATORY

    • 如果当前有事务,则加入当前事务

    • 如果当前没有事务,则抛出异常

  • REQUIERES_NEW

    • 如果当前有事务,则将该事务挂起,另外新创建一个事务

    • 如果当前没有事务,则新创建一个事务

  • NOT_SUPPORTED

    • 如果当前有事务,则将该事务挂起,以非事务方式执行

    • 如果当前没有事务,则以非事务方式执行

  • NEVER

    • 如果当前有事务,则抛出异常

    • 如果当前没有事务,以非事务方式执行

  • NESTED

    • 如果当前有事务,则在当前事务中嵌套一个事务执行

    • 如果当前没有事务,则新建一个事务执行

事务失效或回滚异常的12种情况

事务失效

  • 访问权限问题

    • 事务只能对public的方法方法生效
  • 方法用final或static修饰

    • spring事务是使用动态代理的方式实现的

    • 如果加了final或static方法,则方法无法被代理

  • 方法内部调用

    • @Service
      public class UserService {

      @Autowired
      private UserMapper userMapper;

      public void add(UserModel userModel) {
      userMapper.insertUser(userModel);
      updateStatus(userModel);
      }

      @Transactional
      public void updateStatus(UserModel userModel) {
      doSameThing();
      }
      }

    • 在add方法中是通过this来调用updateStatus方法的,没有通过代理

    • 解决方法:

      • 1.注入自己

        • @Servcie
          public class ServiceA {
          @Autowired
          prvate ServiceA serviceA;

          public void save(User user) {
          queryData1();
          queryData2();
          serviceA.doSave(user);
          }

          @Transactional(rollbackFor=Exception.class)
          public void doSave(User user) {
          addData1();
          updateData2();
          }
          }

      • 2.通过AopContext.currentProxy()获取代理对象

        • @Servcie
          public class ServiceA {

          public void save(User user) {
          queryData1();
          queryData2();
          ((ServiceA)AopContext.currentProxy()).doSave(user);
          }

          @Transactional(rollbackFor=Exception.class)
          public void doSave(User user) {
          addData1();
          updateData2();
          }
          }

  • 未被spring管理

    • 忘了给类添加注释了

    • 没有被放到IOC容器中

  • 多线程调用

    • spring事务是通过数据库连接来实现的

    • 在多线程中,每一个线程用的都是不同的数据库连接

  • 表不支持事务

    • MyISAM引擎的表不支持事务
  • 未开启事务

    • springboot需要在启动类上加上@EnableTransactionManagement的注解

    • 传统spring项目需要在applicationContext.xml中进行相关的配置

事务回滚异常

  • 使用了错误的传播特性

  • 手动捕获了异常

    • 如果想要spring事务能够正常回滚,必须抛出它能够处理的异常

    • 使用try/catch将异常捕获,将导致事务不会回滚

  • 抛得异常类型不正确

    • spring事务,默认情况下只会回滚RuntimeException或Error
  • 自定义回滚异常不匹配

    • 比如定义了BusinessException,但抛出的是SqlException
  • 嵌套事务回滚范围多了

    • public class UserService {

      @Autowired
      private UserMapper userMapper;

      @Autowired
      private RoleService roleService;

      @Transactional
      public void add(UserModel userModel) throws Exception {
      userMapper.insertUser(userModel);
      roleService.doOtherThing();
      }
      }

      @Service
      public class RoleService {

      @Transactional(propagation = Propagation.NESTED)
      public void doOtherThing() {
      System.out.println("保存role表数据");
      }
      }

    • roleService.doOtherThing()如果发生回滚,它抛出的异常没有被处理,会继续往上级抛

    • add()在捕获到向上抛的异常后也会发生回滚

    • 应该手动进行捕获

      • @Slf4j
        @Service
        public class UserService {

        @Autowired
        private UserMapper userMapper;

        @Autowired
        private RoleService roleService;

        @Transactional
        public void add(UserModel userModel) throws Exception {

        userMapper.insertUser(userModel);
        try {
        roleService.doOtherThing();
        } catch (Exception e) {
        log.error(e.getMessage(), e);
        }
        }
        }

posted @ 2023-05-23 11:26  0Andl0  阅读(137)  评论(0)    收藏  举报