Hard‑Learned MyBatis‑Plus Pitfalls and How to Avoid Them in Production
The article reviews the most common MyBatis‑Plus traps encountered in large‑scale Java services—duplicate Snowflake IDs, batch‑insert reordering, enum storage mismatches, camel‑case mapping errors, auto‑fill loss, and JSON field disappearance—explaining each root cause, illustrating it with real code snippets, and providing concrete solutions to keep production systems stable.
MyBatis‑Plus was created to give Java developers a production‑ready ORM that simplifies CRUD, adds features such as logical delete, auto‑fill, optimistic lock, multi‑tenant support, and built‑in pagination. Although the framework’s promises are attractive, the author’s years of production experience reveal a series of hidden pitfalls that only surface under high‑concurrency, containerized, or distributed environments.
1. Snowflake ID duplication
Problem
Duplicate primary‑key errors appear at 3 a.m. when two container instances generate the same Snowflake ID, despite the expectation that IDs are globally unique.
Root causes
Worker ID not configured, so every instance falls back to the default value.
Docker containers share the same MAC‑address prefix; extracting the last two bytes and taking mod 32 yields identical worker IDs after 32 instances.
Kubernetes pods receive virtual MAC addresses from CNI plugins, often identical across pods, causing the same worker ID.
Clock rollback makes the timestamp component repeat, completing the collision when other fields also match.
Solutions
Use an external distributed ID service (e.g., Zookeeper‑based) to guarantee unique worker IDs.
Switch to database auto‑increment IDs when a global unique key is required.
2. Batch insert disorder
Problem phenomenon
When inserting parent‑child orders in bulk, foreign‑key constraints fail because child rows are persisted before their parents. Remote‑ordered lists stored in the database also lose their original order.
MyBatis‑Plus batch modes
Default BATCH mode : receives a list, creates a ExecutorType.BATCH session, and adds each INSERT statement to the batch one by one. It supports primary‑key back‑fill and works in single‑threaded order, but multithreaded execution can scramble order and incurs many round‑trips.
Optimized batch mode : concatenates all values into a single multi‑value INSERT. It is fast (single round‑trip) and order‑stable, but does not support primary‑key back‑fill and requires manual primary‑key handling.
Underlying reasons for disorder
MySQL JDBC driver’s rewriteBatchedStatements merges statements, changing execution order.
The optimizer groups statements by table, further reordering inserts.
Solutions
Split data into smaller batches and call sqlSession.flushStatements() after each batch to force ordering.
Write custom @Insert SQL that inserts the whole list in the desired sequence.
3. Enum field storage risk
Problem
Enum fields are stored as their name() (e.g., "PENDING") instead of the intended numeric code, causing mismatches with existing data.
Solution
Annotate the numeric field with @EnumValue so MyBatis‑Plus persists the code value.
4. Camel‑case conversion misalignment
Problem
Fields such as buyerUID or XMLData are converted to buyer_u_i_d and x_m_l_data, breaking column mapping.
Solutions
Use @TableField("buyer_uid") to explicitly map the column.
Adopt a naming convention that avoids consecutive uppercase letters (e.g., buyerUid, xmlData).
Provide a custom naming strategy via GlobalConfig.DbConfig to control column formatting.
5. Auto‑fill loss during batch operations
Problem
Fields annotated with @TableField(fill = FieldFill.INSERT) or INSERT_UPDATE remain null when saveBatch is used.
Reason
Batch execution bypasses the MetaObjectHandler that normally populates these fields, because the framework directly executes raw JDBC batch statements.
Solutions
Manually invoke the handler before the batch:
entityList.forEach(e -> {
MetaObject mo = SystemMetaObject.forObject(e);
metaObjectHandler.insertFill(mo);
});
service.saveBatch(entityList);Implement a custom batch‑save method that reflects over the entity, sets createTime, updateTime, and createBy, then calls the mapper’s insertBatch.
6. JSON field loss or format error
Problem
Complex fields such as Map<String,Object> or List<String> are stored as toString() output (e.g., {key=value}), which is not valid JSON, and are read back as null.
Solutions
Prefer primitive column types; move complex structures to separate tables.
If a JSON column is unavoidable, serialize the object to a JSON string before persisting and deserialize after retrieval.
Conclusion
The cases demonstrate that MyBatis‑Plus defaults assume a simple, single‑node environment. In production—especially with containers, Kubernetes, or cloud‑native deployments—those defaults become hidden hazards. Understanding the framework’s internal mechanisms, configuring worker IDs, choosing the appropriate batch mode, explicitly mapping columns, and handling complex types are essential steps to keep a Java service reliable.
About the author: Zhu Hongxu, Java engineer at XianKeHui.
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.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
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.
