Unlock MyBatis-Plus: From Zero-Code CRUD to Advanced Plugins

This article introduces MyBatis-Plus, explains its core zero‑injection CRUD capabilities, demonstrates powerful condition builders, plugin extensions, and best practices for secure, efficient data access, including custom query objects, micro‑service integration, and advanced mapper customization, helping developers boost backend productivity.

Zhuanzhuan Tech
Zhuanzhuan Tech
Zhuanzhuan Tech
Unlock MyBatis-Plus: From Zero-Code CRUD to Advanced Plugins

1. MyBatis-Plus Overview: More Than an Enhancement

MyBatis-Plus can be understood as "MyBatis + Swiss‑army‑knife skin + delete‑protection", retaining MyBatis native features while providing zero‑invasion single‑table operations.

Swiss‑army‑knife convenience : Upgrade XML configuration to Lambda expressions, achieving complex operations with minimal code.

Insurance‑level safety : Interceptors make dangerous operations like "delete from table" impossible, preventing data loss.

Zero‑invasion compatibility : No changes to existing MyBatis code, allowing smooth migration of legacy projects.

2. Core Features: A Full‑Featured Toolbox for Single‑Table Operations

All functions are exposed through the BaseMapper interface, eliminating the need for manual implementation.

2.1 Basic CRUD: Zero‑Code Single‑Table Operations

BaseMapper encapsulates all basic operations; key methods include:

int insert(T entity)               // Insert a record, return affected rows
boolean insertOrUpdate(T entity)   // Insert or update based on primary key
int deleteById(Serializable id) // Delete by primary key
int deleteByIds(Collection<?> idList) // Batch delete by primary keys
int updateById(T entity)          // Update by primary key
T selectById(Serializable id)    // Query a single record by primary key
List<T> selectList(Wrapper<T> queryWrapper) // Conditional batch query

2.2 Condition Builder: Elegant Complex Queries

The condition builder is the "soul" of MyBatis‑Plus, supporting object‑oriented SQL construction and avoiding string concatenation pitfalls.

QueryWrapper : Basic builder using string field names, e.g., eq("name", "张三").

LambdaQueryWrapper : Lambda‑based builder, e.g., eq(User::getName, "张三"), recommended for primary use.

UpdateWrapper / LambdaUpdateWrapper : Build update conditions with dynamic set values.

Why prefer LambdaQueryWrapper?

// ❌ Not recommended: field typo not caught at compile time
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "张三").gt("age", 18);

// ✅ Recommended: compile‑time field validation and automatic refactoring
LambdaQueryWrapper<User> lambda = new LambdaQueryWrapper<>();
lambda.eq(User::getName, "张三").gt(User::getAge, 18);

2.3 Rich Plugin Collection: Universal Extension Interface

Plugins are implemented via MybatisPlusInterceptor. Core plugins include: PaginationInnerInterceptor: Automatic pagination (must be configured to avoid in‑memory pagination). BlockAttackInnerInterceptor: Prevent full‑table update/delete without WHERE clause. OptimisticLockerInnerInterceptor: Optimistic locking using version fields. TenantLineInnerInterceptor: Multi‑tenant data isolation. IllegalSQLInnerInterceptor: Enforce SQL performance standards (e.g., block SELECT *).

3. Usage Recommendations: Write Efficient and Safe MP Code

3.1 Prefer Lambda Condition Builder

LambdaQueryWrapper catches missing fields and type mismatches at compile time, providing automatic error correction.

3.2 Null‑Value Handling in Condition Builder

MP supports conditional addition, reducing redundant if statements.

// ❌ Not recommended: many if checks
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(name)) {
    wrapper.eq(User::getName, name);
}
if (age != null) {
    wrapper.eq(User::getAge, age);
}

// ✅ Recommended: conditional addition
wrapper.eq(StringUtils.isNotBlank(name), User::getName, name)
       .eq(Objects.nonNull(age), User::getAge, age);

3.3 Explicit Select Fields to Improve Query Efficiency

Specifying fields enables index covering, reduces data transfer, and avoids full‑table scans.

// ❌ Not recommended: select all fields
List<User> users1 = userMapper.selectList(lambda);

// ✅ Recommended: select only needed fields
lambda.select(User::getId, User::getName, User::getAge);
List<User> users2 = userMapper.selectList(lambda);

4. Practical Development: From “Usable” to “Well‑Used”

4.1 Basic Capability: Query Condition Packaging in Micro‑services

When exposing DAO as an independent service, QueryWrapper has three pain points: complex serialization, heavy dependency, and version‑lock upgrades. The solution is a lightweight QueryCondition object. QueryCondition aggregates query fields, order fields, select fields, and pagination parameters.

public class QueryCondition {
    private List<String> selectFieldList; // Return fields
    private List<QueryField> queryFieldList; // Query conditions
    private List<OrderField> orderFieldList; // Order fields
    private int pageNum; // Page number
    private int pageSize; // Page size
}

4.2 Fast Integration: Three Steps to Implement Single‑Table CRUD Interface

1. Define entity class. 2. Create contract interface extending BaseDao. 3. Implement service by extending AbstractBaseDaoImpl, which inherits MyBatis‑Plus ServiceImpl.

// Entity definition
public class AssOrderEntity {
    private Long id; // Order ID
    // other fields...
}

// Contract interface
public interface IAssOrderDao extends BaseDao<Long, AssOrderEntity> {}

// Service implementation
public class AssOrderDao extends AbstractBaseDaoImpl<Long, AssOrderEntity, AssOrderMapper>
        implements IAssOrderDao {}

4.3 Extending BaseMapper with Custom Methods

Define a custom mapper interface, inject the method via AbstractMethod and SqlInjector, then use it.

public interface MyMapper<T> extends BaseMapper<T> {
    int countByEntity(T entity);
}

public class CountByEntityMethod extends AbstractMethod {
    private static final String SQL_TEMPLATE = "<script>%s SELECT COUNT(%s) FROM %s %s %s
</script>";
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql = String.format(SQL_TEMPLATE, sqlFirst(), selectColumns(tableInfo, true),
                tableInfo.getTableName(), sqlWhereEntityWrapper(true, tableInfo), sqlComment());
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return addSelectMappedStatementForOther(mapperClass, "countByEntity", sqlSource, Integer.class);
    }
}

public class MySqlInjector extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        List<AbstractMethod> methods = super.getMethodList(mapperClass, tableInfo);
        methods.add(new CountByEntityMethod());
        return methods;
    }
}

4.4 Built‑in Plugins for System Security

FullTableScanInterceptor : Blocks SQL without WHERE conditions to prevent full‑table scans.

BlockFullTableOperationInterceptor : Blocks update/delete statements without WHERE clauses, avoiding accidental full‑table modifications.

5. Summary: Why MyBatis‑Plus Is Worth Using

MyBatis‑Plus dramatically reduces CRUD code (about 90% less), provides compile‑time safety with Lambda expressions, enhances system security through plugins, and lowers the learning curve for teams, resulting in higher development efficiency and fewer production incidents.

JavaORMMyBatis-PlusCRUD
Zhuanzhuan Tech
Written by

Zhuanzhuan Tech

A platform for Zhuanzhuan R&D and industry peers to learn and exchange technology, regularly sharing frontline experience and cutting‑edge topics. We welcome practical discussions and sharing; contact waterystone with any questions.

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.