关于mybatis实现batchInsertSelective返回自增id与高性能执行方案

本文介绍了如何通过自定义Mybatis基类,重写`getResources()`方法,实现统一的SQL操作,解决批量插入时自增id的问题,并比较了jdbc批量提交与Mybatisforeach的性能。作者还展示了如何使用`SqlSession`进行批量插入并返回id,以及性能测试的结果。
摘要由CSDN通过智能技术生成

因为之前研究出了mybatis基类统一查询、修改等操作的实现方式,有兴趣的小伙伴可以看看

实现自定义mybatis基类统一查询、修改等操作-CSDN博客文章浏览阅读411次,点赞6次,收藏7次。当我们的spring项目启动的时候,实际上是会通过MybatisProperties类的resolveMapperLocations()中getResources()的方法去读取xml文件,然后加载到内存中(xml解析成dom文件,变成XNode,然后通过parseDynamicTags()方法转换成了MixedSqlNode对象,最终解析成sql,有兴趣的朋友可以去深究一下),那我在思考,我能否通过重写getResources()方法,在里面都加上统一的sql来达到作为一个mapper基类的统一操作。https://blog.csdn.net/qq_37294047/article/details/136056738?spm=1001.2014.3001.5501

然后在封装统一的 batchInsertSelective方法,sql写法如下:

TestMapper.java

public interface TestMapper {
    int foreachInsertSelective(@Param("list") List<Test> entityList);
}

 TestMapper.xml 

<insert id="foreachInsertSelective">
        <foreach collection="list" separator=";" close="" index="index" item="item" open="">
            INSERT INTO
            test
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="item.id != null">id,</if>
                <if test="item.content != null">content,</if>
                <if test="item.puid != null">puid,</if>
            </trim>
            <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
                <if test="item.id != null">#{item.id},</if>
                <if test="item.content != null">#{item.content},</if>
                <if test="item.puid != null">#{item.puid},</if>
            </trim>
        </foreach>
    </insert>

因为忽略null值插入,然后插入的对象可能各自的不同字段。

比如:[{"content":"test"},{"puid":100}]。所以采用xml<foreach>标签进行循环插入。

那么就发现有个需求无法满足。

如果这个表的id是自增的,那么通过<foreach>循环插入的方法无法回填id!

看一下下面的实验:

 TestMapper.java

public interface TestMapper {
    int foreachInsertSelective(@Param("list") List<Test> entityList);
}

TestMapper.xml 

 <insert id="foreachInsertSelective" useGeneratedKeys="true" keyProperty="id">
        <foreach collection="list" separator=";" close="" index="index" item="item" open="">
            INSERT INTO
            test
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="item.id != null">id,</if>
                <if test="item.content != null">content,</if>
                <if test="item.puid != null">puid,</if>
            </trim>
            <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
                <if test="item.id != null">#{item.id},</if>
                <if test="item.content != null">#{item.content},</if>
                <if test="item.puid != null">#{item.puid},</if>
            </trim>
        </foreach>
    </insert>

test结果:

可以看到只返回的第一个id,其他的19个id并没有帮我们回写回去。

为什么会出现这种情况? 

通过翻阅mybatis的源码。

正如网上的传说,新版一点的mybatis使用的默认keyGenerator是Jdbc3KeyGenerator。那我们再接着在这里面找。

重头戏来了: 

可以发现,这里发现,jdbc返回的ResultSet只有一个,并没有返回全部的id字段。那么这也怪不了mybatis了。

思考:

那么如何即实现batchInsertSelective,又能让自增表使用batchInsertSelective方法时返回id呢?

这时候我脑子一抽,想到了在学校中学习到的,jdbc批量提交!!!

从网上搜索到的批量提交的jdbc代码。


