事务颗粒度的控制:声明式事务 VS 编程式事务

01 引言

Spring事务是什么?Spring事务是一个并发控制的单位,开启事务之后,在同一个事务里的一系列操作要么全成功、要么全失败。简单来讲就是一损俱损,一荣俱荣。

举一个经典的银行转账案例,用户A给用户B赚了100元,实际的操作拆解为2步:

  • 第一:用户A的账户余额需要减少100元
  • 第二:用户B的账户余额需要增加100元

这两部操作,只有全部成功才会真正完成。倘若第一步成功了,用户A的账户余额减少了,然而第二步失败了,用户B就收不到钱,那么就会出现严重的问题。事务就可以很好的解决这类问题,要么都成功,要么都不成功才是我们想看到的结果。

02 事务的特点

事务主要有四个特性:原子性、一致性、隔离性、持久性。也是我们常说的ACID。

  • 原子性(Atomicity):

    事务中的所有操作,或者全部完成,或者全部不完成,不会停止在在中间某个环节。

  • 一致性(Consistency):

    在事务开始之前和事务结束以后,保证数据库的完整性没有被破坏。例如上述案例中两个用户相互转账,不管怎么转,两个账户的总额一定不会变。

  • 隔离性(Isolation):

    多个事务之间是独立的,不相互影响的。

  • 持久性(Durability):

    事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

03 事务的分类

事务的本质是数据库的一种机制,有些数据库支持事务,有些不支持事务。数据库也就是我们常说的数据源。而Spring事务则是Spring对数据源的一种支持。

Spring事务分为两类:声明式事务和编程式事务。

3.1 代码准备

在介绍声明式事务和编程式事务之前,我们先准备一些公用代码。

所需依赖

<!-- mysql驱动 -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- JDBC工具 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

接口方法

public interface UserInfoService {

    void save(List<UserInfo> list);
}

方法实现

@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    //@Transactional
    @Override
    public void save(List<UserInfo> list) {
        if (!CollectionUtils.isEmpty(list)) {

            for (int i = 0; i < list.size() ; i++) {
                if (1 == i) {
                    System.out.println("异常出现的位置:" + i);
                    throw new RuntimeException("异常出现了,脚标:" + i);
                }
                UserInfo userInfo = list.get(i);
                String sql = """
                        insert into user_info(name,age,sex,job,birthday,created_time,update_time) 
                        values(?,?,?,?,?,?,?)
                        """;
                jdbcTemplate.update(sql, userInfo.getName(), userInfo.getAge(), userInfo.getSex(), userInfo.getJob(), userInfo.getBirthday(), userInfo.getCreatedTime(), userInfo.getUpdateTime());
            }
        }
    }
}

3.2 声明式事务

声明式事务是借助注解@Transactional实现的,Spring boot中要想开启事务就需要声明@Transactional注解。事务支持的依赖来自spring-tx

<dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-tx</artifactId>
</dependency>

不加事务的案例

假设我们需要插入5条数据:用户名称为哪吒0哪吒4

在插入第一条之后,模拟异常。看看最终插入了几条数据。

最终的结果直插入一条数据,异常之后的数据都,没有进入数据库。

添加事务

删除刚才插入的数据。方法增加@Transactional

结果就会发现,一条记录都没有入库。这就是事务的魅力。

事务回滚的异常限制

案例我们模拟的异常是RuntimeException,如果是其他异常会不会回滚呢?我们需要了解一下@Transactional注解的一个属性rollbackFor

注释里指出默认只回滚RuntimeExceptionError两种。看看具体的实现:

了解其属性之后,我们如何突破这种限制呢?只需要配置该属性,指定异常的类型即可,如:

@Transactional(rollbackFor = Exception.class)

3.3 编程式事务

编程式事需要侵入到了业务代码里面,类似try...catch。因为侵入到业务代码,所以使用上更加加灵活。Spring提供两种方式的编程式事务管理:TransactionTemplatePlatformTransactionManager

TransactionTemplate使用

@Autowired
private TransactionTemplate transactionTemplate;

@Override
public void save(List<UserInfo> list) {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @SneakyThrows
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            if (!CollectionUtils.isEmpty(list)) {
                for (int i = 0; i < list.size(); i++) {
                    if (1 == i) {
                        System.out.println("异常出现的位置:" + i);
                        throw new Exception("异常出现了,脚标:" + i);
                    }
                    UserInfo userInfo = list.get(i);
                    String sql = """
                    insert into user_info(name,age,sex,job,birthday,created_time,update_time) 
                    values(?,?,?,?,?,?,?)
                    """;
                    jdbcTemplate.update(sql, userInfo.getName(), userInfo.getAge(), userInfo.getSex(), userInfo.getJob(), userInfo.getBirthday(), userInfo.getCreatedTime(), userInfo.getUpdateTime());
                }
            }
        }
    });
}

其中包含两个方法:

  • TransactionCallbackWithoutResult
  • TransactionCallback

分别来处理有没有返回值的情况。

PlatformTransactionManager

因为TransactionTemplate本身也是使用了PlatformTransactionManager,如果使用它就需要我们手动控制提交还是回滚。

3.4 两种事务类型对比

编程式事务和声明式事务的最终达到的结果是一样的,但是编程式事务更加细粒度的事控制事务。但是代码的侵入性相对较高。

声明式事务一般属于大事务,最终取决于方法拆分的够不够细。企业中大事务坑会造成内存溢出、系统假死的想象。使用应当谨慎!

04 小结

上面分享了事务的基本概念以及两种事务的类型,以及使用案例。使用的时候选择合适的事务类型。

事务有好有坏,事务可以帮助我们实现数据的一致性。但是占用的资源较多,可能会影响系统的可用性。大事务容易造成假死或内存溢出,需要细粒度的控制事务的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智_永无止境

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值