最近一个老的项目从ibatis升级到了mybatis,之前的批处理方法需要在mybatis中重新实现。 实际实现过程中发现并没有如ibatis类似的api需要一些特殊处理。下面来讲讲mybatis中实现批处理的方式。

sql处理方式

sql的处理方式批较简单,直接将对应的sql变成批量sql,如下:

批量插入

1
2
3
4
5
6
7
8
<insert id="batchInsert">
insert into 
    user(id, gmt_create, gmt_modified, username, age, sex, phone)
    values
    <foreatch item="user" collection="userList" separator=",">
    (#{user.id}, now(), now(), #{user.username}, #{user.age}, #{user.sex}, #{user.phone})
    </foreatch>
</insert>

批量更新

  • 单行sql
1
2
3
4
5
6
7
8
<update id="batchUpdate">
update user set 
    age = #{age}
where id in 
    <foreatch item=id collection="idList" open="(" separator="," close=")">
    #{id}
    </foreatch>
</update>
  • 多行sql
1
2
3
4
5
6
7
8
9
<update id="batchUpdate">
    <foreatch item=user collection="userList" separator=";">
    update user set 
        age = #{user.age},
        phone = #{user.phone}
    where 
        id = #{user.id}
    </foreatch>
</update>

批量删除

  • 单行sql
1
2
3
4
5
6
7
<delete id="batchDelete">
delete from user 
where id in 
    <foreatch item=id collection="idList" open="(" separator="," close=")">
    #{id}
    </foreatch>
</update>
  • 多行sql
1
2
3
4
5
<update id="batchUpdate">
    <foreatch item=id collection="idList" separator=";">
    delete from user where id = #{id}
    </foreatch>
</update>

多行sql需在mysql链接参数上增加allowMultiQueries=true

用sql批量处理方式存在局限性,最明显的是长度问题,另外就是in条件存在性能问题。

jdbc batch方式

ibatisexecute的回调api可以直接实现,底层使用的是jdbcbatch能力,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public int batchUpdate(String statementId, List<Map<String, Object>> params) {
        
    return getSqlMapClientTemplate().execute(executor -> {

        int count = 0;
        int batchSize = 0;
        executor.startBatch();
        for (Map<String, Object> param : params) {
            executor.update(statementId, param);
            batchSize++;
            if (batchSize == 50) {
                count += executor.executeBatch();
                executor.startBatch();
                batchSize = 0;
            }
        }

        if (batchSize > 0) {
            count += executor.executeBatch();
        }

        return count;
    });
}

mybatis中不在提供execute这种api了,实际还是支持的但预要另外特殊处理下,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Autowired
private SqlSessionFactory sqlSessionFactory;

private SqlSession batchSqlSession;

@PostConstruct
public void init() {
    // 初始化批量处理的sql session
    batchSqlSession = new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
}

public int batchUpdate(String statementId, List<Map<String, Object>> params) {

    List<BatchResult> batchResults = new ArrayList<>();

    int batchSize = 0;
    for (Map<String, Object> param : params) {
        batchSqlSession.update(statementId, param);
        batchSize++;
        if (batchSize == 50) {
            batchResults.addAll(batchSqlSession.flushStatements());
            batchSize = 0;
        }
    }

    if (batchSize > 0) {
        batchResults.addAll(batchSqlSession.flushStatements());
    }

    return batchResults.stream().map(BatchResult::getUpdateCounts).flatMapToInt(Arrays::stream).sum();
}

这里的核心是初化一个BATCHSqlSession,上面代码设置的BatcSize为50,你可以根据自已的情况进行调整,但这个值不建议过大,会导致游标过大的错误。

总结

  1. 单行sql这种方式比较简单直接针对sql进行处理即可,无需特殊编码,但是存在sql过长和性能问题。
  2. jdbc batch这种方式需要额外的编码,但不存在sql过长和性能问题。

这里推荐jdbc batch方式,没有副作用,不存任何隐患。

笔者这里生产环境中3w数据的更新只需5s,未使用BATCH之前在15-20s左右。