MyBatis-Plus 从零实战:完整搭建可运行 Demo,BaseMapper 零 SQL、Wrapper 条件构造、分页插件与代码生成器详解

一、前言:为什么需要 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 官网**

  1. 打开 start.spring.io/

  2. 选择:

    • Project: Maven
    • Language: Java
    • Spring Boot: 3.2.0
    • Dependencies: Spring Web, MySQL Driver
  3. 点击 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.javaRun,或命令行:

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 插件扩展:自定义拦截器
相关推荐
用户3721574261351 小时前
Java 处理 PDF 图片:提取 PDF 中的图片,并压缩 PDF 图片体积
java
码事漫谈1 小时前
AI 编程的「三体」架构:OpenSpec + Superpowers + GStack 如何让一个开发者撑起整个研发团队
后端
吃饱了得干活1 小时前
深入解析 OpenFeign:从重试、拦截到负载均衡的全维度实践
后端
onething3651 小时前
Spring Boot + Spring AI 从入门到实战:7天转型计划 Day 6 —— 业务完善 + 会话消息预览
人工智能·后端·全栈
用户3721574261351 小时前
Java 打印 Word 文档:从基础打印到高级设置
java
BingoGo2 小时前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack2 小时前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
IT_陈寒2 小时前
SpringBoot自动配置的坑,我爬了三天才出来
前端·人工智能·后端
ServBay13 小时前
打通 AI 编程本地运维边界,利用 MCP 协议简化环境与服务管理
后端·ai编程·mcp