MyBatis 批量更新實現技術解析
前言
在企業級應用開發中,數據的批量處理是常見需求。當面對需要同時更新多條數據記錄的場景時,使用MyBatis
實現批量更新可以有效提升數據處理效率,減少數據庫交互次數。
實現方式
循環單條更新
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Transactional
public void batchUpdate(List<User> userList) {
for (User user : userList) {
userMapper.updateUser(user);
}
}
}
對應的XML
映射文件:
<update id="updateUser" parameterType="com.example.entity.User">
UPDATE user
SET username = #{username},
age = #{age},
email = #{email}
WHERE id = #{id}
</update>
- 優點:實現簡單,無需特殊配置;單條
SQL
結構清晰,易于調試。 - 缺點:性能較差,每執行一次更新都要與數據庫建立一次連接,數據庫通信開銷大;對于大數據量操作,事務管理壓力大。
- 適用場景:數據量較小(幾十條以內)、對性能要求不高的場景。
原生saveOrUpdateBatch
default boolean saveOrUpdateBatch(Collection<T> entityList) {
return this.saveOrUpdateBatch(entityList, 1000);
}
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 i = 1;
for(Iterator var6 = list.iterator(); var6.hasNext(); ++i) {
E element = var6.next();
consumer.accept(sqlSession, element);
if (i % batchSize == 0 || i == size) {
sqlSession.flushStatements();
}
}
});
}
最終來到了executeBatch()
方法,可以看到這很明顯是在一條一條循環更新,通過sqlSession.flushStatements()
將一個個單條插入的update
語句分批次進行提交,而且是同一個sqlSession
,這相比遍歷集合循環update
來說有一定的性能提升,但是這并不是sql
層面真正的批量插入。
使用 foreach 標簽拼接 SQL
<update id="batchUpdateUser" parameterType="java.util.List">
<foreach collection="list" item="item" separator=";">
UPDATE user
SET username = #{item.username},
age = #{item.age},
email = #{item.email}
WHERE id = #{item.id}
</foreach>
</update>
注意事項:需要在數據庫連接配置中添加
allowMultiQueries=true
參數,以支持多條SQL
語句的執行
- 優點:只需一次數據庫連接,減少了通信開銷,性能明顯優于循環單條更新。
- 缺點:
SQL
語句長度隨數據量增長,可能達到數據庫限制。 - 適用場景:數據量中等(幾百條)、數據庫支持多語句執行的場景。
使用 Case When 結構
<update id="batchUpdateUser" parameterType="java.util.List">
UPDATE user
SET
username = CASE
<foreach collection="list" item="item">
WHEN id = #{item.id} THEN #{item.username}
</foreach>
END,
age = CASE
<foreach collection="list" item="item">
WHEN id = #{item.id} THEN #{item.age}
</foreach>
END,
email = CASE
<foreach collection="list" item="item">
WHEN id = #{item.id} THEN #{item.email}
</foreach>
END
WHERE id IN
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item.id}
</foreach>
</update>
- 優點:一條
SQL
語句完成所有更新,數據庫只需要解析一次SQL
,執行效率高;事務原子性得到保證;大多數數據庫都支持這種語法。 - 缺點:
SQL
結構復雜,可讀性較差;SQL
語句長度隨數據量增長,可能達到數據庫限制。 - 適用場景:數據量中等偏大(幾千條)、需要保證事務原子性的場景。
使用 ExecutorType.BATCH
public void batchUpdate(List<User> userList) {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
try {
for (User user : userList) {
userMapper.update(user);
}
sqlSession.commit();
} catch (Exception e) {
sqlSession.rollback();
throw e;
} finally {
sqlSession.close();
}
}
- 優點:性能最高,尤其是在處理大量數據時。
- 缺點:代碼復雜度較高,需要手動管理
SqlSession
和事務。 - 適用場景:數據量中等偏大(幾千條)、需要保證事務原子性的場景
總結
方式 | 性能 | 適用場景 | 優點 | 缺點 |
foreach 拼接多個 UPDATE | 中等 | 數據量較小,數據庫支持多語句執行 | 實現簡單,直觀易懂 | 需要數據庫支持多語句,SQL 語句可能過長 |
CASE WHEN 語句 | 較高 | 數據規則,更新字段較少 | 只執行一條 SQL,減少數據庫交互 | SQL 復雜度高,可讀性差 |
ExecutorType.BATCH | 最高 | 數據量大,對性能要求高 | 性能最優 | 代碼復雜度高,需要手動管理事務 |