
在Java持久层开发中,MyBatis凭借灵活的SQL控制能力成为主流选择,但原生MyBatis存在大量重复CRUD代码、分页逻辑冗余、条件查询繁琐等问题。MyBatis-Plus(简称MP)作为MyBatis的增强工具,在完全兼容MyBatis的基础上,提供了无侵入式的增强功能,大幅简化开发流程。本文从核心特性、实战落地、进阶功能到生产避坑,完整拆解MyBatis-Plus的使用全流程,助力开发者提升持久层开发效率。
原生MyBatis在实际开发中面临诸多痛点:单表CRUD需手动编写XML映射文件、分页查询需自定义拦截器、条件查询拼接SQL易出错且冗余。MyBatis-Plus的核心价值在于“增强不改变”,既保留MyBatis的灵活性,又通过自动化能力解决重复劳动,具体优势如下:
选型建议:若项目已使用MyBatis,可无缝集成MyBatis-Plus;新项目优先选择MyBatis-Plus,能减少50%以上的持久层代码量。
MyBatis-Plus基于MyBatis的插件机制实现增强,核心架构分为三层,对原生MyBatis无侵入:
设计理念:约定大于配置,通过默认规则自动适配数据库表结构与实体类,减少配置成本;同时支持自定义扩展,兼顾规范性与灵活性。
功能模块 | 核心能力 | 使用场景 |
|---|---|---|
BaseMapper接口 | 内置insert、delete、update、select系列方法,覆盖单表全量CRUD | 大部分单表操作场景,无需编写SQL |
Lambda条件构造器 | 基于Lambda表达式构建查询条件,类型安全,避免字段名硬编码 | 复杂条件查询、动态SQL场景 |
分页插件 | 自动拦截分页查询,生成物理分页SQL,支持多种数据库 | 列表分页、分页筛选场景 |
乐观锁插件 | 通过版本号机制防止并发更新冲突,自动拼接版本号条件 | 库存扣减、订单状态更新等并发场景 |
逻辑删除 | 假删除机制,更新删除标记字段,不物理删除数据 | 需要数据回溯、避免误删的业务场景 |
代码生成器 | 根据数据库表结构,自动生成实体类、Mapper、Service、Controller | 项目初始化、新增表结构时快速生成代码 |
多数据源 | 支持动态切换多数据源,适配读写分离、多库关联场景 | 跨库查询、主从架构场景 |
以电商订单模块为例,实现MyBatis-Plus的基础集成与单表CRUD操作。技术栈:Spring Boot 2.7.x + MyBatis-Plus 3.5.3.1 + MySQL 8.0 + Druid连接池。
pom.xml中引入MyBatis-Plus Starter、MySQL驱动、Druid连接池依赖,无需额外引入MyBatis(Starter已包含):
<!-- Spring Boot Web核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus Starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<!-- Lombok(简化实体类) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>application.yml中配置数据源、MyBatis-Plus基础参数,无需额外配置MyBatis核心文件:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 123456
# Druid连接池配置
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
# MyBatis-Plus配置
mybatis-plus:
# mapper.xml文件路径
mapper-locations: classpath:mapper/**/*.xml
# 实体类扫描路径
type-aliases-package: com.example.mp.entity
configuration:
# 驼峰命名自动映射
map-underscore-to-camel-case: true
# 日志打印SQL(开发环境开启,生产环境关闭)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 全局配置
global-config:
db-config:
# 主键生成策略(雪花算法)
id-type: ASSIGN_ID
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除值(1=删除)
logic-delete-value: 1
# 逻辑未删除值(0=正常)
logic-not-delete-value: 0启动类上添加@MapperScan注解,扫描Mapper接口所在包:
package com.example.mp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
// 扫描Mapper接口包
@MapperScan("com.example.mp.mapper")
public class MyBatisPlusDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisPlusDemoApplication.class, args);
}
}使用Lombok简化实体类,通过注解映射数据库表与字段:
package com.example.mp.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
// 映射数据库表名(表名与类名一致可省略)
@TableName("t_order")
public class Order {
// 主键,使用雪花算法生成
@TableId(type = IdType.ASSIGN_ID)
private Long id;
// 订单编号(与表字段一致可省略@TableField)
private String orderNo;
// 用户ID
private Long userId;
// 订单金额
private BigDecimal amount;
// 订单状态(0-待支付,1-已支付,2-已取消)
private Integer status;
// 创建时间(自动填充)
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 更新时间(自动填充)
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
// 逻辑删除字段(全局配置后可省略注解)
@TableLogic
private Integer deleted;
}继承BaseMapper接口,无需编写方法即可获得全量单表CRUD能力:
package com.example.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.entity.Order;
import org.apache.ibatis.annotations.Mapper;
// @Mapper注解可在启动类通过@MapperScan批量扫描,此处可省略
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
// 无需编写任何方法,BaseMapper已提供以下功能:
// insert、deleteById、updateById、selectById、selectList、selectPage等
}继承IService接口与ServiceImpl实现类,获得更丰富的业务层方法(如批量操作、条件查询):
// Service接口
package com.example.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mp.entity.Order;
public interface OrderService extends IService<Order> {
// 可自定义业务方法,基础CRUD无需编写
}
// Service实现类
package com.example.mp.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mp.entity.Order;
import com.example.mp.mapper.OrderMapper;
import com.example.mp.service.OrderService;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
// 继承ServiceImpl后,自动获得IService的所有方法
}Controller层调用Service/Mapper,实现无SQL的CRUD操作:
package com.example.mp.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.mp.entity.Order;
import com.example.mp.service.OrderService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
@RestController
@RequestMapping("/order")
public class OrderController {
@Resource
private OrderService orderService;
// 新增订单
@PostMapping("/add")
public boolean addOrder() {
Order order = new Order();
order.setOrderNo("ORDER20260119001");
order.setUserId(1001L);
order.setAmount(new BigDecimal("199.00"));
order.setStatus(0);
return orderService.save(order);
}
// 根据ID查询订单
@GetMapping("/{id}")
public Order getOrderById(@PathVariable Long id) {
return orderService.getById(id);
}
// 根据用户ID查询订单列表
@GetMapping("/list/{userId}")
public List<Order> getOrderList(@PathVariable Long userId) {
// 条件构造器:查询用户ID=userId且未删除的订单
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", userId)
.eq("deleted", 0);
return orderService.list(wrapper);
}
// 更新订单状态
@PutMapping("/update/status/{id}/{status}")
public boolean updateOrderStatus(@PathVariable Long id, @PathVariable Integer status) {
Order order = new Order();
order.setId(id);
order.setStatus(status);
return orderService.updateById(order);
}
// 逻辑删除订单
@DeleteMapping("/{id}")
public boolean deleteOrder(@PathVariable Long id) {
// 自动更新deleted字段为1,而非物理删除
return orderService.removeById(id);
}
}使用LambdaQueryWrapper替代QueryWrapper,避免字段名硬编码,支持类型安全校验:
// 根据用户ID和订单状态查询,支持链式调用
LambdaQueryWrapper<Order> lambdaWrapper = new LambdaQueryWrapper<>();
lambdaWrapper.eq(Order::getUserId, 1001L)
.eq(Order::getStatus, 1)
.ge(Order::getCreateTime, LocalDateTime.of(2026, 1, 1, 0, 0))
.orderByDesc(Order::getCreateTime);
List<Order> orderList = orderService.list(lambdaWrapper);
// 动态条件查询(避免if-else判断)
lambdaWrapper.eq(Order::getUserId, 1001L)
.when(status != null, wrapper -> wrapper.eq(Order::getStatus, status))
.like(StringUtils.hasText(orderNo), Order::getOrderNo, orderNo);MyBatis-Plus内置分页插件,配置后即可实现物理分页,无需手动编写分页SQL:
package com.example.mp.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
// 配置分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加MySQL分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}// 分页查询用户订单(第1页,每页10条)
@GetMapping("/page/{userId}")
public IPage<Order> getOrderPage(@PathVariable Long userId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
// 构建分页对象
Page<Order> page = new Page<>(pageNum, pageSize);
// 条件构造器
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Order::getUserId, userId)
.eq(Order::getDeleted, 0);
// 分页查询(自动生成物理分页SQL)
return orderService.page(page, wrapper);
}针对创建时间、更新时间等公共字段,通过自动填充功能避免重复赋值:
package com.example.mp.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 新增时自动填充
@Override
public void insertFill(MetaObject metaObject) {
// 填充创建时间
strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
// 填充更新时间
strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
// 更新时自动填充
@Override
public void updateFill(MetaObject metaObject) {
// 填充更新时间
strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}解决并发更新冲突,通过版本号机制确保数据一致性:
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}@Data
@TableName("t_order")
public class Order {
// 其他字段省略...
// 乐观锁版本号字段
@Version
private Integer version;
}// 并发更新订单状态
public boolean updateOrderStatusWithLock(Long id, Integer status) {
Order order = orderService.getById(id);
order.setStatus(status);
// 自动拼接条件:where id = ? and version = ?,更新成功后version+1
return orderService.updateById(order);
}基于数据库表结构自动生成实体类、Mapper、Service、Controller,大幅提升初始化效率:
package com.example.mp.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
public class CodeGenerator {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=Asia/Shanghai",
"root", "123456")
// 全局配置
.globalConfig(builder -> {
builder.author("开发者") // 作者
.outputDir(System.getProperty("user.dir") + "/src/main/java") // 输出路径
.disableOpenDir() // 禁止自动打开目录
.commentDate("yyyy-MM-dd HH:mm:ss"); // 注释日期
})
// 包配置
.packageConfig(builder -> {
builder.parent("com.example.mp") // 父包名
.moduleName("order") // 模块名
.mapper("mapper") // Mapper包名
.service("service") // Service包名
.controller("controller") // Controller包名
.entity("entity") // 实体类包名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml,
System.getProperty("user.dir") + "/src/main/resources/mapper/order")); // MapperXML路径
})
// 策略配置
.strategyConfig(builder -> {
builder.addInclude("t_order") // 生成的表名(多个表用逗号分隔)
.addTablePrefix("t_") // 表前缀(生成实体类时去掉前缀)
.entityBuilder()
.enableLombok() // 启用Lombok
.enableTableFieldAnnotation() // 启用字段注解
.controllerBuilder()
.enableRestStyle() // 启用RestController风格
.serviceBuilder()
.formatServiceFileName("%sService") // Service命名规则
.formatServiceImplFileName("%sServiceImpl");
})
// 模板引擎(Freemarker)
.templateEngine(new FreemarkerTemplateEngine())
.execute();
}
}通过MyBatis-Plus多数据源组件,实现读写分离、多库关联场景:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>spring:
datasource:
dynamic:
primary: master # 主数据源
strict: false # 非严格模式,允许未匹配数据源
datasource:
# 主数据源(写库)
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 从数据源(读库)
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/order_db?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
// 写操作(默认主数据源)
@Override
public boolean saveOrder(Order order) {
return save(order);
}
// 读操作(切换到从数据源)
@Override
@DS("slave")
public List<Order> getOrderListByUserId(Long userId) {
return lambdaQuery().eq(Order::getUserId, userId).list();
}
}MyBatis-Plus完全兼容原生MyBatis,复杂查询可通过XML编写自定义SQL:
public interface OrderMapper extends BaseMapper<Order> {
// 自定义SQL查询:关联用户表查询订单详情
List<OrderVO> selectOrderVOList(@Param("userId") Long userId);
}<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mp.mapper.OrderMapper">
<select id="selectOrderVOList" resultType="com.example.mp.vo.OrderVO">
SELECT o.id, o.order_no, o.amount, o.status, u.username
FROM t_order o
LEFT JOIN t_user u ON o.user_id = u.id
WHERE o.user_id = #{userId} AND o.deleted = 0
</select>
</mapper>现象:分页查询时list有数据,但total为0,分页控件显示异常。 规避方案:
现象:并发更新时无版本号校验,数据被覆盖。 规避方案:
现象:调用removeById后,查询仍能获取到数据。 规避方案:
现象:createTime、updateTime未自动填充,数据库字段为null。 规避方案:
现象:Lambda表达式引用实体类方法时,报字段不存在错误。 规避方案:
现象:@DS注解切换数据源无效果,始终使用主数据源。 规避方案:
现象:生成实体类、Mapper后,无MapperXML文件。 规避方案:
现象:集成Sharding-JDBC后,分页、乐观锁插件失效。 规避方案:
现象:使用saveBatch方法批量插入时,速度缓慢。 规避方案:
现象:在@Transactional事务中调用updateById,数据未更新且无报错。 规避方案:
MyBatis-Plus的核心价值在于“简化开发、兼顾灵活”,落地时需遵循以下原则,最大化发挥其优势:
MyBatis-Plus并非替代MyBatis,而是通过自动化能力解放开发者的重复劳动,让精力聚焦于复杂业务逻辑。掌握本文所述的基础用法、进阶功能与避坑要点,能大幅提升持久层开发效率,适配从单体应用到微服务的全场景需求。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。