一、前言:为什么需要 MyBatis-Plus?
如果你用过 MyBatis,一定写过这样的重复代码:
java
// MyBatis 的标准 CRUD,每个表都要写一遍
@Select("SELECT * FROM t_user WHERE id = #{id}")
User selectById(Long id);
@Select("SELECT * FROM t_user WHERE deleted = 0")
List<User> selectAll();
@Insert("INSERT INTO t_user (username, email, age) VALUES (#{username}, #{email}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE t_user SET username=#{username}, email=#{email}, age=#{age} WHERE id=#{id}")
int update(User user);
@Update("UPDATE t_user SET deleted = 1 WHERE id = #{id}")
int deleteById(Long id);
MyBatis-Plus 解决的核心问题:
| MyBatis 痛点 | MyBatis-Plus 方案 | 效果 |
|---|---|---|
| 标准 CRUD 重复写 | BaseMapper 内置完整 CRUD | 零 SQL |
| 条件查询拼接麻烦 | Wrapper 链式构造 | 类型安全,IDE 提示 |
| 分页手动写 LIMIT | 内置分页插件 | 一行代码 |
| 创建时间/更新时间手动填 | 自动填充机制 | 无感知 |
| 逻辑删除写 WHERE 条件 | @TableLogic 注解 | 自动过滤 |
| 新表重复写 Entity/Mapper | AutoGenerator 代码生成 | 一键生成全套 |
MyBatis-Plus 定位: 在 MyBatis 基础上只做增强不做改变,简化开发、提高效率。
二、环境准备
2.1 创建 Spring Boot 项目
Spring Initializr 官网**
-
选择:
- Project: Maven
- Language: Java
- Spring Boot: 3.2.0
- Dependencies: Spring Web, MySQL Driver
-
点击 Generate,下载解压后用 IDEA 打开
2.2 项目结构预览
plain
mybatis-plus-demo/
├── pom.xml
└── src/
└── main/
├── java/com/example/demo/
│ ├── DemoApplication.java
│ ├── config/ ← 配置类
│ │ ├── MybatisPlusConfig.java
│ │ └── MyMetaObjectHandler.java
│ ├── entity/ ← 实体类
│ │ └── User.java
│ ├── mapper/ ← Mapper 接口
│ │ └── UserMapper.java
│ ├── service/ ← 业务层
│ │ └── UserService.java
│ ├── controller/ ← 控制器
│ │ └── UserController.java
│ └── generator/ ← 代码生成器(可选)
│ └── CodeGenerator.java
└── resources/
├── application.yml ← 配置文件
└── mapper/ ← XML 映射文件(可选)
三、创建数据库
3.1 连接 MySQL
bash
mysql -u root -p
3.2 执行建库脚本
sql
-- 创建数据库
CREATE DATABASE IF NOT EXISTS mybatis_plus_demo
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE mybatis_plus_demo;
-- 创建用户表
CREATE TABLE t_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL COMMENT '用户名',
email VARCHAR(100) COMMENT '邮箱',
age INT COMMENT '年龄',
phone VARCHAR(20) COMMENT '手机号',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0-正常 1-删除'
) COMMENT '用户表';
-- 插入测试数据
INSERT INTO t_user (username, email, age, phone) VALUES
('张三', 'zhangsan@example.com', 25, '13800138001'),
('李四', 'lisi@example.com', 30, '13800138002'),
('王五', 'wangwu@example.com', 28, '13800138003'),
('赵六', 'zhaoliu@example.com', 35, '13800138004'),
('孙七', 'sunqi@example.com', 22, '13800138005');
-- 创建订单表(用于代码生成器演示)
CREATE TABLE t_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
order_no VARCHAR(50) NOT NULL COMMENT '订单号',
total_amount DECIMAL(10,2) COMMENT '总金额',
status TINYINT DEFAULT 0 COMMENT '状态:0-待支付 1-已支付 2-已发货 3-已完成',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted TINYINT DEFAULT 0
) COMMENT '订单表';
-- 验证数据
SELECT * FROM t_user;
四、完整代码
4.1 pom.xml(完整依赖)
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>mybatis-plus-demo</artifactId>
<version>1.0.0</version>
<name>mybatis-plus-demo</name>
<description>MyBatis-Plus Demo Project</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2 main/java/resources/application.yml(完整配置)
yaml
# 服务器端口
server:
port: 8080
# 数据源配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis_plus_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: your_password # 修改为你的密码
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis-Plus 配置
mybatis-plus:
# XML 映射文件位置(如果用 XML 方式)
mapper-locations: classpath:mapper/*.xml
# 实体类别名包
type-aliases-package: com.example.demo.entity
configuration:
# 自动驼峰转换:create_time → createTime
map-underscore-to-camel-case: true
# 打印 SQL 到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 缓存开启
cache-enabled: true
global-config:
db-config:
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除值(已删除)
logic-delete-value: 1
# 逻辑未删除值(正常)
logic-not-delete-value: 0
# ID 生成策略:数据库自增
id-type: auto
# 表名前缀(如果实体类名和表名不一致时使用)
# table-prefix: t_
# 日志级别
logging:
level:
com.example.demo.mapper: debug
4.3 实体类 User.java
java
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户实体类
*
* @Data Lombok 注解,自动生成 getter/setter/toString/equals/hashCode
* @TableName 指定数据库表名,如果类名和表名一致可省略
*/
@Data
@TableName("t_user")
public class User {
/**
* 主键 ID
* @TableId 标记主键字段
* type = IdType.AUTO 使用数据库自增
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String username;
/**
* 邮箱
*/
private String email;
/**
* 年龄
*/
private Integer age;
/**
* 手机号
*/
private String phone;
/**
* 创建时间
* @TableField fill = FieldFill.INSERT 表示插入时自动填充
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
* @TableField fill = FieldFill.INSERT_UPDATE 表示插入和更新时自动填充
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 逻辑删除字段
* @TableLogic 标记逻辑删除字段
* 配置后,删除操作自动变为 UPDATE deleted = 1
* 查询操作自动添加 WHERE deleted = 0
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
}
4.4 统一响应结果 Result.java
java
package com.example.demo.common;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 统一响应结果
*/
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
private String timestamp;
private Result() {
this.timestamp = LocalDateTime.now().toString();
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("成功");
result.setData(data);
return result;
}
public static <T> Result<T> success() {
return success(null);
}
public static <T> Result<T> error(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
}
4.5 分页结果 PageResult.java
java
package com.example.demo.common;
import lombok.Data;
import java.util.List;
/**
* 分页结果
*/
@Data
public class PageResult<T> {
private Long total;
private List<T> list;
private Integer pageNum;
private Integer pageSize;
private Integer totalPages;
public static <T> PageResult<T> of(Long total, List<T> list, Integer pageNum, Integer pageSize) {
PageResult<T> result = new PageResult<>();
result.setTotal(total);
result.setList(list);
result.setPageNum(pageNum);
result.setPageSize(pageSize);
result.setTotalPages((int) Math.ceil((double) total / pageSize));
return result;
}
}
4.6 业务异常 BusinessException.java
java
package com.example.demo.exception;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private final Integer code;
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
}
4.7 全局异常处理器 GlobalExceptionHandler.java
java
package com.example.demo.handler;
import com.example.demo.common.Result;
import com.example.demo.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import jakarta.servlet.http.HttpServletRequest;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
log.warn("业务异常 [{}] - {}", request.getRequestURI(), e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e, HttpServletRequest request) {
log.error("系统异常 [{}]", request.getRequestURI(), e);
return Result.error(500, "系统繁忙,请稍后重试");
}
}
4.8 MyBatis-Plus 配置类
java
package com.example.demo.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;
/**
* MyBatis-Plus 配置类
*/
@Configuration
public class MybatisPlusConfig {
/**
* 添加分页插件
* 必须配置数据库类型,否则分页无效
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件,指定数据库类型为 MySQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
4.9 自动填充处理器
java
package com.example.demo.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 自动填充处理器
* 实现 MetaObjectHandler 接口,在插入和更新时自动填充字段
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时自动填充
*/
@Override
public void insertFill(MetaObject metaObject) {
log.debug("开始插入填充...");
// 严格填充(如果字段有值则不覆盖)
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
}
/**
* 更新时自动填充
*/
@Override
public void updateFill(MetaObject metaObject) {
log.debug("开始更新填充...");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
4.10 Mapper 接口 UserMapper.java
java
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户 Mapper
*
* 继承 BaseMapper<User> 获得完整的 CRUD 能力:
* - insert(T entity) 插入
* - deleteById(Serializable id) 根据 ID 删除
* - deleteByMap(Map<String, Object> columnMap) 根据 Map 删除
* - delete(Wrapper<T> wrapper) 条件删除
* - deleteBatchIds(Collection<? extends Serializable> idList) 批量删除
* - updateById(T entity) 根据 ID 更新
* - update(T entity, Wrapper<T> wrapper) 条件更新
* - selectById(Serializable id) 根据 ID 查询
* - selectBatchIds(Collection<? extends Serializable> idList) 批量查询
* - selectByMap(Map<String, Object> columnMap) Map 查询
* - selectOne(Wrapper<T> wrapper) 查询一条
* - selectCount(Wrapper<T> wrapper) 查询总数
* - selectList(Wrapper<T> wrapper) 条件查询列表
* - selectMaps(Wrapper<T> wrapper) 查询 Map 列表
* - selectObjs(Wrapper<T> wrapper) 查询对象列表
* - selectPage(Page<T> page, Wrapper<T> wrapper) 分页查询
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 自定义复杂查询可在这里扩展
// BaseMapper 已提供的方法无需再写
}
4.11 Service 层 UserService.java
java
package com.example.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.common.PageResult;
import com.example.demo.common.Result;
import com.example.demo.entity.User;
import com.example.demo.exception.BusinessException;
import com.example.demo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 用户 Service
*
* 继承 ServiceImpl<UserMapper, User> 获得大量通用方法:
* - save(T entity) 保存
* - saveBatch(Collection<T> entityList) 批量保存
* - removeById(Serializable id) 根据 ID 删除
* - removeByIds(Collection<? extends Serializable> idList) 批量删除
* - remove(Wrapper<T> queryWrapper) 条件删除
* - updateById(T entity) 根据 ID 更新
* - update(T entity, Wrapper<T> updateWrapper) 条件更新
* - getById(Serializable id) 根据 ID 查询
* - list() 查询所有
* - list(Wrapper<T> queryWrapper) 条件查询
* - page(Page<T> page, Wrapper<T> queryWrapper) 分页查询
* - count() 统计总数
* - count(Wrapper<T> queryWrapper) 条件统计
*/
@Slf4j
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
/**
* 根据 ID 查询
*/
public User getById(Long id) {
if (id == null || id <= 0) {
throw new BusinessException(400, "用户ID必须大于0");
}
User user = super.getById(id);
if (user == null) {
throw new BusinessException(404, "用户不存在");
}
return user;
}
/**
* 查询所有(自动过滤逻辑删除)
*/
public List<User> findAll() {
// LambdaQueryWrapper 类型安全,IDE 有提示
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(User::getCreateTime);
return super.list(wrapper);
}
/**
* 条件查询
*/
public List<User> findByCondition(String username, Integer minAge, Integer maxAge) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 用户名模糊查询
if (username != null && !username.isEmpty()) {
wrapper.like(User::getUsername, username);
}
// 年龄范围
if (minAge != null) {
wrapper.ge(User::getAge, minAge); // greater or equal
}
if (maxAge != null) {
wrapper.le(User::getAge, maxAge); // less or equal
}
wrapper.orderByDesc(User::getCreateTime);
return super.list(wrapper);
}
/**
* 分页查询
*/
public PageResult<User> findByPage(Integer pageNum, Integer pageSize, String username) {
// 1. 创建分页对象
Page<User> page = new Page<>(pageNum, pageSize);
// 2. 构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
if (username != null && !username.isEmpty()) {
wrapper.like(User::getUsername, username);
}
wrapper.orderByDesc(User::getCreateTime);
// 3. 执行分页查询(自动统计总数 + 查询数据)
Page<User> result = super.page(page, wrapper);
// 4. 转换为统一分页结果
return PageResult.of(
result.getTotal(),
result.getRecords(),
(int) result.getCurrent(),
(int) result.getSize()
);
}
/**
* 新增用户(事务)
* 自动填充 createTime、updateTime、deleted
*/
@Transactional
public User create(User user) {
if (user == null) {
throw new BusinessException(400, "用户信息不能为空");
}
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
throw new BusinessException(400, "用户名不能为空");
}
// save 方法自动调用 insert,触发自动填充
boolean success = super.save(user);
if (!success) {
throw new BusinessException(500, "保存失败");
}
return user;
}
/**
* 更新用户(事务)
* 自动填充 updateTime
*/
@Transactional
public User updateUser(User user) {
if (user == null || user.getId() == null) {
throw new BusinessException(400, "用户ID不能为空");
}
// updateById 自动调用 update,触发自动填充
boolean success = super.updateById(user);
if (!success) {
throw new BusinessException(404, "用户不存在或已被删除");
}
return super.getById(user.getId());
}
/**
* 删除用户(逻辑删除,实际执行 UPDATE)
* 自动转换为 UPDATE t_user SET deleted = 1 WHERE id = ?
*/
@Transactional
public void delete(Long id) {
if (id == null || id <= 0) {
throw new BusinessException(400, "用户ID必须大于0");
}
// removeById 自动处理逻辑删除
boolean success = super.removeById(id);
if (!success) {
throw new BusinessException(404, "用户不存在");
}
}
/**
* 批量删除
*/
@Transactional
public void deleteBatch(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
throw new BusinessException(400, "ID列表不能为空");
}
super.removeByIds(ids);
}
}
4.12 Controller 层 UserController.java
java
package com.example.demo.controller;
import com.example.demo.common.PageResult;
import com.example.demo.common.Result;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 用户控制器
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
/**
* 根据 ID 查询
* GET /api/users/1
*/
@GetMapping("/{id}")
public Result<User> getById(@PathVariable Long id) {
return Result.success(userService.getById(id));
}
/**
* 查询所有
* GET /api/users
*/
@GetMapping
public Result<List<User>> findAll() {
return Result.success(userService.findAll());
}
/**
* 条件查询
* GET /api/users/search?username=张&minAge=20&maxAge=30
*/
@GetMapping("/search")
public Result<List<User>> search(
@RequestParam(required = false) String username,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge) {
return Result.success(userService.findByCondition(username, minAge, maxAge));
}
/**
* 分页查询
* GET /api/users/page?pageNum=1&pageSize=2&username=张
*/
@GetMapping("/page")
public Result<PageResult<User>> page(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String username) {
return Result.success(userService.findByPage(pageNum, pageSize, username));
}
/**
* 新增
* POST /api/users
* Body: {"username":"周八","email":"zhouba@example.com","age":40,"phone":"13800138006"}
*/
@PostMapping
public Result<User> create(@RequestBody User user) {
return Result.success(userService.create(user));
}
/**
* 更新
* PUT /api/users
* Body: {"id":1,"username":"张三(已修改)","age":26}
*/
@PutMapping
public Result<User> update(@RequestBody User user) {
return Result.success(userService.updateUser(user));
}
/**
* 删除(逻辑删除)
* DELETE /api/users/1
*/
@DeleteMapping("/{id}")
public Result<Void> delete(@PathVariable Long id) {
userService.delete(id);
return Result.success();
}
/**
* 批量删除
* DELETE /api/users/batch?ids=1,2,3
*/
@DeleteMapping("/batch")
public Result<Void> deleteBatch(@RequestParam List<Long> ids) {
userService.deleteBatch(ids);
return Result.success();
}
}
4.13 启动类 DemoApplication.java
java
package com.example.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动类
* @MapperScan 指定 Mapper 接口扫描包,替代每个 Mapper 上的 @Mapper
*/
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println("=== MyBatis-Plus Demo 启动成功 ===");
System.out.println("访问地址: http://localhost:8080");
System.out.println("测试接口: http://localhost:8080/api/users");
}
}
五、Wrapper 条件构造器详解
5.1 LambdaQueryWrapper 常用方法
java
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 等于
wrapper.eq(User::getId, 1); // WHERE id = 1
// 不等于
wrapper.ne(User::getAge, 18); // WHERE age <> 18
// 大于
wrapper.gt(User::getAge, 18); // WHERE age > 18
// 大于等于
wrapper.ge(User::getAge, 18); // WHERE age >= 18
// 小于
wrapper.lt(User::getAge, 60); // WHERE age < 60
// 小于等于
wrapper.le(User::getAge, 60); // WHERE age <= 60
// 范围
wrapper.between(User::getAge, 18, 30); // WHERE age BETWEEN 18 AND 30
// 模糊查询
wrapper.like(User::getUsername, "张"); // WHERE username LIKE '%张%'
wrapper.likeRight(User::getPhone, "138"); // WHERE phone LIKE '138%'
// IN 查询
wrapper.in(User::getId, Arrays.asList(1, 2, 3)); // WHERE id IN (1, 2, 3)
// 为空
wrapper.isNull(User::getEmail); // WHERE email IS NULL
// 非空
wrapper.isNotNull(User::getEmail); // WHERE email IS NOT NULL
// 排序
wrapper.orderByDesc(User::getCreateTime); // ORDER BY create_time DESC
wrapper.orderByAsc(User::getAge); // ORDER BY age ASC
// 组合条件(默认 AND)
wrapper.eq(User::getAge, 25)
.like(User::getUsername, "张"); // WHERE age = 25 AND username LIKE '%张%'
// OR 条件
wrapper.eq(User::getAge, 25)
.or()
.eq(User::getAge, 30); // WHERE age = 25 OR age = 30
5.2 链式查询示例
java
// 查询年龄大于 20 且用户名包含 "张" 的用户,按创建时间倒序
List<User> list = userService.list(
new LambdaQueryWrapper<User>()
.gt(User::getAge, 20)
.like(User::getUsername, "张")
.orderByDesc(User::getCreateTime)
);
// 查询 ID 为 1, 2, 3 或年龄大于 30 的用户
List<User> list = userService.list(
new LambdaQueryWrapper<User>()
.in(User::getId, Arrays.asList(1, 2, 3))
.or()
.gt(User::getAge, 30)
);
六、运行与测试
6.1 启动项目
在 IDEA 中右键 DemoApplication.java → Run,或命令行:
bash
mvn spring-boot:run
6.2 接口测试
bash
# 1. 查询所有用户(自动过滤 deleted=0)
curl http://localhost:8080/api/users
# 2. 根据 ID 查询
curl http://localhost:8080/api/users/1
# 3. 条件查询(模糊匹配用户名,年龄范围)
curl "http://localhost:8080/api/users/search?username=张&minAge=20&maxAge=30"
# 4. 分页查询(第1页,每页2条)
curl "http://localhost:8080/api/users/page?pageNum=1&pageSize=2"
# 5. 新增用户(自动填充 createTime、updateTime、deleted)
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"username":"周八","email":"zhouba@example.com","age":40,"phone":"13800138006"}'
# 6. 更新用户(自动填充 updateTime)
curl -X PUT http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"id":1,"username":"张三(已修改)","age":26}'
# 7. 删除用户(逻辑删除,实际执行 UPDATE deleted=1)
curl -X DELETE http://localhost:8080/api/users/1
# 8. 验证删除后查不到(返回 404)
curl http://localhost:8080/api/users/1
# 9. 批量删除
curl -X DELETE "http://localhost:8080/api/users/batch?ids=2,3"
七、代码生成器(AutoGenerator)
7.1 添加依赖
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
7.2 代码生成器
java
package com.example.demo.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import java.util.Collections;
/**
* MyBatis-Plus 代码生成器
* 根据数据库表结构自动生成 Entity、Mapper、Service、Controller
*/
public class CodeGenerator {
public static void main(String[] args) {
FastAutoGenerator.create(
"jdbc:mysql://localhost:3306/mybatis_plus_demo",
"root",
"your_password" // 修改为你的密码
)
.globalConfig(builder -> {
builder.author("YourName") // 作者
.outputDir(System.getProperty("user.dir") + "/src/main/java") // 输出目录
.disableOpenDir(); // 生成后不打开文件夹
})
.packageConfig(builder -> {
builder.parent("com.example.demo") // 父包名
.moduleName("order") // 模块名(会生成 order 包)
.entity("entity")
.mapper("mapper")
.service("service")
.serviceImpl("service.impl")
.controller("controller")
.pathInfo(Collections.singletonMap(
OutputFile.xml,
System.getProperty("user.dir") + "/src/main/resources/mapper"
));
})
.strategyConfig(builder -> {
builder.addInclude("t_order") // 要生成的表名
// Entity 策略
.entityBuilder()
.enableLombok() // 使用 Lombok
.enableTableFieldAnnotation() // 生成 @TableField
.logicDeleteColumnName("deleted") // 逻辑删除字段
// Controller 策略
.controllerBuilder()
.enableRestStyle(); // 生成 @RestController
})
.templateEngine(new VelocityTemplateEngine())
.execute();
}
}
运行后生成文件:
plain
src/main/java/com/example/demo/order/
├── entity/
│ └── Order.java ← @TableName("t_order") + @TableId + @TableField
├── mapper/
│ └── OrderMapper.java ← extends BaseMapper<Order>
├── service/
│ ├── IOrderService.java ← extends IService<Order>
│ └── impl/
│ └── OrderServiceImpl.java ← extends ServiceImpl<OrderMapper, Order>
└── controller/
└── OrderController.java ← @RestController + CRUD 方法
src/main/resources/mapper/
└── OrderMapper.xml ← 基础 CRUD XML
八、核心知识点总结
| 知识点 | 实现方式 | 效果 |
|---|---|---|
| 零 SQL CRUD | 继承 BaseMapper | 无需手写任何 SQL |
| 链式条件查询 | LambdaQueryWrapper | 类型安全,IDE 提示 |
| 分页查询 | Page 对象 + 分页插件 | 一行代码完成分页 |
| 自动填充 | MetaObjectHandler + @TableField(fill=...) | 创建/更新时间自动维护 |
| 逻辑删除 | @TableLogic + 全局配置 | 删除变更新,查询自动过滤 |
| 代码生成 | AutoGenerator | 根据表结构生成全套代码 |
九、常见错误排查
| 错误 | 原因 | 解决 |
|---|---|---|
Invalid bound statement |
Mapper 未被扫描 | 检查 @MapperScan 或 @Mapper |
| 分页无效 | 未配置分页插件 | 添加 MybatisPlusInterceptor |
| 自动填充不生效 | 未实现 MetaObjectHandler | 创建 MyMetaObjectHandler 并加 @Component |
| 逻辑删除不生效 | 未配置 logic-delete-field | 检查 application.yml 配置 |
| SQL 不打印 | 日志配置未开启 | 添加 log-impl 配置 |
十、面试题自检
| 问题 | 答案 |
|---|---|
| MyBatis-Plus 与 MyBatis 关系? | 增强工具,只做增强不做改变 |
| BaseMapper 提供哪些方法? | 完整 CRUD,包括分页、批量操作 |
| Wrapper 作用? | 条件构造器,链式编程,类型安全 |
| 分页插件怎么配? | MybatisPlusInterceptor + PaginationInnerInterceptor |
| 逻辑删除怎么实现? | @TableLogic + 全局配置,自动转换 |
| 自动填充怎么实现? | MetaObjectHandler + @TableField(fill=...) |
十一、下一步
掌握本文后,建议学习:
- MyBatis-Plus 多数据源:读写分离场景
- MyBatis-Plus 动态表名:分库分表场景
- MyBatis-Plus 插件扩展:自定义拦截器