MyBatis-Plus 开发指北
📖 内容概览
分享MyBatis-Plus的使用经验,涵盖从基础配置到高级特性的完整知识体系。通过实际业务场景(电商、仓储、财务)的示例,帮助开发者快速掌握MyBatis-Plus的核心功能。
🚀 快速开始
第一步:添加依赖
在 pom.xml 中添加MyBatis-Plus依赖:
xml
<dependencies>
<!-- MyBatis-Plus 核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
第二步:配置数据源
在 application.yml 中配置数据库连接:
yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
# MyBatis-Plus 配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
mapper-locations: classpath*:/mapper/**/*.xml
第三步:创建实体类
创建用户实体类:
java
@Data
@TableName("sys_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("user_name")
private String username;
private String email;
private Integer age;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
private Integer deleted;
}
第四步:创建Mapper接口
创建用户Mapper接口:
java
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 继承BaseMapper后自动拥有CRUD方法
// 可以添加自定义方法
List<User> selectUsersByAge(@Param("minAge") Integer minAge, @Param("maxAge") Integer maxAge);
}
第五步:创建Service层
创建用户Service:
java
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
// 继承ServiceImpl后自动拥有CRUD方法
public List<User> getUsersByAgeRange(Integer minAge, Integer maxAge) {
return baseMapper.selectUsersByAge(minAge, maxAge);
}
public Page<User> getUsersWithPagination(Page<User> page, String keyword) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(keyword)) {
wrapper.like(User::getUsername, keyword)
.or()
.like(User::getEmail, keyword);
}
return page(page, wrapper);
}
}
第六步:创建Controller
创建用户Controller:
java
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getById(id);
}
@PostMapping
public boolean createUser(@RequestBody User user) {
return userService.save(user);
}
@PutMapping("/{id}")
public boolean updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return userService.updateById(user);
}
@DeleteMapping("/{id}")
public boolean deleteUser(@PathVariable Long id) {
return userService.removeById(id);
}
@GetMapping
public Page<User> getUsers(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword) {
Page<User> page = new Page<>(current, size);
return userService.getUsersWithPagination(page, keyword);
}
}
第七步:测试接口
启动应用并测试:
bash
# 创建用户
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{
"username": "张三",
"email": "zhangsan@example.com",
"age": 25
}'
# 获取用户列表
curl http://localhost:8080/api/users?current=1&size=10&keyword=张
# 更新用户
curl -X PUT http://localhost:8080/api/users/1 \
-H "Content-Type: application/json" \
-d '{
"username": "张三丰",
"email": "zhangsanfeng@example.com",
"age": 30
}'
# 删除用户
curl -X DELETE http://localhost:8080/api/users/1
完成! 现在您已经成功使用MyBatis-Plus进行数据库操作。接下来可以继续阅读详细的技术内容。
1. MyBatis-Plus 简介
1.1 什么是MyBatis-Plus
MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。
1.2 核心特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
- 损耗小:启动即会自动注入基本CRUD,性能基本无损耗,直接面向对象操作
- 强大的CRUD操作:内置通用Mapper、通用Service,仅仅通过少量配置即可实现单表大部分CRUD操作
- 支持Lambda形式调用:通过Lambda表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达4种主键策略(包含分布式唯一ID生成器-Sequence),可自由配置
- 支持ActiveRecord模式:支持ActiveRecord形式调用,实体类只需继承Model类即可进行强大的CRUD操作
- 支持自定义全局通用操作:支持全局通用方法注入(Write once, use anywhere)
- 内置代码生成器:采用代码或者Maven插件可快速生成Mapper、Model、Service、Controller层代码
- 内置分页插件:基于MyBatis物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询
- 分页插件支持多种数据库:支持MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer等多种数据库
- 内置性能分析插件:可输出SQL语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表delete、update操作智能分析阻断,也可自定义拦截规则,预防误操作
1.3 适用场景
- 快速开发:新项目快速搭建,减少重复代码编写
- CRUD操作:单表增删改查操作频繁的业务场景
- 分页查询:需要分页展示数据的场景
- 条件查询:复杂的动态条件查询
- 批量操作:批量插入、更新、删除操作
2. 核心注解详解
2.1 实体类注解
@TableName
指定实体类对应的数据库表名。
java
@TableName("sys_user")
public class User {
// 实体类字段
}
参数说明:
value:数据库表名schema:数据库schemaresultMap:XML中resultMap的id
@TableId
指定主键字段。
java
@TableId(type = IdType.AUTO)
private Long id;
主键策略:
AUTO:数据库自增NONE:无状态,需要手动设置INPUT:手动输入ASSIGN_ID:分配ID(雪花算法)ASSIGN_UUID:分配UUID
@TableField
指定字段映射。
java
@TableField("user_name")
private String username;
@TableField(exist = false)
private String tempField;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
参数说明:
value:数据库字段名exist:是否为数据库表字段fill:字段自动填充策略select:是否查询该字段update:是否更新该字段
@TableLogic
逻辑删除字段。
java
@TableLogic
private Integer deleted;
配置示例:
yaml
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
2.2 条件构造器注解
@SqlParser
SQL解析注解,用于过滤SQL。
java
@SqlParser(filter = true)
public class UserMapper extends BaseMapper<User> {
// 自定义SQL方法
}
3. 核心接口与类
3.1 BaseMapper接口
MyBatis-Plus提供的通用Mapper接口,包含基本的CRUD操作。
java
public interface BaseMapper<T> extends Mapper<T> {
// 插入
int insert(T entity);
// 根据ID删除
int deleteById(Serializable id);
// 根据条件删除
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据ID更新
int updateById(@Param(Constants.ENTITY) T entity);
// 根据条件更新
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
// 根据ID查询
T selectById(Serializable id);
// 根据条件查询单个
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据条件查询列表
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据条件查询分页
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据条件查询总数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
3.2 IService接口
通用Service接口,提供业务层常用方法。
java
public interface IService<T> {
// 保存
boolean save(T entity);
// 批量保存
boolean saveBatch(Collection<T> entityList);
// 根据ID删除
boolean removeById(Serializable id);
// 根据条件删除
boolean remove(Wrapper<T> queryWrapper);
// 根据ID更新
boolean updateById(T entity);
// 根据条件更新
boolean update(Wrapper<T> updateWrapper);
// 根据ID查询
T getById(Serializable id);
// 根据条件查询单个
T getOne(Wrapper<T> queryWrapper);
// 根据条件查询列表
List<T> list(Wrapper<T> queryWrapper);
// 分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 根据条件查询总数
int count(Wrapper<T> queryWrapper);
}
3.3 ServiceImpl类
IService接口的实现类。
java
@Service
public class UserService extends ServiceImpl<UserMapper, User> implements IUserService {
// 可以添加自定义业务方法
}
4. 条件构造器
4.1 QueryWrapper
查询条件构造器,用于构建WHERE条件。
java
// 基础查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", "张三")
.gt("age", 18)
.like("email", "@example.com")
.orderByDesc("create_time");
List<User> users = userMapper.selectList(wrapper);
4.2 LambdaQueryWrapper
Lambda表达式查询条件构造器,类型安全。
java
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, "张三")
.gt(User::getAge, 18)
.like(User::getEmail, "@example.com")
.orderByDesc(User::getCreateTime);
List<User> users = userMapper.selectList(wrapper);
4.3 UpdateWrapper
更新条件构造器,用于构建UPDATE语句。
java
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("username", "张三")
.set("age", 25)
.set("email", "newemail@example.com");
userMapper.update(null, wrapper);
4.4 LambdaUpdateWrapper
Lambda表达式更新条件构造器。
java
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getUsername, "张三")
.set(User::getAge, 25)
.set(User::getEmail, "newemail@example.com");
userMapper.update(null, wrapper);
4.5 常用条件方法
比较条件
java
// 等于
wrapper.eq("age", 25);
// 不等于
wrapper.ne("age", 25);
// 大于
wrapper.gt("age", 18);
// 大于等于
wrapper.ge("age", 18);
// 小于
wrapper.lt("age", 65);
// 小于等于
wrapper.le("age", 65);
模糊查询
java
// LIKE '%value%'
wrapper.like("username", "张");
// LIKE 'value%'
wrapper.likeRight("username", "张");
// LIKE '%value'
wrapper.likeLeft("username", "三");
// NOT LIKE '%value%'
wrapper.notLike("username", "张");
范围查询
java
// IN
wrapper.in("age", Arrays.asList(18, 19, 20));
// NOT IN
wrapper.notIn("age", Arrays.asList(18, 19, 20));
// BETWEEN
wrapper.between("age", 18, 65);
// NOT BETWEEN
wrapper.notBetween("age", 18, 65);
空值判断
java
// IS NULL
wrapper.isNull("email");
// IS NOT NULL
wrapper.isNotNull("email");
排序
java
// 升序
wrapper.orderByAsc("age");
// 降序
wrapper.orderByDesc("create_time");
// 多字段排序
wrapper.orderByAsc("age", "create_time");
分组
java
// GROUP BY
wrapper.groupBy("age");
// HAVING
wrapper.having("COUNT(*) > 1");
5. 分页插件
5.1 配置分页插件
java
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setDbType(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(1000L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
5.2 使用分页查询
java
// 创建分页对象
Page<User> page = new Page<>(1, 10);
// 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getUsername, "张");
// 执行分页查询
Page<User> result = userMapper.selectPage(page, wrapper);
// 获取结果
List<User> records = result.getRecords();
long total = result.getTotal();
long current = result.getCurrent();
long size = result.getSize();
5.3 自定义分页查询
java
// 在Mapper中定义自定义分页方法
@Mapper
public interface UserMapper extends BaseMapper<User> {
IPage<User> selectUsersWithRole(IPage<User> page, @Param("roleId") Long roleId);
}
// 在XML中实现
<select id="selectUsersWithRole" resultType="com.example.entity.User">
SELECT u.*, r.role_name
FROM sys_user u
LEFT JOIN sys_user_role ur ON u.id = ur.user_id
LEFT JOIN sys_role r ON ur.role_id = r.id
WHERE r.id = #{roleId}
</select>
6. 代码生成器
6.1 配置代码生成器
java
@Component
public class CodeGenerator {
public void generateCode() {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("Your Name");
gc.setOpen(false);
gc.setFileOverride(true);
gc.setServiceName("%sService");
gc.setMapperName("%sMapper");
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("sys");
pc.setParent("com.example");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude("sys_user", "sys_role", "sys_menu");
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("sys_");
mpg.setStrategy(strategy);
// 执行生成
mpg.execute();
}
}
6.2 使用Maven插件生成
xml
<plugin>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3.1</version>
</plugin>
7. 自动填充
7.1 配置自动填充处理器
java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUserId());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUserId());
}
private String getCurrentUserId() {
// 获取当前用户ID的逻辑
return "admin";
}
}
7.2 在实体类中使用自动填充
java
@Data
@TableName("sys_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private String createBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
}
8. 性能分析插件
8.1 配置性能分析插件
java
@Configuration
public class MybatisPlusConfig {
@Bean
@Profile({"dev", "test"}) // 只在开发环境和测试环境启用
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor interceptor = new PerformanceInterceptor();
interceptor.setMaxTime(1000); // 设置SQL执行的最大时间,单位:ms
interceptor.setFormat(true); // 是否格式化SQL
return interceptor;
}
}
8.2 配置SQL日志输出
yaml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
9. 多数据源配置
9.1 配置多数据源
java
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryDataSourceConfig {
@Bean(name = "primaryDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "primarySqlSessionFactory")
@Primary
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/primary/*.xml"));
return bean.getObject();
}
@Bean(name = "primarySqlSessionTemplate")
@Primary
public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
9.2 使用多数据源
java
@Service
public class UserService {
@Autowired
@Qualifier("primaryUserMapper")
private UserMapper primaryUserMapper;
@Autowired
@Qualifier("secondaryUserMapper")
private UserMapper secondaryUserMapper;
public List<User> getUsersFromPrimary() {
return primaryUserMapper.selectList(null);
}
public List<User> getUsersFromSecondary() {
return secondaryUserMapper.selectList(null);
}
}
10. 业务场景示例
10.1 电商场景:商品管理
java
// 商品实体
@Data
@TableName("product")
public class Product {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
private String category;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableLogic
private Integer deleted;
}
// 商品Service
@Service
public class ProductService extends ServiceImpl<ProductMapper, Product> {
// 根据分类查询商品
public List<Product> getProductsByCategory(String category) {
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Product::getCategory, category)
.gt(Product::getStock, 0)
.orderByDesc(Product::getCreateTime);
return list(wrapper);
}
// 分页查询商品
public Page<Product> getProductsWithPagination(Page<Product> page, String keyword, String category) {
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(keyword)) {
wrapper.like(Product::getName, keyword);
}
if (StringUtils.isNotBlank(category)) {
wrapper.eq(Product::getCategory, category);
}
wrapper.orderByDesc(Product::getCreateTime);
return page(page, wrapper);
}
// 批量更新库存
public boolean updateStock(List<Long> productIds, Integer stock) {
LambdaUpdateWrapper<Product> wrapper = new LambdaUpdateWrapper<>();
wrapper.in(Product::getId, productIds)
.set(Product::getStock, stock);
return update(wrapper);
}
}
10.2 仓储场景:库存管理
java
// 库存实体
@Data
@TableName("inventory")
public class Inventory {
@TableId(type = IdType.AUTO)
private Long id;
private String productCode;
private String warehouseCode;
private Integer quantity;
private Integer reservedQuantity;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
// 库存Service
@Service
public class InventoryService extends ServiceImpl<InventoryMapper, Inventory> {
// 根据商品编码查询库存
public List<Inventory> getInventoryByProductCode(String productCode) {
LambdaQueryWrapper<Inventory> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Inventory::getProductCode, productCode)
.gt(Inventory::getQuantity, 0)
.orderByDesc(Inventory::getQuantity);
return list(wrapper);
}
// 库存预警查询
public List<Inventory> getLowStockInventory(Integer threshold) {
LambdaQueryWrapper<Inventory> wrapper = new LambdaQueryWrapper<>();
wrapper.le(Inventory::getQuantity, threshold)
.orderByAsc(Inventory::getQuantity);
return list(wrapper);
}
// 批量更新库存
public boolean updateInventory(List<Inventory> inventoryList) {
return updateBatchById(inventoryList);
}
}
10.3 财务场景:订单管理
java
// 订单实体
@Data
@TableName("order")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long userId;
private BigDecimal totalAmount;
private String status;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
// 订单Service
@Service
public class OrderService extends ServiceImpl<OrderMapper, Order> {
// 根据用户ID查询订单
public List<Order> getOrdersByUserId(Long userId) {
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Order::getUserId, userId)
.orderByDesc(Order::getCreateTime);
return list(wrapper);
}
// 根据状态查询订单
public List<Order> getOrdersByStatus(String status) {
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Order::getStatus, status)
.orderByDesc(Order::getCreateTime);
return list(wrapper);
}
// 统计订单金额
public BigDecimal getTotalAmountByDateRange(LocalDateTime startTime, LocalDateTime endTime) {
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.between(Order::getCreateTime, startTime, endTime)
.eq(Order::getStatus, "COMPLETED");
List<Order> orders = list(wrapper);
return orders.stream()
.map(Order::getTotalAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
11. 最佳实践
11.1 实体类设计
java
// 推荐:使用Lombok简化代码
@Data
@TableName("sys_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("user_name")
private String username;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableLogic
private Integer deleted;
}
// 不推荐:手动编写getter/setter
public class User {
private Long id;
private String username;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
// ... 其他getter/setter
}
11.2 条件构造器使用
java
// 推荐:使用LambdaQueryWrapper,类型安全
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, "张三")
.gt(User::getAge, 18)
.orderByDesc(User::getCreateTime);
// 不推荐:使用QueryWrapper,容易写错字段名
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", "张三")
.gt("age", 18)
.orderByDesc("create_time");
11.3 分页查询
java
// 推荐:使用Page对象进行分页
Page<User> page = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getUsername, "张");
Page<User> result = userMapper.selectPage(page, wrapper);
// 不推荐:手动计算分页参数
int offset = (current - 1) * size;
List<User> users = userMapper.selectUsersWithLimit(offset, size);
11.4 批量操作
java
// 推荐:使用MyBatis-Plus提供的批量方法
List<User> users = Arrays.asList(user1, user2, user3);
boolean result = userService.saveBatch(users);
// 不推荐:循环调用单个保存方法
for (User user : users) {
userService.save(user);
}
11.5 事务管理
java
// 推荐:在Service层使用@Transactional
@Service
@Transactional
public class UserService extends ServiceImpl<UserMapper, User> {
public boolean createUserWithRole(User user, Long roleId) {
// 保存用户
boolean userSaved = save(user);
// 分配角色
UserRole userRole = new UserRole();
userRole.setUserId(user.getId());
userRole.setRoleId(roleId);
boolean roleAssigned = userRoleService.save(userRole);
return userSaved && roleAssigned;
}
}
12. 常见问题与解决方案
12.1 字段映射问题
问题:数据库字段名与实体类字段名不匹配
java
// 解决方案:使用@TableField注解
@TableField("user_name")
private String username;
12.2 逻辑删除问题
问题:删除数据时希望逻辑删除而不是物理删除
java
// 解决方案:使用@TableLogic注解
@TableLogic
private Integer deleted;
// 配置逻辑删除值
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
12.3 分页查询问题
问题:分页查询没有生效
java
// 解决方案:确保配置了分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
12.4 自动填充问题
问题:自动填充字段没有生效
java
// 解决方案:实现MetaObjectHandler接口
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
12.5 性能问题
问题:查询性能较慢
java
// 解决方案:
// 1. 添加索引
// 2. 使用分页查询
// 3. 避免使用select *,指定需要的字段
// 4. 使用缓存
13. 总结
MyBatis-Plus是一个功能强大的MyBatis增强工具,通过本指南的学习,您应该能够:
- 快速上手:掌握MyBatis-Plus的基本配置和使用方法
- 核心功能:熟练使用条件构造器、分页插件、代码生成器等核心功能
- 最佳实践:了解在实际项目中的最佳实践和注意事项
- 问题解决:能够解决常见的配置和使用问题
MyBatis-Plus大大简化了MyBatis的使用,提高了开发效率,是现代Java项目中的优秀选择。建议在实际项目中逐步引入,根据业务需求选择合适的特性使用。
持续学习建议:
- 关注MyBatis-Plus官方文档的更新
- 参与开源社区讨论,分享使用经验
- 在实际项目中不断实践和优化
- 学习相关的数据库优化知识
14. 高阶应用(含业务场景)
本章节聚焦企业级落地的常用"增强能力"。每条都给出:适用场景 → 规约要点 → 最小示例 → 业务说明(电商/仓储/财务)。
14.1 多租户隔离(TenantLineInnerInterceptor)
- 适用:SaaS 多租户,一库多租按租户列隔离(如
tenant_id)。 - 规约:
- 全局拦截器统一追加
tenant_id = ?条件; - 白名单表(如公共字典)跳过租户拼接;
- 不要在SQL手写租户条件,避免遗漏。
- 全局拦截器统一追加
java
@Configuration
public class MybatisPlusTenantConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
MybatisPlusInterceptor i = new MybatisPlusInterceptor();
i.addInnerInterceptor(new TenantLineInnerInterceptor(() -> new LongValue(getTenantId())));
i.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return i;
}
private Long getTenantId(){ return TenantContext.getTenantId(); }
}
- 业务说明:
- 电商:按
tenant_id隔离商家数据; - 仓储:多仓储系统按租户隔离库存;
- 财务:各租户账套互不见。
- 电商:按
14.2 乐观锁并发控制(@Version + OptimisticLockerInnerInterceptor)
- 适用:并发更新同一行,避免"后写覆盖前写"。
- 规约:
- 在热点表加
version整型列; - 更新时携带版本号,失败则重试/前端提示。
- 在热点表加
java
@Data @TableName("order")
public class OrderEntity {
@TableId(type=IdType.ASSIGN_ID) private Long id;
private BigDecimal totalAmount; private String status;
@Version private Integer version; // 自动维护
}
@Configuration
class MpConfig {
@Bean MybatisPlusInterceptor interceptor(){
MybatisPlusInterceptor i=new MybatisPlusInterceptor();
i.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return i;
}
}
- 业务说明:
- 电商:订单支付状态更新;
- 仓储:扣减库存;
- 财务:对账记录确认。
14.3 全表操作保护(BlockAttackInnerInterceptor)
- 适用:防止误写
UPDATE table/DELETE FROM table无条件语句。 - 规约:开发/测试/生产都建议开启,生产必须开启。
java
@Bean
public MybatisPlusInterceptor interceptor(){
MybatisPlusInterceptor i = new MybatisPlusInterceptor();
i.addInnerInterceptor(new BlockAttackInnerInterceptor());
return i;
}
14.4 JSON 字段映射(JacksonTypeHandler)
- 适用:可变扩展字段(商品属性、地址详情、配置项)。
- 规约:列类型使用 JSON/Text,统一对象结构与版本。
java
@Data @TableName(value="product", autoResultMap = true)
class Product {
@TableId(type=IdType.ASSIGN_ID) Long id;
String name; BigDecimal price;
@TableField(typeHandler = JacksonTypeHandler.class)
Map<String, Object> attributes; // 如 {color: "red", size: "M"}
}
- 电商:商品属性;仓储:库位扩展;财务:凭证扩展信息。
14.5 字段透明加解密(自定义 TypeHandler)
- 适用:手机号、身份证、银行卡等敏感字段。
- 规约:
- 写库前加密,读库后解密;
- 操作员仅见脱敏显示,不反查密文。
java
public class EncryptStringTypeHandler extends BaseTypeHandler<String> {
@Override public void setNonNullParameter(PreparedStatement ps,int i,String param,JdbcType jdbcType) throws SQLException {
ps.setString(i, AesUtil.encrypt(param));
}
@Override public String getNullableResult(ResultSet rs,String column) throws SQLException {
String v = rs.getString(column); return v==null?null:AesUtil.decrypt(v);
}
@Override public String getNullableResult(ResultSet rs,int i) throws SQLException {return getNullableResult(rs, rs.getMetaData().getColumnName(i));}
@Override public String getNullableResult(CallableStatement cs,int i) throws SQLException {return cs.getString(i);} }
@TableField(typeHandler = EncryptStringTypeHandler.class)
private String phone;
14.6 批量 UPSERT(Insert or Update)
- 适用:导入覆盖、增量同步。
- 规约:使用数据库原生语法,控制批次大小。
xml
<insert id="upsertBatch">
INSERT INTO t (id,name,val) VALUES
<foreach collection="list" item="e" separator=",">
(#{e.id},#{e.name},#{e.val})
</foreach>
ON DUPLICATE KEY UPDATE name=VALUES(name), val=VALUES(val)
<!-- PostgreSQL: ON CONFLICT (id) DO UPDATE SET ... -->
</insert>
- 电商:商品价格批量同步;仓储:库存对账回填;财务:科目余额导入。
14.7 复杂联表查询(XML 或 MP-Join)
- 适用:多表聚合(订单+明细+用户)。
- 规约:单表 CRUD 用 MP,联表尽量走 XML 以便可控与调优。
xml
<select id="selectOrderWithItems" resultType="com.example.dto.OrderDTO">
SELECT o.id,o.order_no,u.username,i.product_name,i.qty
FROM `order` o
LEFT JOIN user u ON u.id=o.user_id
LEFT JOIN order_item i ON i.order_id=o.id
WHERE o.id=#{orderId}
</select>
14.8 动态数据源(读写分离/多库路由)
- 规约:只在 Service 层标注,避免散布到控制器。
java
@DS("slave")
public List<User> listFromSlave(){ return list(); }
- 电商:主写从读;财务:报表读从库。
14.9 审计与逻辑删除(MetaObjectHandler + @TableLogic)
- 规约:统一
createBy/createTime/updateBy/updateTime/deleted;逻辑删除默认开启,归档任务做物理清理。
java
@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;
@TableLogic private Integer deleted;
14.10 自定义全局方法注入(SqlInjector)
- 适用:给所有 Mapper 注入通用方法(如批量软删、还原)。
java
public class CustomInjector extends DefaultSqlInjector {
@Override public List<AbstractMethod> getMethodList(Configuration c, Class<?> m){
List<AbstractMethod> list = new ArrayList<>(super.getMethodList(c, m));
list.add(new LogicRestoreMethod()); return list; }
}
14.11 选择列与聚合(select / func)
- 规约:只查必要列,减少 IO;聚合下沉数据库。
java
lambdaQuery().select(User::getId, User::getUsername).list();
// 分组聚合
query().select("age, COUNT(1) cnt").groupBy("age").list();
14.12 高效分页(Keyset/Seek 分页)
- 适用:深分页性能差时,基于主键游标翻页。
java
lambdaQuery().gt(User::getId, lastId).orderByAsc(User::getId).last("limit 100").list();
14.13 业务数据权限(自定义 InnerInterceptor)
- 适用:按组织/仓库/用户维度追加权限条件。
java
public class DataScopeInterceptor implements InnerInterceptor {
@Override public void beforePrepare(Executor executor, MappedStatement ms, BoundSql bs) {
// 解析SQL并在 where 中追加 data_scope 条件(略)
}
}
14.14 软硬删除并存策略
- 规约:默认逻辑删除;物理删除走受控接口,需审计。
java
@InterceptorIgnore(tenantLine = "true", logicDelete = "true")
int physicalDeleteByIds(@Param("ids") List<Long> ids);
14.15 批量写入性能优化
- 规约:
saveBatch(list, batchSize);或使用ExecutorType.BATCH,分批 flush。
java
userService.saveBatch(users, 1000);
14.16 分库分表(ShardingSphere 等)
- 适用:订单、流水按月分表;MP 负责 CRUD,路由交给中间件。
- 规约:分片键落到查询条件;避免跨分片聚合。
14.17 统一ID与时间戳
- 规约:使用
ASSIGN_ID(雪花)或自研发号器;时间统一 UTC 存储、展示本地化。
java
@TableId(type = IdType.ASSIGN_ID) private Long id;
14.18 幂等保障(唯一约束 + 去重键)
- 适用:支付回调、消息消费、对账导入。
xml
<!-- MySQL 幂等写:忽略重复 -->
INSERT IGNORE INTO t(unique_key, data) VALUES(#{uk}, #{data})
14.19 变更审计(差异记录)
- 规约:更新前后比对差异,写入审计表;可 AOP 拦截
update*。
java
class ChangeLog { Long bizId; String table; String diffJson; LocalDateTime at; String operator; }
14.x 速查建议(选型指南)
- 高并发扣减:优先"乐观锁 + 幂等 + 短事务",必要时行级锁;
- 多租户:首选列级隔离 + 拦截器;再考虑库/表级;
- 大表分页:Seek 分页替代深度 offset;
- 批量导入:分批 + UPSERT + 异步;
- 数据权限:统一拦截器拼条件,避免 Controller/Mapper 分散;
- 敏感数据:TypeHandler 加解密 + 脱敏展示 + 严控可见性。
以上高阶能力均可与现有章节组合使用,建议按业务优先级渐进引入,配合监控和回滚预案,确保风险可控。