Advanced MyBatis-Plus Features: Batch Insert, Logical Delete, Auto Field Fill, Type Handlers, Dynamic Table Names, Multi-Tenant
The article demonstrates advanced MyBatis‑Plus techniques—including high‑speed batch inserts with rewriteBatchedStatements, logical deletes via global config, automatic timestamp and user fields through MetaObjectHandler, JSON serialization with JacksonTypeHandler, dynamic table naming via an interceptor, and a brief multi‑tenant overview—to streamline development and reduce boilerplate code.
Continuation of a MyBatis-Plus introductory tutorial, showing how to create the tb_user table and the corresponding User entity.
CREATE TABLE `tb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_no` varchar(255) NOT NULL COMMENT '编号',
`nickname` varchar(255) DEFAULT NULL COMMENT '昵称',
`email` varchar(255) DEFAULT NULL COMMENT '邮箱',
`phone` varchar(255) NOT NULL COMMENT '手机号',
`gender` tinyint(4) NOT NULL DEFAULT '0' COMMENT '性别 0:男生 1:女生',
`birthday` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '出生日期',
`is_delete` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标志 0:否 1:是',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`create_by` bigint(20) DEFAULT NULL COMMENT '创建人',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新人',
`address` varchar(1024) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;Entity class definition with Lombok annotations and @TableName .
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "tb_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userNo;
private String nickname;
private String email;
private String phone;
private Integer gender;
private Date birthday;
private Integer isDelete;
private Date createTime;
private Date updateTime;
private Long createBy;
private Long updateBy;
}1. Batch Insert – performance comparison between mapper‑level loop insert and service‑level saveBatch . Without JDBC rewriteBatchedStatements the mapper takes ~42 s, service ~19 s; after adding rewriteBatchedStatements=true the service batch completes in ~1.3 s.
@Test
public void testMapperBatchAdd() {
List
users = new ArrayList<>();
for (long i = 1; i <= 1000; i++) {
User user = User.builder()
.id(i)
.userNo("No-" + i)
.nickname("哈哈")
.phone("12345678901")
.email("[email protected]")
.birthday(new Date())
.gender(0)
.isDelete(0)
.build();
users.add(user);
}
long start = System.currentTimeMillis();
users.forEach(userDAO::insert);
long end = System.currentTimeMillis();
System.out.println("执行时长:" + (end - start) + "毫秒");
} @Test
public void testServiceBatchAdd() {
List
users = new ArrayList<>();
for (long i = 1; i <= 1000; i++) {
User user = User.builder()
.id(i)
.userNo("No-" + i)
.nickname("哈哈")
.phone("12345678901")
.email("[email protected]")
.birthday(new Date())
.gender(0)
.isDelete(0)
.build();
users.add(user);
}
long start = System.currentTimeMillis();
userService.saveBatch(users);
long end = System.currentTimeMillis();
System.out.println("执行时长:" + (end - start) + "毫秒");
}JDBC URL must contain rewriteBatchedStatements=true for high‑performance batch inserts.
2. Logical Delete – global configuration and example of a logical delete operation.
mybatis-plus:
global-config:
db-config:
logic-delete-field: isDelete # field name
logic-delete-value: 1 # deleted flag
logic-not-delete-value: 0 # not deleted flag @Test
public void testLogicDelete() {
userDAO.deleteById(1L);
}Query automatically adds is_delete=0 condition.
@Test
public void testQuery() {
User user = userDAO.selectById(1L);
}3. Default Field Fill – BaseDO with createTime , updateTime , createBy , updateBy and a MetaObjectHandler implementation that populates these fields automatically.
@Data
public class BaseDO implements Serializable {
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
} public class DefaultDBFieldHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
if (metaObject.getOriginalObject() instanceof BaseDO) {
BaseDO base = (BaseDO) metaObject.getOriginalObject();
Date now = new Date();
if (base.getCreateTime() == null) base.setCreateTime(now);
if (base.getUpdateTime() == null) base.setUpdateTime(now);
base.setCreateBy(1001L);
base.setUpdateBy(1002L);
}
}
@Override
public void updateFill(MetaObject metaObject) {
Object modifyTime = getFieldValByName("updateTime", metaObject);
if (modifyTime == null) {
setFieldValByName("updateTime", new Date(), metaObject);
}
Object modifier = getFieldValByName("updateBy", metaObject);
if (modifier == null) {
setFieldValByName("updateBy", 1002L, metaObject);
}
}
}4. Field Type Handling – storing a complex JSON object ( Address ) in a varchar column using JacksonTypeHandler .
@Data
public class Address {
private Long id;
private String province;
private String city;
private String region;
private String address;
} @TableField(typeHandler = JacksonTypeHandler.class)
private Address address;Insert and query examples demonstrate automatic JSON serialization/deserialization.
@Test
public void testTypeHandler() {
Address address = new Address();
address.setId(1L);
address.setProvince("浙江省");
address.setCity("杭州市");
address.setRegion("余杭区");
address.setAddress("城北万象城");
User user = User.builder()
.id(100L)
.userNo("No-001")
.nickname("哈哈")
.phone("12345678901")
.email("[email protected]")
.birthday(new Date())
.gender(0)
.isDelete(0)
.address(address)
.build();
userDAO.insert(user);
}5. Dynamic Table Name – MyBatis‑Plus interceptor configuration and a TableNameHandler that selects a table suffix based on a request parameter.
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());
return interceptor;
}
@Bean
public DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
DynamicTableNameInnerInterceptor inner = new DynamicTableNameInnerInterceptor();
inner.setTableNameHandler(new MyTableNameHandler());
return inner;
} public class MyTableNameHandler implements TableNameHandler {
@Override
public String dynamicTableName(String sql, String tableName) {
Map
paramMap = RequestDataHelper.getRequestData();
if (paramMap == null || paramMap.isEmpty()) {
return tableName;
}
int random = (int) paramMap.get("tableNo");
String suffix = (random % 2 == 1) ? "_2" : "_1";
return tableName + suffix;
}
}6. Multi‑Tenant – reference to a previous article on SaaS multi‑tenant data isolation.
7. Summary – the tutorial covers practical advanced features of MyBatis‑Plus that improve development efficiency and reduce repetitive code.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
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.