【Mybatis-Plus】——第一章 快速入门与核心功能(上)

目录

[1. 快速入门](#1. 快速入门)

[1.1. 环境准备](#1.1. 环境准备)

[1.2. 快速开始](#1.2. 快速开始)

[1.2.1 引入依赖](#1.2.1 引入依赖)

[1.2.2 定义 Mapper](#1.2.2 定义 Mapper)

[1.2.3 测试](#1.2.3 测试)

[1.3. 常见注解](#1.3. 常见注解)

[1.3.1 @TableName](#1.3.1 @TableName)

[1.3.2 @TableId](#1.3.2 @TableId)

[1.3.3 @TableField](#1.3.3 @TableField)

[1.4. 常见配置](#1.4. 常见配置)

[2. 核心功能](#2. 核心功能)

[2.1. 条件构造器](#2.1. 条件构造器)

[2.1.1 QueryWrapper](#2.1.1 QueryWrapper)

[2.1.2 UpdateWrapper](#2.1.2 UpdateWrapper)

[2.1.3 LambdaQueryWrapper](#2.1.3 LambdaQueryWrapper)

[2.2. 自定义 SQL](#2.2. 自定义 SQL)

[2.2.1 基本用法](#2.2.1 基本用法)

[2.2.2 多表关联](#2.2.2 多表关联)

[2.3. Service 接口](#2.3. Service 接口)

[2.3.1 基本用法](#2.3.1 基本用法)

[2.3.2 快速实现 CRUD 接口](#2.3.2 快速实现 CRUD 接口)

[2.3.3 自定义业务逻辑](#2.3.3 自定义业务逻辑)

[2.3.4 Lambda 查询](#2.3.4 Lambda 查询)

[2.3.5 批量新增](#2.3.5 批量新增)


本文介绍了MyBatisPlus的快速入门与核心功能。内容涵盖:

  1. 基础配置(JDK11环境准备、数据库连接配置);

  2. 快速实现CRUD(通过继承BaseMapper简化操作);

  3. 常用注解(@TableName、@TableId、@TableField的用法);

  4. 条件构造器的使用(QueryWrapper/LambdaQueryWrapper实现复杂查询);

  5. 自定义SQL与多表关联方案;

  6. Service层封装(IService接口的CRUD与批处理优化)。

文章通过完整代码示例演示了从基础配置到复杂查询的实现过程,特别强调了通过Wrapper构建动态条件和Lambda表达式避免魔法值的技巧。

1. 快速入门

1.1. 环境准备

创建一个新的项目,配置项目的 JDK 版本为 JDK 11。项目结构如下:

点击项目结构设置:

在弹窗中配置 JDK:

准备数据库表,对应的数据库表结构如下:

application.yaml 中配置数据库连接参数:

java 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: MySQL123
logging:
  level:
    com.example: debug
  pattern:
    dateformat: HH:mm:ss

1.2. 快速开始

实现 User 表的 CRUD,只需要下面几步:

1.2.1 引入依赖

MyBatis Plus 提供了 starter,实现了自动装配功能:

java 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

最终项目依赖如下:

java 复制代码
<dependencies>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
1.2.2 定义 Mapper

MyBatis Plus 提供了 BaseMapper 接口,已实现单表 CRUD:

java 复制代码
package com.example.mp.mapper;
​
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.domain.po.User;
​
public interface UserMapper extends BaseMapper<User> {
}
1.2.3 测试

编写单元测试:

java 复制代码
package com.example.mp.mapper;
​
import com.example.mp.domain.po.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
​
import java.time.LocalDateTime;
import java.util.List;
​
@SpringBootTest
class UserMapperTest {
​
    @Autowired
    private UserMapper userMapper;
​
    @Test
    void testInsert() {
        User user = new User();
        user.setId(5L);
        user.setUsername("Lucy");
        user.setPassword("123");
        user.setPhone("18688990011");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        userMapper.insert(user);
    }
​
    @Test
    void testSelectById() {
        User user = userMapper.selectById(5L);
        System.out.println("user = " + user);
    }
​
    @Test
    void testSelectByIds() {
        List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L, 5L));
        users.forEach(System.out::println);
    }
​
    @Test
    void testUpdateById() {
        User user = new User();
        user.setId(5L);
        user.setBalance(20000);
        userMapper.updateById(user);
    }
​
    @Test
    void testDelete() {
        userMapper.deleteById(5L);
    }
}

运行日志显示生成的 SQL 非常标准:

java 复制代码
DEBUG 15524 --- [main] c.i.mp.mapper.UserMapper.selectById     : ==> Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM user WHERE id=?
DEBUG 15524 --- [main] c.i.mp.mapper.UserMapper.selectById     : ==> Parameters: 5(Long)
DEBUG 15524 --- [main] c.i.mp.mapper.UserMapper.selectById     : <== Total: 1

只需要继承 BaseMapper 就能省去所有单表 CRUD,非常简单!

1.3. 常见注解

MyBatis Plus 根据 PO 实体的信息推断表信息,从而生成 SQL。默认规则:

  • 表名 = 类名(小写)

  • 字段名 = 属性名(驼峰转下划线)

  • 主键字段 = id

但很多情况下,默认规则与实际场景不符,因此 MyBatis Plus 提供了注解声明表信息。

1.3.1 @TableName

指定实体类对应的表名:

java 复制代码
@TableName("user")
public class User {
    private Long id;
    private String name;
}

支持的属性:

属性 类型 默认值 描述
value String "" 表名
schema String "" schema
keepGlobalPrefix boolean false 是否保持使用全局 tablePrefix
resultMap String "" xml 中 resultMap 的 id
autoResultMap boolean false 是否自动构建 resultMap
excludeProperty String\[\] {} 需要排除的属性名
1.3.2 @TableId

指定主键字段:

java 复制代码
@TableName("user")
public class User {
    @TableId
    private Long id;
    private String name;
}

支持的属性:

属性 类型 默认值 描述
value String "" 主键字段名
type Enum IdType.NONE 指定主键类型

IdType 支持的类型:

描述
AUTO 数据库 ID 自增
NONE 未设置主键类型
INPUT insert 前自行 set 主键值
ASSIGN_ID 分配 ID(雪花算法)
ASSIGN_UUID 分配 UUID

常见的三种主键策略:自增、雪花算法、手动输入。

1.3.3 @TableField

指定非主键字段:

java 复制代码
@TableName("user")
public class User {
    @TableId
    private Long id;
    private String name;
    private Integer age;
    @TableField("is_married")
    private Boolean isMarried;
    @TableField("`concat`")
    private String concat;
}

一般情况下不需要添加此注解,特殊情况除外:

  • 字段名与属性名不一致

  • 字段是数据库关键字(需用反引号包裹)

  • 字段不是数据库表字段(exist = false

支持的其他属性:

属性 类型 默认值 描述
value String "" 数据库字段名
exist boolean true 是否为数据库表字段
condition String "" 查询条件
update String "" 更新 set 注入
insertStrategy Enum DEFAULT 插入策略
updateStrategy Enum DEFAULT 更新策略
fill Enum DEFAULT 字段自动填充策略
select boolean true 是否进行 select 查询

1.4. 常见配置

MyBatis Plus 支持基于 yaml 文件的自定义配置:

java 复制代码
mybatis-plus:
  type-aliases-package: com.example.mp.domain.po
  global-config:
    db-config:
      id-type: auto  # 全局 id 类型为自增长

Mapper XML 文件的读取地址配置:

java 复制代码
mybatis-plus:
  mapper-locations: "classpath*:/mapper/**/*.xml"  # 默认值

示例:新建 UserMapper.xml 文件:

java 复制代码
<?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.UserMapper">
    <select id="queryById" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

测试自定义方法:

java 复制代码
@Test
void testQuery() {
    User user = userMapper.queryById(1L);
    System.out.println("user = " + user);
}

2. 核心功能

复杂条件的 SQL 需要用到更高级的功能。

2.1. 条件构造器

BaseMapper 中提供的修改、删除、查询方法除了支持 id 条件外,还支持更复杂的 where 条件。参数中的 Wrapper 就是条件构造的抽象类,其子类关系如图:

Wrapper 的子类 AbstractWrapper 提供了 where 中包含的所有条件构造方法:

QueryWrapperAbstractWrapper 的基础上拓展了一个 select 方法,允许指定查询字段:

UpdateWrapperAbstractWrapper 的基础上拓展了一个 set 方法,允许指定 SQL 中的 SET 部分:

2.1.1 QueryWrapper

无论是修改、删除、查询,都可以使用 QueryWrapper 构建查询条件。

查询示例:查询名字中带 "o",存款大于等于 1000 元的人

java 复制代码
@Test
void testQueryWrapper() {
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            .select("id", "username", "info", "balance")
            .like("username", "o")
            .ge("balance", 1000);
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

更新示例:更新用户名为 jack 的用户余额为 2000

java 复制代码
@Test
void testUpdateByQueryWrapper() {
    QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");
    User user = new User();
    user.setBalance(2000);
    userMapper.update(user, wrapper);
}
2.1.2 UpdateWrapper

BaseMapperupdate 方法只能直接赋值,对于复杂需求(如 balance = balance - 200)需要使用 UpdateWrappersetSql 功能:

java 复制代码
@Test
void testUpdateWrapper() {
    List<Long> ids = List.of(1L, 2L, 4L);
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
            .setSql("balance = balance - 200")
            .in("id", ids);
    userMapper.update(null, wrapper);
}

对应的 SQL:UPDATE user SET balance = balance - 200 WHERE id IN (1, 2, 4)

2.1.3 LambdaQueryWrapper

QueryWrapperUpdateWrapper 在构造条件时需要写死字段名称,会出现字符串魔法值。MyBatis Plus 提供了基于 Lambda 的 Wrapper:

java 复制代码
@Test
void testLambdaQueryWrapper() {
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.lambda()
            .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000);
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

2.2. 自定义 SQL

在某些场景下,SQL 语句最好维护在持久层。MyBatis Plus 提供了自定义 SQL 功能,可以利用 Wrapper 生成查询条件,再结合 Mapper XML 编写 SQL。

2.2.1 基本用法
java 复制代码
@Test
void testCustomWrapper() {
    List<Long> ids = List.of(1L, 2L, 4L);
    QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);
    userMapper.deductBalanceByIds(200, wrapper);
}

UserMapper 中自定义 SQL:

java 复制代码
@Update("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
2.2.2 多表关联

理论上 MyBatis Plus 不支持多表查询,但可以利用 Wrapper 结合自定义 SQL 实现:

java 复制代码
@Test
void testCustomJoinWrapper() {
    QueryWrapper<User> wrapper = new QueryWrapper<User>()
            .in("u.id", List.of(1L, 2L, 4L))
            .eq("a.city", "北京");
    List<User> users = userMapper.queryUserByWrapper(wrapper);
    users.forEach(System.out::println);
}

UserMapper 中定义方法:

java 复制代码
@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew") QueryWrapper<User> wrapper);

或者在 XML 中写 SQL:

java 复制代码
<select id="queryUserByIdAndAddr" resultType="com.example.mp.domain.po.User">
    SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}
</select>

2.3. Service 接口

MyBatis Plus 提供了通用的 Service 接口及默认实现,封装了常用的 service 模板方法。

2.3.1 基本用法

定义 IUserService 接口,继承 IService

java 复制代码
package com.example.mp.service;
​
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mp.domain.po.User;
​
public interface IUserService extends IService<User> {
}

编写 UserServiceImpl 类,继承 ServiceImpl

java 复制代码
package com.example.mp.service.impl;
​
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mp.domain.po.User;
import com.example.mp.mapper.UserMapper;
import com.example.mp.service.IUserService;
import org.springframework.stereotype.Service;
​
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}

项目结构如下:

2.3.2 快速实现 CRUD 接口

引入依赖:

java 复制代码
<!--swagger-->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
    <version>4.1.0</version>
</dependency>
<!--web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

配置 Swagger:

java 复制代码
knife4j:
  enable: true
  openapi:
    title: 用户管理接口文档
    description: "用户管理接口文档"
    email: dev@example.com
    concat: 开发者
    url: https://www.example.com
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - com.example.mp.controller

定义 DTO 和 VO:

java 复制代码
// UserFormDTO
@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {
    @ApiModelProperty("id") private Long id;
    @ApiModelProperty("用户名") private String username;
    @ApiModelProperty("密码") private String password;
    @ApiModelProperty("注册手机号") private String phone;
    @ApiModelProperty("详细信息") private String info;
    @ApiModelProperty("账户余额") private Integer balance;
}
​
// UserVO
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
    @ApiModelProperty("用户id") private Long id;
    @ApiModelProperty("用户名") private String username;
    @ApiModelProperty("详细信息") private String info;
    @ApiModelProperty("使用状态") private Integer status;
    @ApiModelProperty("账户余额") private Integer balance;
}

编写 Controller:

java 复制代码
@Api(tags = "用户管理接口")
@RequiredArgsConstructor
@RestController
@RequestMapping("users")
public class UserController {
​
    private final IUserService userService;
​
    @PostMapping
    @ApiOperation("新增用户")
    public void saveUser(@RequestBody UserFormDTO userFormDTO) {
        User user = BeanUtil.copyProperties(userFormDTO, User.class);
        userService.save(user);
    }
​
    @DeleteMapping("/{id}")
    @ApiOperation("删除用户")
    public void removeUserById(@PathVariable("id") Long userId) {
        userService.removeById(userId);
    }
​
    @GetMapping("/{id}")
    @ApiOperation("根据id查询用户")
    public UserVO queryUserById(@PathVariable("id") Long userId) {
        User user = userService.getById(userId);
        return BeanUtil.copyProperties(user, UserVO.class);
    }
​
    @GetMapping
    @ApiOperation("根据id集合查询用户")
    public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids) {
        List<User> users = userService.listByIds(ids);
        return BeanUtil.copyToList(users, UserVO.class);
    }
}
2.3.3 自定义业务逻辑

实现扣减用户余额的业务,包含业务逻辑处理:

java 复制代码
// Controller
@PutMapping("{id}/deduction/{money}")
@ApiOperation("扣减用户余额")
public void deductBalance(@PathVariable("id") Long id, @PathVariable("money") Integer money) {
    userService.deductBalance(id, money);
}
​
// Service 接口
public interface IUserService extends IService<User> {
    void deductBalance(Long id, Integer money);
}
​
// Service 实现
@Override
public void deductBalance(Long id, Integer money) {
    User user = getById(id);
    if (user == null || user.getStatus() == 2) {
        throw new RuntimeException("用户状态异常");
    }
    if (user.getBalance() < money) {
        throw new RuntimeException("用户余额不足");
    }
    baseMapper.deductMoneyById(id, money);
}
​
// Mapper
@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
void deductMoneyById(@Param("id") Long id, @Param("money") Integer money);
2.3.4 Lambda 查询

定义查询条件实体:

java 复制代码
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
    @ApiModelProperty("用户名关键字") private String name;
    @ApiModelProperty("用户状态") private Integer status;
    @ApiModelProperty("余额最小值") private Integer minBalance;
    @ApiModelProperty("余额最大值") private Integer maxBalance;
}

使用 Lambda 查询:

java 复制代码
@GetMapping("/list")
@ApiOperation("根据条件查询用户")
public List<UserVO> queryUsers(UserQuery query) {
    List<User> users = userService.lambdaQuery()
            .like(query.getName() != null, User::getUsername, query.getName())
            .eq(query.getStatus() != null, User::getStatus, query.getStatus())
            .ge(query.getMinBalance() != null, User::getBalance, query.getMinBalance())
            .le(query.getMaxBalance() != null, User::getBalance, query.getMaxBalance())
            .list();
    return BeanUtil.copyToList(users, UserVO.class);
}
2.3.5 批量新增

逐条插入数据:

java 复制代码
@Test
void testSaveOneByOne() {
    long b = System.currentTimeMillis();
    for (int i = 1; i <= 100000; i++) {
        userService.save(buildUser(i));
    }
    long e = System.currentTimeMillis();
    System.out.println("耗时:" + (e - b));
}

使用批处理:

java 复制代码
@Test
void testSaveBatch() {
    List<User> list = new ArrayList<>(1000);
    long b = System.currentTimeMillis();
    for (int i = 1; i <= 100000; i++) {
        list.add(buildUser(i));
        if (i % 1000 == 0) {
            userService.saveBatch(list);
            list.clear();
        }
    }
    long e = System.currentTimeMillis();
    System.out.println("耗时:" + (e - b));
}

优化批处理性能,修改 JDBC URL 添加 rewriteBatchedStatements=true

java 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true

总结

MyBatis Plus 通过 BaseMapper 简化了单表 CRUD 操作,通过条件构造器实现了复杂查询。掌握这些核心技能,可以大幅提高开发效率。