Master MyBatis-Plus Advanced Features: Batch Insert, Logical Delete, Auto Fill, Type Handler, Dynamic Tables and More

This tutorial walks through MyBatis-Plus's advanced capabilities—including high‑performance batch inserts, logical deletion with global config, automatic timestamp and user fields via MetaObjectHandler, JSON type handling, dynamic table name resolution, and multi‑tenant support—showing concrete code, configuration, and benchmark results for each feature.

Shepherd Advanced Notes
Shepherd Advanced Notes
Shepherd Advanced Notes
Master MyBatis-Plus Advanced Features: Batch Insert, Logical Delete, Auto Fill, Type Handler, Dynamic Tables and More

First, a tb_user table is created with typical columns (id, user_no, nickname, email, phone, gender, birthday, is_delete, create_time, update_time, create_by, update_by, address) and a corresponding User entity annotated with @TableName("tb_user").

1. Batch Insert

The article compares two approaches for inserting 1,000 User records. The mapper‑level test inserts each record individually, taking 42516 ms. The service‑level saveBatch method reduces the time to 19385 ms, but still remains slow for a service response.

@Test
public void testMapperBatchAdd() {
    List<User> 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(user -> userDAO.insert(user));
    long end = System.currentTimeMillis();
    System.out.println("执行时长:" + (end - start) + "毫秒");
}

Adding rewriteBatchedStatements=true to the MySQL JDBC URL (and using driver ≥ 5.1.13) dramatically improves performance; the same service‑level batch now finishes in 1364 ms.

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://ip:3306/db_test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF8&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true

2. Logical Delete

Logical deletion is enabled via global configuration:

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: isDelete
      logic-delete-value: 1
      logic-not-delete-value: 0

Calling userDAO.deleteById(1L) generates an UPDATE tb_user SET is_delete=1 WHERE id=? AND is_delete=0 statement, as shown in the debug log. Queries automatically add is_delete=0 to filter out logically deleted rows.

When a column participates in a unique index, logical deletion can cause conflicts. Adding a delete_time column to the unique index resolves this, as demonstrated by the two scenarios before and after the change.

3. Automatic Field Filling

A base class BaseDO defines common audit fields with @TableField(fill = FieldFill.INSERT) or INSERT_UPDATE. A custom MetaObjectHandler implementation populates these fields when they are null.

public class DefaultDBFieldHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        if (metaObject.getOriginalObject() instanceof BaseDO) {
            BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
            Date current = new Date();
            if (Objects.isNull(baseDO.getCreateTime())) {
                baseDO.setCreateTime(current);
            }
            if (Objects.isNull(baseDO.getUpdateTime())) {
                baseDO.setUpdateTime(current);
            }
            baseDO.setCreateBy(1001L);
            baseDO.setUpdateBy(1002L);
        }
    }
    @Override
    public void updateFill(MetaObject metaObject) {
        Object modifyTime = getFieldValByName("updateTime", metaObject);
        if (Objects.isNull(modifyTime)) {
            setFieldValByName("updateTime", new Date(), metaObject);
        }
        Object modifier = getFieldValByName("updateBy", metaObject);
        if (Objects.isNull(modifier)) {
            setFieldValByName("updateBy", 1002L, metaObject);
        }
    }
}

The handler is registered as a Spring bean:

@Bean
public MetaObjectHandler defaultMetaObjectHandler() {
    return new DefaultDBFieldHandler();
}

Inserting a User without explicitly setting audit fields results in the generated SQL containing populated create_time, update_time, create_by, and update_by values.

4. Complex Field Type Handling

For JSON or array columns, a custom Address class is defined and stored as a JSON string using JacksonTypeHandler. The entity must enable autoResultMap = true to map the JSON automatically.

@Data
public class Address {
    private Long id;
    private String province;
    private String city;
    private String region;
    private String address;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "tb_user", autoResultMap = true)
public class User extends BaseDO {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String userNo;
    private String nickname;
    private String email;
    private String phone;
    private Integer gender;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date birthday;
    private Integer isDelete;
    @TableField(typeHandler = JacksonTypeHandler.class)
    private Address address;
}

Inserting a user with an Address object produces a JSON string in the address column, and a subsequent query returns a fully populated Address instance.

5. Dynamic Table Name

The dynamic‑table‑name plugin is configured via a MybatisPlusInterceptor and a custom TableNameHandler. The handler reads a request‑scoped tableNo parameter and appends _1 or _2 to the original table name.

@Bean
public MybatisPlusInterceptor paginationInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());
    return interceptor;
}

@Bean
public DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {
    DynamicTableNameInnerInterceptor interceptor = new DynamicTableNameInnerInterceptor();
    interceptor.setTableNameHandler(new MyTableNameHandler());
    return interceptor;
}
public class MyTableNameHandler implements TableNameHandler {
    @Override
    public String dynamicTableName(String sql, String tableName) {
        Map<String, Object> 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;
    }
}

The helper class stores request parameters in a ThreadLocal map, making them accessible to the handler.

6. Multi‑Tenant Plugin

MyBatis‑Plus also offers a multi‑tenant interceptor; the article references a previous analysis for detailed usage.

7. Summary

All the demonstrated features—batch operations, logical deletion, automatic field filling, JSON type handling, dynamic table resolution, and multi‑tenant support—are practical extensions that improve code efficiency, reduce boilerplate, and address common enterprise scenarios when using MyBatis‑Plus in backend development.

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.

JavaSpring BootORMMyBatis-PlusBatch InsertDynamic TableLogical DeleteAuto Fill
Shepherd Advanced Notes
Written by

Shepherd Advanced Notes

Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.

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.