public class Batch {
    @Test
    public void batch() throws Exception {
        Connection connection = JDBCUtils.getConnection();
 
        String sql = "insert into admin values(?,?)";
 
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
 
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000; i++) {
            preparedStatement.setString(1,"jak");
            preparedStatement.setString(2,"abc");
            //语句添加到批处理包里
            preparedStatement.addBatch();
 
            if (((i + 1) % 1000) == 0){
                preparedStatement.executeBatch();
                //到了装1000条执行后清空
                preparedStatement.clearBatch();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("Batch方法执行时间" + (end - start));
        JDBCUtils.close(null,connection,preparedStatement);
    }
}

 这代码多么的古老哦。那么现在既然有了mybatis,那就不可能去使用原生的jdbc来再封装一次造轮子。并且mybatis是对jdbc的封装,那么看看它有没有暴露出来对应的功能。

对mybatis的了解,知道mybatis其实最重要的就是sqlSession这玩意。

那我们看一下正常使用mybatis功能时,他是怎么获取sqlSession的。

其实就是通过SqlSessionUtils.getSqlSession方法获取,那么就简单了。看看入参都有些什么:

 看代码的方法说明。最主要的是

executorType The executor type of the SqlSession to create。(传入需要创建的执行类型)

振奋人心,有BATCH的类型,那么说明mybatis其实是支持jdbc批量执行的。

那么接下来上代码

BaseServiceImpl.java

public class BaseServiceImpl<S extends BaseService<T>, M extends IBaseMapper<T>, T extends BaseModel> {

    @Autowired
    protected M baseMapper;

    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    private Class<S> currentServiceClass = getCurrentClass(0);
    private Class<M> currentMapperClass = getCurrentClass(1);
    private S currentService;

    private <Z> Class<Z> getCurrentClass(int index) {
        Type genericSuperclass = getClass().getGenericSuperclass();
        System.out.println(genericSuperclass);
        ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
        return (Class<Z>) parameterizedType.getActualTypeArguments()[index];
    }

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public int batchInsertSelective(List<T> entityList) {
        setDefault(entityList, true);
        AtomicInteger row = new AtomicInteger();
        executeBatch(entityList, IBaseMapper::insertSelective);
        return row.get();
    }

/**
     * 批量执行
     * 关于为什么弄出来了这个执行方法,
     * 一、因为我们公司大部分的表都设置了主键自增,如果插入的时候,用的是mybatis foreach标签进行插入,是不会填充id到实体里面的。
     * 这主要是为了insertSelective,insert方法的批量操作设计。
     * 二、性能,用executeBatch效率更高,可以看
     * @see TestMybatis#test()
     * @param entityList
     * @param biFunction
     * @return 执行成功的条数
     */
    private int executeBatch(List<T> entityList, BiFunction<IBaseMapper<T>, T, Integer> biFunction) {
        if (entityList.size() == 1) {
            return biFunction.apply(baseMapper, entityList.get(0));
        }
        // 获取sqlSession
        SqlSession sqlSession = SqlSessionUtils.getSqlSession(
                sqlSessionTemplate.getSqlSessionFactory(),
                ExecutorType.BATCH,
                sqlSessionTemplate.getPersistenceExceptionTranslator());
        IBaseMapper<T> mapper = sqlSession.getMapper(currentMapperClass);
        AtomicInteger row = new AtomicInteger();
        batchDoSomething(entityList, list -> {
            for (T t : list) {
                row.addAndGet(biFunction.apply(mapper, t));
            }
            sqlSession.flushStatements();
        });
        return row.get();
    }
}

最主要的关注方法是:executeBatch 

BaseMapper.xml

  <insert id="insertSelective" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO
        test
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="item.id != null">id,</if>
            <if test="item.content != null">content,</if>
            <if test="item.puid != null">puid,</if>
        </trim>
        <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
            <if test="item.id != null">#{item.id},</if>
            <if test="item.content != null">#{item.content},</if>
            <if test="item.puid != null">#{item.puid},</if>
        </trim>
    </insert>

ok,附上测试结果:

并且做了一下性能的测试。发现使用jdbc批量提交比在xml中用<foreach>标签批量提交的效率是要高许多的。并且返回了对应的id。对于批量提交并且回写id的源码我并没有去深究,有兴趣的小伙伴可以尝试去看看

结束。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值