Spring之事务

407 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情

一、前言

本篇文章主要内容: Springboot中事务的简单使用, 事务的几种传播方式, 常见的一种事务失效场景及其解决方式

Spring相关文章汇总(Ps:有部分还未完成):

二、Spring中事务的几种传播行为

事务的传播行为: 当多个声明的事务方法在相互调用时, 这个事务的传递方式

Spring的七种事务传播行为:

  • REQUIRED(默认): 他是Spring里面默认的事务传播行为, 如果当前存在事务就加入, 不存在则新增
  • REQUIRED_NEW: 他不管当前是否存在事务, 都会新增一个事务来执行, 新老事务相互独立, 外部事务抛出异常不影响内部事务
  • NESTED: 如果当前存在事务则以嵌套事务执行, 不存在则新增
  • SUPPORTS: 表示支持当前的事务, 存在则加入, 不存在就以非事务的方式执行
  • NOT_SUPPORT: 以非事务的方式来执行, 若当前存在事务, 则把当前事务挂起
  • MANDATORY: 强制的事务执行, 若当前不存在事务则抛出异常
  • NEVER: 以非事务的方式来执行, 若当前存在事务则抛出异常

三、事务

Spring事务的代理对象执行某个方法时的步骤

  • 判断有没有@Transctional注解
  • 若存在@Transctional注解, 则创建一个数据库连接conn (事务管理器 DataSource)
  • 修改属性: conn.autocommit = false
  • 执行业务代码
  • 执行完了没有异常则提交, 若存在异常则回滚

Spring事务是否会失效的判断标准:

某个加了 @Transaction注解的方法被调用时, 要判断是否是直接被代理对象调用的, 如果是则事务会生效, 若不是则失效, 具体测试可看下面常见事务失效场景案例

事务的简单应用

新建test库juejin表

image.png

Config类为

@ComponentScan("com.ningxuan")
@Configuration
public class AppConfig {
    @Bean
    public NingxuanService ningxuanService(){
        return new NingxuanService();
    }
    @Bean
    // jdbc
    public JdbcTemplate jdbcTemplate(){
        return new JdbcTemplate(dataSource());
    }
    @Bean
    // 事务管理器
    public PlatformTransactionManager transactionManager(){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource());
        return transactionManager;
    }
    public DataSource dataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false");
        dataSource.setUsername("username");
        dataSource.setPassword("password");
        return dataSource;
    }
}

业务类为

@Component()
public class NingxuanService implements InitializingBean {


    @Resource
    private JdbcTemplate jdbcTemplate;
    
    // 使用事务
    @Transactional
    public void test() {
        jdbcTemplate.execute("1, 'ningxuan', 'https://juejin.cn/user/3334188415845838'");
        // 抛出一个空指针异常
        throw new NullPointerException();
    }
}

启动main方法为

public static void main(String[] args) {
    // 创建一个Spring对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    // 获取Bean容器
    NingxuanService ningxuanService = (NingxuanService)context.getBean("ningxuanService");
    // 执行方法
    ningxuanService.test();
}

可以看到, 我们在NingxuanService类里面对test()方法开启了事务注解@Transctional, 现在我们执行一下main方法爆了空指针异常, 同时我们的数据库新增也失败了

image.png

image.png

四、事务的常见失效场景

@Transctional失效

还是上面的Config类和main方法, 在我们的业务类NingXuanService类中新增test2()方法, 同时把test2()方法的事务传播机制设置为 NEVER

NEVER: 以非事务的方式来执行, 若当前存在事务则抛出异常

按照下面的代码来看, test()方法调用了test2()方法, 且test()方法存在事务, 那么按照事务传播方式 NEVER来看, 执行到test2()的时候就会抛出异常, 代码回滚, 具体如下

@Component()
public class NingxuanService implements InitializingBean {
    @Resource
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void test() {
        jdbcTemplate.execute("insert juejin value(1, 'ningxuan', 'https://juejin.cn/user/3334188415845838')");
        test2();
    }
    // 设置传播机制为NEVER
    @Transactional(propagation = Propagation.NEVER)
    public void test2(){
        jdbcTemplate.execute("insert juejin value(2, 'ningxuan_blog', 'https://juejin.cn/user/3334188415845838')");
    }
}

执行main方法之后, 我们发现事情没有按照我们想的那样进行, 没有异常的同时, 两条SQL还都执行成功, MySQL中能看到结果了, 这是为什么呢?

image.png

image.png

它实际的原因是因为我们在执行test2()方法的时候, 不是通过代理对象去执行的, 而是通过普通对象去执行的test2()方法, 这个时候没有去走@Transctional注解

执行test2()就相当于在NingxuanService中去 this.test2(); 而不是通过代理对象去执行的

解决方式:

  • 调用别的代理 新建一个类, 将我们的test2()方法放入那个类中, 然后调用
@Component
public class TestService {

    @Transactional(propagation = Propagation.NEVER)
    public void test2(){

    }

}

修改之后的NingxuanService, test2()通过testService去调用

@Component()
public class NingxuanService implements InitializingBean {

    @Resource
    private JdbcTemplate jdbcTemplate;
    @Resource
    private TestService testService;

    @Transactional
    public void test() {
        jdbcTemplate.execute("insert juejin value(1, 'ningxuan', 'https://juejin.cn/user/3334188415845838')");
        testService.test2();
    }
}

执行main方法, 如期报错

image.png

  • 自己注入自己

修改NingxuanService调用test2()方法, 改为自己调用自己的方式

@Component()
public class NingxuanService implements InitializingBean {

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Resource
    private NingxuanService ningxuanService;

    @Transactional
    public void test() {
        jdbcTemplate.execute("insert juejin value(1, 'ningxuan', 'https://juejin.cn/user/3334188415845838')");
        ningxuanService.test2();
    }
    // 设置传播机制为NEVER
    @Transactional(propagation = Propagation.NEVER)
    public void test2(){
        jdbcTemplate.execute("insert juejin value(2, 'ningxuan_blog', 'https://juejin.cn/user/3334188415845838')");
    }
}

执行main方法之后, 如期报错

image.png

本文内容到此结束了

如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。

如有错误❌疑问💬欢迎各位大佬指出。

我是 宁轩 , 我们下次再见