源码分析Mybatis拦截器(Interceptor)拦截saveBatch()获取不到实体id的原因

1.背景

由于业务需求想在Mybatis拦截器层面获取insert后实体id去做相关业务。但是发现执行**saveBatch()方法时,获取参数实体的时候,拿不到自增id。但是save()**方法可以。

save方法之所以可以是因为:

MybatisPlus的BaseMapper执行insert方法后实体带自增id的原因是,在数据库表中设置了主键自增属性。当插入一条新的记录时,数据库会自动为这条记录生成一个唯一的自增id,并将这个id赋值给实体类中的主键属性。因此,当你查询这条记录时,实体类中的主键属性就会带有自增id。

save()和saveBatch()的基本原理:

MybatisPlus是MyBatis的增强工具,它在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。在具体操作上,MybatisPlus提供了save()和saveBatch()两种方法用于数据的保存。

save()方法是通过调用BaseMapper封装的单条保存数据的insert()方法实现的。也就是说,当我们调用save()方法时,会直接将数据插入到数据库中。

而saveBatch()方法则是用于批量插入数据。在使用时,需要传入一个列表集合以及一个默认的批量插入数量(默认为1000)。在内部实现上,MybatisPlus会遍历这个列表集合,并依次将数据插入到数据库中。此外,需要注意的是,虽然saveBatch()方法可以实现批量插入,但根据实际测试和观察,批量插入的效率可能会受到一定影响。因此,在使用saveBatch()方法进行批量插入时,可能需要考虑优化方案以提高插入效率。

拦截器代码(简化版):

java 复制代码
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class TodoInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        Object parameter = args[1];

        // 调用原始方法
        Object result = invocation.proceed();

        if (parameter instanceof Enquiry) {
            Enquiry enquiry = (Enquiry) parameter;
        }


        return result;
    }

2.现象

我分别执行save和saveBatch方法,附带截图:

save():

saveBatch():

如图所示,saveBatch()方法的实体在拦截器里是获取不到自增id的。特别说明下,debug你会发现,MybatisPlus执行saveBatch()方法会遍历这个列表集合,并依次将数据插入到数据库中。因为拦截器会执行次数是和实体列表的数量是一致。

3.源码分析原因

第一步:

ServiceImpl类

java 复制代码
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);
        return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {

            //这里是拼装SQL,即每个实体类都是一个insert into 的sql
            sqlSession.insert(sqlStatement, entity);
        });
    }

第二步:

复制代码
SqlHelper类
java 复制代码
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one", new Object[0]);
        return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (sqlSession) -> {
            int size = list.size();
            int idxLimit = Math.min(batchSize, size);
            int i = 1;
            
            //从这里for可以看出,每实体就是一个insert语句
            for(Iterator var7 = list.iterator(); var7.hasNext(); ++i) {
                E element = var7.next();

                //这里就是与数据库配置,执行了sql,同时拦截器也是在这个方法里面执行的
                consumer.accept(sqlSession, element);
                if (i == idxLimit) {

                    //这里很重要,按批提交sql,刷新缓存,执行这里后可以拿到插入后的id。
                    //所以,大家想想,上面accept方法拿不到id的原因就是如此了。
                    sqlSession.flushStatements();
                    idxLimit = Math.min(idxLimit + batchSize, size);
                }
            }

        });
    }

展示debug结果:

大家可以看到,执行了sqlSession.flushStatements()方法后就出现了id。所以也是大家发现在controller层,service层等执行saveBatch方法能拿到id,在拦截器却不可以,原因就在这里

相关推荐
qq_5298353530 分钟前
对计算机中缓存的理解和使用Redis作为缓存
数据库·redis·缓存
月光水岸New3 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6753 小时前
数据库基础1
数据库
我爱松子鱼3 小时前
mysql之规则优化器RBO
数据库·mysql
chengooooooo3 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
Rverdoser4 小时前
【SQL】多表查询案例
数据库·sql
Galeoto4 小时前
how to export a table in sqlite, and import into another
数据库·sqlite
人间打气筒(Ada)5 小时前
MySQL主从架构
服务器·数据库·mysql
leegong231115 小时前
学习PostgreSQL专家认证
数据库·学习·postgresql
喝醉酒的小白5 小时前
PostgreSQL:更新字段慢
数据库·postgresql