源码分析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,在拦截器却不可以,原因就在这里

相关推荐
十二同学啊36 分钟前
向量数据库:从核心原理到 RAG 与 Java 实战
数据库
蓝速科技1 小时前
会议室门牌公告通知发布选型与落地指南丨蓝速科技
大数据·运维·数据库·人工智能·科技
一 乐4 小时前
二手交易平台|基于springboot + vue二手交易平台(源码+数据库+文档)
java·数据库·vue.js·spring boot·小程序
SelectDB4 小时前
同等资源下 Apache Doris 4.2 vs StarRocks 4.1.1:1TB SSB 与 TPC-H 性能实测
大数据·数据库·数据分析
这个DBA有点耶4 小时前
数据库集群与分布式架构:三条技术路线对比、金仓KES RAC实测数据与决策框架
数据库·程序员·架构
MayBaymax5 小时前
MongoDB 索引与事务
java·数据库·mongodb
SelectDB5 小时前
Doris vs ClickHouse:企业 OLAP 走向下一阶段,两种技术路线如何选择
大数据·数据库·数据分析
vx-程序开发5 小时前
【计算机毕设】django校园跑腿服务系统83141
java·数据库·vue.js·spring boot·spring·elasticsearch·django
大漠孤烟s5 小时前
4.Mybatis学习-动态sql、缓存、逆向工程
mybatis
一 乐5 小时前
养老院管理系统|基于springboot + vue养老院管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计