学习笔记-Spring事务

学习的文章

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

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

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

带你读懂Spring 事务——事务的传播机制 - 知乎 (zhihu.com)

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<TransactionStatus> action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作
     * (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
     * 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。
     * 那么什么时候事务会回滚,有2种方式:
     * (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态
     * (2)execute方法或者executeWithoutResult方法内部抛出异常
     * 什么时候事务会提交?
     * 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();
     */
    transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
        @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中有默认的事务管理器

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

    • 作用位置

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

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

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

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

    • 属性

      • "transactionManager"或"value"

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

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

      • “propagation”

        • 指定事务的传播类型

        • 默认为REQUIRED

      • “rollbackFor”

        • 自定义回滚异常

事务的传播类型

  • REQUIRED

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

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

  • SUPPORTS

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

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

  • MANDATORY

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

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

  • REQUIERES_NEW

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

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

  • NOT_SUPPORTED

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

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

  • NEVER

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

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

  • NESTED

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

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

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

事务失效

  • 访问权限问题

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

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

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

  • 方法内部调用

    • @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);
                }
            }
        }
        

热门相关:无量真仙   极度诱惑之街头湿女   梦回大明春   战神   修仙界最后的单纯