How to Optimize MyBatis-Plus Batch Insert Performance
This article explains why MyBatis-Plus saveBatch does not perform true batch inserts, shows how to enable JDBC rewriteBatchedStatements, creates a custom SQL injector with InsertBatchSomeColumn, implements batch splitting in a service layer, and presents benchmark results that demonstrate a several‑second speedup for inserting tens of thousands of rows.
When dealing with large tables, the usual approach is to shard tables or increase batch insert speed to improve consumer throughput. The article focuses on how to truly improve batch insert efficiency in MyBatis‑Plus.
The standard MyBatis‑Plus batch method saveBatch() relies on the JDBC URL parameter rewriteBatchedStatements=true to enable real batch mode; without it, the driver splits the batch into individual statements, resulting in low performance.
Source code of saveBatch and the underlying executeBatch shows a loop that inserts each entity and calls sqlSession.flushStatements() after each batch size, which only provides a modest improvement over a plain loop and does not achieve true SQL‑level batch insertion.
MyBatis‑Plus offers a SQL injector that can be extended to add custom methods. The article introduces the built‑in method InsertBatchSomeColumn, which generates a single INSERT statement with multiple value rows, achieving genuine batch insertion.
Custom SQL Injector
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
// Add custom method that skips fields filled only on UPDATE
methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));
return methodList;
}
}The custom injector is registered in the Spring container:
@Configuration
public class MybatisPlusConfig {
@Bean
public MySqlInjector sqlInjector() {
return new MySqlInjector();
}
}A common mapper interface is defined to expose the new method:
public interface CommonMapper<T> extends BaseMapper<T> {
/**
* Real batch insert
*/
int insertBatchSomeColumn(List<T> entityList);
}The specific mapper extends this interface:
@Mapper
public interface UserMapper extends CommonMapper<User> {}Service Layer with Batch Splitting
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
@Transactional(rollbackFor = {Exception.class})
public boolean saveBatch(Collection<User> entityList, int batchSize) {
try {
int size = entityList.size();
int idxLimit = Math.min(batchSize, size);
int i = 1;
List<User> oneBatchList = new ArrayList<>();
for (Iterator<User> it = entityList.iterator(); it.hasNext(); ++i) {
User element = it.next();
oneBatchList.add(element);
if (i == idxLimit) {
baseMapper.insertBatchSomeColumn(oneBatchList);
oneBatchList.clear();
idxLimit = Math.min(idxLimit + batchSize, size);
}
}
} catch (Exception e) {
log.error("saveBatch fail", e);
return false;
}
return true;
}
}Test code measures execution time for the original saveBatch (which does not perform true batch) and the custom insertBatchSomeColumn (true batch) on a table containing over one million rows, with the JDBC driver configured to rewrite batched statements.
// Original fake batch insert test
for (int i = 0; i < 50000; i++) {
User user = new User();
user.setAge(10);
user.setUsername("zhmsky");
user.setEmail("[email protected]");
userList.add(user);
}
long start = System.currentTimeMillis();
userService.saveBatch(userList, 1000);
long end = System.currentTimeMillis();
System.out.println("Original saveBatch time: " + (end - start));
// Custom true batch insert test
for (int i = 0; i < 50000; i++) {
User user = new User();
user.setAge(10);
user.setUsername("zhmsky");
user.setEmail("[email protected]");
userList.add(user);
}
start = System.currentTimeMillis();
userService.saveBatch(userList, 1000); // internally calls insertBatchSomeColumn
end = System.currentTimeMillis();
System.out.println("Custom insertBatchSomeColumn time: " + (end - start));Benchmark results show that the custom true batch insertion reduces the total time by about three seconds for 50 000 records, and inserting 1 500 records takes roughly 650 ms, which is considered fast.
In summary, by enabling rewriteBatchedStatements, adding a custom SQL injector method that generates a single multi‑value INSERT, and implementing batch splitting in the service layer, developers can achieve significant performance gains for large‑scale data insertion in MyBatis‑Plus.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
IT Niuke
Focused on IT technology sharing, original and innovative content. IT Niuke, we grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
