MybatisPlus
Li Wei
MybatisPlus
Overview
Summary
MyBatis-Plus (opens new window) (abbreviated MP) is an enhancement tool that adds features on top of MyBatis without changing its core. It was created to simplify development and improve efficiency.
Official site:
Features
- Non‑intrusive: only adds enhancements, does not alter existing code, integrates smoothly.
- Low overhead: basic CRUD methods are auto‑injected at startup, with virtually no performance loss; you work directly with objects.
- Powerful CRUD: includes a generic Mapper and Service; with minimal configuration you can handle most single‑table CRUD operations, plus a robust condition builder for various needs.
- Lambda support: use lambda expressions to write query conditions safely, avoiding typo‑prone field names.
- Automatic primary‑key generation: up to four strategies (including a distributed unique ID generator – Sequence) can be configured, solving primary‑key issues effortlessly.
- ActiveRecord mode: entities that extend the
Modelclass gain full CRUD capabilities. - Custom global operations: inject global methods once and use them anywhere.
- Built‑in code generator: generate Mapper, Model, Service, and Controller code via code or Maven plugin; supports template engines and many custom options.
- Built‑in pagination plugin: uses MyBatis physical pagination; after configuring the plugin, pagination queries are written like ordinary
Listqueries. - Multi‑database pagination support: works with MySQL, MariaDB, Oracle, DB2, H2, HSQL, SQLite, PostgreSQL, SQL Server, etc.
- Performance analysis plugin: logs SQL statements and execution times; recommended during development to spot slow queries.
- Global interceptor plugin: intelligently blocks whole‑table
delete/updateoperations and allows custom interception rules to prevent accidental data loss.
Architecture
Quick Start
Add the dependency: MybatisPlus provides a starter that auto‑configures MyBatis and MybatisPlus. Coordinates are:
- Because this starter also auto‑configures MyBatis, it can completely replace the MyBatis starter.
Configuration file: create a Spring Boot configuration file and specify the database URL.
Create the Spring Boot main class
Create entity classes
Create a mapper that extends
BaseMapper: to simplify single‑table CRUD, MybatisPlus offers a basicBaseMapperinterface that already implements CRUD for a single table.Consequently, a custom mapper only needs to extend
BaseMapperand specify the entity class as the generic type; you no longer have to write CRUD methods yourself. Example code:Test: create a test class and write a few unit tests to verify basic CRUD functionality.
You’ll see standard SQL logs printed during execution:
Core Features
Common Annotations
Overview
MybatisPlus infers table information from the PO entity type declared in a Mapper’s generic parameter, then generates the corresponding SQL.
By default:
- The PO class name is converted from camelCase to snake_case to become the table name.
- All PO field names are converted from camelCase to snake_case to become column names, and the field type is used to infer the column type.
- A field named
idis treated as the primary key.
@TableName
Description: table‑name annotation that maps an entity class to a database table.
Target: entity class.
Example:
Besides specifying the table name, @TableName can set many other attributes:
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
value |
String |
No | "" |
Table name |
schema |
String |
No | "" |
Schema name |
keepGlobalPrefix |
boolean |
No | false |
Keep the global tablePrefix when it is active |
resultMap |
String |
No | "" |
ID of the resultMap in XML (for special entity bindings) |
autoResultMap |
boolean |
No | false |
Auto‑generate and use a resultMap (disabled if resultMap is set) |
excludeProperty |
String[] |
No | {} |
Property names to exclude (since 3.3.1) |
@TableId
Description: primary‑key annotation that marks the primary‑key field in an entity.
Target: primary‑key field of an entity class.
Example:
@TableId supports two attributes:
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
value |
String |
No | "" |
Column name |
type |
Enum |
No | IdType.NONE |
Primary‑key generation strategy |
IdType options:
| Value | Description |
|---|---|
AUTO |
Database auto‑increment (e.g., auto_increment) |
NONE |
No strategy; behaves as if not set (falls back to global or INPUT) |
INPUT |
User sets the primary‑key value before insertion |
ASSIGN_ID |
Assign a numeric ID (Long/Integer) or String (since 3.3.0) using the IdentifierGenerator (default is DefaultIdentifierGenerator – Snowflake algorithm) |
ASSIGN_UUID |
Assign a UUID string (since 3.3.0) using IdentifierGenerator.nextUUID |
ID_WORKER |
Distributed global unique long ID (use ASSIGN_ID instead) |
UUID |
32‑character UUID string (use ASSIGN_UUID instead) |
ID_WORKER_STR |
Distributed global unique ID as a string (use ASSIGN_ID instead) |
Commonly used strategies:
AUTO: use the database’s auto‑increment IDINPUT: manually generate the IDASSIGN_ID: generate a globally unique Long using the Snowflake algorithm (the default strategy)
@TableField
Description: ordinary field annotation.
Example:
Usually you don’t need to add @TableField annotation to a field, except in special cases:
- The Java field name differs from the column name.
- The field follows the
isXXXconvention; MybatisPlus strips the leadingis, which may cause mismatches. - The field name collides with a SQL keyword. Use
@TableFieldto add escape characters: ````
Supported additional attributes:
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
value |
String |
No | "" |
Column name |
exist |
boolean |
No | true |
Whether the column exists in the table |
condition |
String |
No | "" |
Custom where clause; if empty, defaults to global %s=#{%s} (see reference) |
update |
String |
No | "" |
Custom SET fragment, e.g., update="%s+1" results in SET version=version+1 (higher priority than el) |
insertStrategy |
Enum |
No | FieldStrategy.DEFAULT |
Example: NOT_NULL → INSERT INTO table_a (column) VALUES (#{columnProperty}) |
updateStrategy |
Enum |
No | FieldStrategy.DEFAULT |
Example: IGNORED → UPDATE table_a SET column=#{columnProperty} |
whereStrategy |
Enum |
No | FieldStrategy.DEFAULT |
Example: NOT_EMPTY → WHERE column=#{columnProperty} |
fill |
Enum |
No | FieldFill.DEFAULT |
Automatic field filling strategy |
select |
boolean |
No | true |
Include in SELECT queries |
keepGlobalFormat |
boolean |
No | false |
Keep global format processing |
jdbcType |
JdbcType |
No | JdbcType.UNDEFINED |
JDBC type (default does not enforce) |
typeHandler |
TypeHandler |
No | — | Custom type handler (default does not enforce) |
numericScale |
String |
No | "" |
Number of digits after the decimal point |
Common Configuration
MybatisPlus also supports custom configuration via a YAML file; see the official docs:
https://www.baomidou.com/pages/56bac0/#%E5%9F%BA%E6%9C%AC%E9%85%8D%E7%BD%AE
Most settings have defaults, so you often don’t need to specify them. Some items must be configured, such as:
- Packages to scan for entity classes
- Global ID type
Note that MybatisPlus also allows handwritten SQL. The location of mapper XML files can be customized:
The default location is classpath*:/mapper/**/*.xml, meaning any mapper.xml placed under that path will be automatically loaded.
Condition Builder
Overview
Beyond inserts, update, delete, and select statements all require WHERE conditions. BaseMapper provides methods that accept complex conditions, not just primary‑key based ones.
Wrapper is the abstract base for condition building. Many concrete implementations inherit from it (see diagram).
AbstractWrapper, a subclass of Wrapper, supplies all the methods for constructing WHERE clauses.
QueryWrapper extends AbstractWrapper with a select method to specify the fields to retrieve.
UpdateWrapper extends AbstractWrapper with a set method to define the SET part of an UPDATE statement.
QueryWrapper
Use QueryWrapper for building conditions for selects, updates, or deletes.
Example – Query: Find people whose name contains “o” and whose balance is ≥ 1000.
QueryWrapper<User> qw = new QueryWrapper<>();
qw.like("name", "o").ge("balance", 1000);
List<User> list = userMapper.selectList(qw);
Example – Update: Set the balance of the user whose username is “jack” to 2000.
UpdateWrapper<User> uw = new UpdateWrapper<>();
uw.eq("username", "jack").set("balance", 2000);
userMapper.update(null, uw);
UpdateWrapper
BaseMapper.update normally only allows direct value assignment, which is insufficient for complex needs.
Example: Decrease the balance of users with IDs 1, 2, 4 by 200.
UpdateWrapper<User> uw = new UpdateWrapper<>();
uw.in("id", 1, 2, 4).setSql("balance = balance - 200");
userMapper.update(null, uw);
LambdaWrapper
Writing field names as strings creates “magic values” that are error‑prone. To avoid hard‑coding field names, you can use method references or lambda expressions, which let MybatisPlus infer the field name via reflection.
MybatisPlus therefore provides Lambda‑based wrappers:
- LambdaQueryWrapper
- LambdaUpdateWrapper
These correspond to QueryWrapper and UpdateWrapper.
Usage example:
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.like(User::getName, "o")
.ge(User::getBalance, 1000);
List<User> list = userMapper.selectList(lqw);
Custom SQL
Overview
In the earlier UpdateWrapper example we wrote the SQL directly in Java. Some companies forbid this practice because SQL should reside in the persistence layer, not the service layer. For an IN condition, you would normally place the SQL in Mapper.xml and use <foreach> to generate dynamic SQL—tedious, especially for complex queries.
MybatisPlus offers a custom‑SQL feature: you can build conditions with a Wrapper, then write the actual SQL in the mapper XML, combining the two approaches.
Basic Usage
Continuing the previous example, you could write:
List<User> list = userMapper.selectList(
new QueryWrapper<User>().in("id", ids)
);
and in UserMapper.xml define a custom statement that uses the generated condition.
Multi‑Table Joins
MybatisPlus does not natively support multi‑table queries, but you can achieve them by combining a custom Wrapper condition with hand‑written SQL.
Scenario: Find users whose shipping address is in Beijing and whose IDs are 1, 2, 4.
A pure MyBatis implementation might look like:
SELECT u.* FROM user u
JOIN address a ON u.id = a.user_id
WHERE a.city = 'Beijing' AND u.id IN (1,2,4);
The hardest part is the WHERE clause. With the custom‑SQL + Wrapper technique, you let the wrapper build the condition and hand‑write the SELECT/FROM part.
Build the condition:
QueryWrapper<User> qw = new QueryWrapper<>();
qw.in("u.id", Arrays.asList(1,2,4))
.eq("a.city", "Beijing");
Mapper method:
List<User> selectUserWithAddress(@Param("ew") Wrapper<User> wrapper);
Corresponding XML:
<select id="selectUserWithAddress" resultType="User">
SELECT u.* FROM user u
JOIN address a ON u.id = a.user_id
<where>
${ew.sqlSegment}
</where>
</select>
IService Interface
Overview
MybatisPlus not only provides BaseMapper but also a generic Service interface and its default implementation, encapsulating common service‑layer patterns. The generic interface is IService; the default implementation is ServiceImpl. The methods can be grouped as:
save: createremove: deleteupdate: updateget: fetch a single resultlist: fetch a collectioncount: countpage: paginate
CRUD
Typical CRUD methods include:
Create:
save– insert a single recordsaveBatch– batch insertsaveOrUpdate– insert or update based on ID existencesaveOrUpdateBatch– batch insert or update
Delete:
removeById– delete by IDremoveByIds– batch delete by IDsremoveByMap– delete by aMapof column/value pairsremove(Wrapper )– delete byWrappercondition~~removeBatchByIds~~– (not supported)
Update:
updateById– update by IDupdate(Wrapper )– update usingUpdateWrapper(containsSETandWHERE)update(T,Wrapper )– update records matching aWrapperwith values from the entityupdateBatchById– batch update by IDs
Get:
getById– fetch one record by IDgetOne(Wrapper )– fetch one record byWrappergetBaseMapper– obtain the underlyingBaseMapperfrom the service (useful for calling custom mapper SQL)
List:
listByIds– batch fetch by IDslist(Wrapper )– fetch multiple records byWrapperconditionlist()– fetch all records
Count:
count()– count all recordscount(Wrapper )– count records matching aWrappercondition
getBaseMapper: When a service needs to call custom SQL defined in its mapper, retrieve the mapper via this method.
Basic Usage
Because services often need business‑specific methods, you typically define your own service interface that extends IService, then implement it by extending ServiceImpl. This way you inherit all generic methods without writing them yourself.
Interface hierarchy:
public interface UserService extends IService<User> {
// custom business methods
}
Implementation:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
// custom method implementations
}
First, define IUserService extending IService:
public interface UserService extends IService<User> {
// additional methods
}
Then create the UserServiceImpl class extending ServiceImpl:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
// custom logic
}
(content truncated)
Originally written by Li Wei (李唯_) and published in Chinese on 后端技术栈全书 (Full-Stack Backend Engineering). Translated and adapted for DriftSeas with permission.