Databases 8 min read

Boost MyBatis Batch Inserts: Why foreach Slows Down and How to Fix It

This article explains why using MyBatis foreach for bulk inserts can cause severe performance degradation, analyzes the underlying parsing and executor issues, and provides practical solutions such as using ExecutorType.BATCH and limiting batch sizes to achieve fast, reliable batch insertion.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Boost MyBatis Batch Inserts: Why foreach Slows Down and How to Fix It

Problem Overview

In a project a long‑running job showed high CPU usage because the bulk insert was implemented with a MyBatis <foreach> loop, generating a single massive INSERT statement. When inserting many columns (20+) and thousands of rows (5000+), the operation took up to 14 minutes.

Traditional vs. Combined INSERT

Typical code executes many separate INSERT statements:

INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2");
...

MySQL documentation suggests combining them into one statement with multiple value tuples:

INSERT INTO `table1` (`field1`, `field2`) VALUES ("data1", "data2"),
                                 ("data1", "data2"),
                                 ("data1", "data2"),
                                 ("data1", "data2");

While this reduces round‑trips, the generated SQL becomes extremely long, causing parsing and placeholder‑mapping overhead.

Why &lt;foreach&gt; Is Not a Real Batch

The <foreach> construct produces a single giant SQL statement, which hits database limits (e.g., ~2000 parameters per statement) and forces MyBatis to re‑parse and re‑map placeholders on every execution. The default ExecutorType.SIMPLE creates a new PreparedStatement for each call, and MyBatis cannot cache statements containing <foreach>.

Recommended Solution

Do not perform the iteration in the MyBatis XML. Instead, use a Java loop with ExecutorType.BATCH so the statement is prepared once and executed repeatedly:

SqlSession session = sessionFactory.openSession(ExecutorType.BATCH);
for (Model model : list) {
    session.insert("insertStatement", model);
}
session.flushStatements();

This approach avoids the huge SQL string and dramatically reduces parsing cost. Experiments show insertion time dropping from minutes to under 2 seconds.

Batch Size Guidance

Even with batch execution, inserting an excessive number of rows in a single batch is unwise. A practical batch size of 20–50 rows per statement balances performance and avoids hitting parameter limits.

Alternative: MyBatis Dynamic SQL Batch Insert

MyBatis’ dynamic‑SQL module provides a dedicated batch‑insert API:

SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
    SimpleTableMapper mapper = session.getMapper(SimpleTableMapper.class);
    List<SimpleTableRecord> records = getRecordsToInsert();
    BatchInsert<SimpleTableRecord> batchInsert = insert(records)
        .into(simpleTable)
        .map(id).toProperty("id")
        .map(firstName).toProperty("firstName")
        .map(lastName).toProperty("lastName")
        .map(birthDate).toProperty("birthDate")
        .map(employed).toProperty("employed")
        .map(occupation).toProperty("occupation")
        .build()
        .render(RenderingStrategy.MYBATIS3);
    batchInsert.insertStatements().forEach(mapper::insert);
    session.commit();
} finally {
    session.close();
}

This method also relies on ExecutorType.BATCH and yields similar performance gains.

Conclusion

For MyBatis bulk inserts, prefer using ExecutorType.BATCH with a reasonable batch size (20‑50 rows) or adopt MyBatis Dynamic SQL’s batch‑insert support. Avoid the <foreach> pattern that creates a single massive INSERT statement, as it leads to heavy parsing overhead and poor performance.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performanceSQLMyBatisBatch InsertExecutorType.BATCH
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.