当前位置: 首页 > news >正文

MyBatis-Plus 详解教程

文章目录

    • 1. MyBatis-Plus 简介
      • 1.1 什么是 MyBatis-Plus?
      • 1.2 为什么要使用 MyBatis-Plus?
        • 传统 MyBatis 的痛点
        • MyBatis-Plus 的优势
      • 1.3 MyBatis-Plus 与 MyBatis 的关系
    • 2. 快速开始
      • 2.1 环境要求
      • 2.2 依赖引入
        • Maven
        • Gradle
      • 2.3 数据库准备
      • 2.4 配置 Spring Boot
      • 2.5 创建实体类
      • 2.6 创建 Mapper 接口
      • 2.7 配置 Spring Boot 启动类
      • 2.8 编写简单测试类
    • 3. 核心功能
      • 3.1 通用 CRUD
        • 3.1.1 插入操作
        • 3.1.2 删除操作
        • 3.1.3 更新操作
        • 3.1.4 查询操作
      • 3.2 条件构造器
        • 3.2.1 QueryWrapper 示例
        • 3.2.2 UpdateWrapper 示例
        • 3.2.3 LambdaQueryWrapper 示例
        • 3.2.4 LambdaUpdateWrapper 示例
      • 3.3 分页查询
        • 3.3.1 配置分页插件
        • 3.3.2 使用分页查询
      • 3.4 自定义 SQL
        • 3.4.1 在 Mapper 接口中定义方法
        • 3.4.2 创建对应的 XML 映射文件
    • 4. 进阶特性
      • 4.1 ActiveRecord 模式
        • 4.1.1 启用 ActiveRecord
        • 4.1.2 使用 ActiveRecord 操作
      • 4.2 逻辑删除
        • 4.2.1 全局配置
        • 4.2.2 实体类配置
        • 4.2.3 使用逻辑删除
      • 4.3 字段自动填充
        • 4.3.1 实体类配置
        • 4.3.2 实现元数据处理器
        • 4.3.3 测试自动填充
      • 4.4 枚举类型处理
        • 4.4.1 定义枚举类
        • 4.4.2 配置枚举包扫描
        • 4.4.3 在实体类中使用枚举
        • 4.4.4 测试枚举类型
      • 4.5 乐观锁插件
        • 4.5.1 数据库添加 version 字段
        • 4.5.2 实体类添加 version 字段
        • 4.5.3 配置乐观锁插件
        • 4.5.4 测试乐观锁
      • 4.6 SQL 性能分析插件
        • 4.6.1 配置性能分析插件
        • 4.6.2 执行SQL查看分析结果
      • 4.7 多租户插件
        • 4.7.1 基于字段的多租户
          • 配置多租户插件
          • 修改实体类
          • 在请求中设置租户ID
    • 5. Service 层封装
      • 5.1 IService 接口和实现
        • 5.1.1 定义 Service 接口
        • 5.1.2 实现 Service 接口
      • 5.2 使用 Service 方法
      • 5.3 批量操作
    • 6. 代码生成器
      • 6.1 引入依赖
      • 6.2 编写代码生成器
      • 6.3 运行代码生成器
    • 7. 动态表名
      • 7.1 配置动态表名插件
      • 7.2 测试动态表名
    • 8. JSON 字段处理
      • 8.1 定义类型处理器
      • 8.2 在实体类中使用 JSON 处理器
      • 8.3 注册类型处理器
    • 9. 高级查询
      • 9.1 聚合查询
      • 9.2 子查询
      • 9.3 复杂条件查询
      • 9.4 动态条件查询
    • 10. 性能优化
      • 10.1 减少不必要的查询字段
      • 10.2 批量操作优化
      • 10.3 避免全表更新和删除
      • 10.4 使用索引
    • 11. 常见问题与解决方案
      • 11.1 ID 生成问题
      • 11.2 字段名映射问题
      • 11.3 分页问题
      • 11.4 逻辑删除问题
      • 11.5 复杂查询问题
      • 11.6 XML 映射文件无法加载
    • 12. 实战案例:构建完整的用户管理系统
      • 12.1 数据库设计
      • 12.2 实体类设计
        • 用户实体
        • 部门实体
        • 角色实体
        • 用户角色关联实体
        • 基础实体
      • 12.3 Mapper 接口
        • 用户 Mapper
        • 用户 Mapper XML
      • 12.4 Service 层
        • 用户 Service 接口
        • 用户 Service 实现
      • 12.5 Controller 层
      • 12.6 DTO 和查询对象
      • 12.7 统一返回结果
      • 12.8 配置文件
    • 13. 总结与最佳实践
      • 13.1 MyBatis-Plus 核心优势
      • 13.2 最佳实践
        • 13.2.1 Entity 设计
        • 13.2.2 Mapper 设计
        • 13.2.3 Service 设计
        • 13.2.4 性能优化
        • 13.2.5 安全性
      • 13.3 进阶学习方向
      • 13.4 总结
    • 附录:常用注解说明
      • @TableName
      • @TableId
      • @TableField
      • @TableLogic
      • @Version
      • @EnumValue
      • @KeySequence
      • @OrderBy
    • 13. 总结与最佳实践
      • 13.1 MyBatis-Plus 核心优势
      • 13.2 最佳实践
        • 13.2.1 Entity 设计
        • 13.2.2 Mapper 设计
        • 13.2.3 Service 设计
        • 13.2.4 性能优化
        • 13.2.5 安全性
      • 13.3 进阶学习方向
      • 13.4 总结
    • 附录:常用注解说明
      • @TableName
      • @TableId
      • @TableField
      • @TableLogic
      • @Version
      • @EnumValue
      • @KeySequence
      • @OrderBy

1. MyBatis-Plus 简介

1.1 什么是 MyBatis-Plus?

MyBatis-Plus(简称 MP)是一个基于 MyBatis 框架的增强工具,它在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

MyBatis-Plus 的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,齐力面对难题。它提供了诸多功能特性,如:

  • 无侵入:只做增强不做改变,引入它不会对现有项目产生影响
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便地编写各类查询条件,无需担心字段写错
  • 支持主键自动生成:支持多种主键策略,可自由配置,完美解决主键问题
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,写分页等同于写基本 List 查询
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

1.2 为什么要使用 MyBatis-Plus?

传统 MyBatis 的痛点

在使用原生 MyBatis 时,我们经常需要进行以下重复工作:

  1. 编写大量的 XML 映射文件,即使是最简单的 CRUD 操作
  2. 在执行分页查询时,需要手写分页相关的 SQL 语句
  3. 没有统一的 Service 层封装,导致代码重复
  4. SQL 语句容易写错,且不同数据库的 SQL 语法存在差异
MyBatis-Plus 的优势

相比于原生 MyBatis,使用 MyBatis-Plus 可以带来以下优势:

  1. 减少代码量:内置通用 Mapper、Service,单表 CRUD 操作无需编写 SQL
  2. 提高开发效率:避免了手动编写基础 CRUD 操作,团队开发更加规范高效
  3. 增强功能特性:内置主键生成、分页、性能分析、全局拦截等功能
  4. 支持 Lambda 表达式:使用 Lambda 编写查询条件,类型安全,避免字段名称错误
  5. 多种主键策略:内置多种主键生成策略,支持自定义主键生成器
  6. 代码生成器:可快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码

1.3 MyBatis-Plus 与 MyBatis 的关系

MyBatis-Plus 是在 MyBatis 的基础上构建的增强工具,其核心功能仍然依赖于 MyBatis。它通过自动注入的通用方法,减少了手动编写 SQL 的工作量,但同时也完全兼容 MyBatis 的所有功能。你可以:

  • 使用 MyBatis-Plus 的通用 Mapper 执行基础 CRUD
  • 使用原生 MyBatis 的方式编写复杂查询
  • 混合使用两者,优势互补

实际上,MyBatis-Plus 底层依然是调用 MyBatis 的API,它只是为 MyBatis 赋予了更多的能力而已。

2. 快速开始

2.1 环境要求

  • JDK 1.8 及以上
  • Maven 或 Gradle
  • Spring Boot 2.0 及以上(本教程以 Spring Boot 为例)

2.2 依赖引入

在 Spring Boot 项目中,我们只需要引入 MyBatis-Plus 的 starter 依赖即可。

Maven
<!-- Maven 项目 -->
<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.28</version>
</dependency>
Gradle
// Gradle 项目
implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.3.1'
implementation 'mysql:mysql-connector-java:8.0.28'

2.3 数据库准备

创建一个简单的用户表作为示例:

CREATE TABLE `user` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`name` varchar(30) DEFAULT NULL COMMENT '姓名',`age` int(11) DEFAULT NULL COMMENT '年龄',`email` varchar(50) DEFAULT NULL COMMENT '邮箱',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',`deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除:0-未删除,1-已删除',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';-- 插入一些测试数据
INSERT INTO `user` (`name`, `age`, `email`) VALUES
('张三', 18, 'zhangsan@example.com'),
('李四', 20, 'lisi@example.com'),
('王五', 28, 'wangwu@example.com'),
('赵六', 21, 'zhaoliu@example.com'),
('孙七', 24, 'sunqi@example.com');

2.4 配置 Spring Boot

application.yml 中配置数据库连接信息:

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/your_database?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: rootpassword: your_password# MyBatis-Plus 配置
mybatis-plus:configuration:# 开启下划线转驼峰map-underscore-to-camel-case: true# 开启SQL语句打印(开发环境使用)log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 全局配置global-config:db-config:# 主键类型id-type: auto# 逻辑删除配置logic-delete-field: deletedlogic-delete-value: 1logic-not-delete-value: 0# XML 映射文件位置mapper-locations: classpath*:/mapper/**/*.xml

2.5 创建实体类

创建对应用户表的实体类:

package com.example.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import lombok.Data;@Data
@TableName("user")
public class User {@TableId(value = "id", type = IdType.AUTO)private Long id;private String name;private Integer age;private String email;private LocalDateTime createTime;private LocalDateTime updateTime;@TableLogicprivate Integer deleted;
}

2.6 创建 Mapper 接口

创建 UserMapper 接口,继承 BaseMapper:

package com.example.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper extends BaseMapper<User> {// 无需编写任何方法,即可获得 CRUD 功能
}

2.7 配置 Spring Boot 启动类

确保 Spring Boot 启动类中添加了 MapperScan 注解:

package com.example;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

2.8 编写简单测试类

使用 Spring Boot 的测试功能编写一个简单的测试类:

package com.example;import com.example.entity.User;
import com.example.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;@SpringBootTest
public class SampleTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelect() {System.out.println("---------- 查询所有用户 ----------");List<User> userList = userMapper.selectList(null);userList.forEach(System.out::println);}@Testpublic void testInsert() {System.out.println("---------- 插入用户 ----------");User user = new User();user.setName("小明");user.setAge(22);user.setEmail("xiaoming@example.com");int result = userMapper.insert(user);System.out.println("插入结果:" + result);System.out.println("插入后,自动获取的ID:" + user.getId());}@Testpublic void testUpdate() {System.out.println("---------- 更新用户 ----------");User user = userMapper.selectById(1);if (user != null) {user.setAge(user.getAge() + 1);int result = userMapper.updateById(user);System.out.println("更新结果:" + result);}}@Testpublic void testDelete() {System.out.println("---------- 删除用户 ----------");int result = userMapper.deleteById(1);System.out.println("删除结果:" + result);}
}

3. 核心功能

3.1 通用 CRUD

通过继承 BaseMapper,你的 Mapper 接口将自动拥有通用的 CRUD 能力。以下是 BaseMapper 提供的常用方法:

3.1.1 插入操作
// 插入一条记录
int insert(T entity);

示例:

User user = new User();
user.setName("小红");
user.setAge(23);
user.setEmail("xiaohong@example.com");// 插入一条记录
int result = userMapper.insert(user);
3.1.2 删除操作
// 根据 ID 删除
int deleteById(Serializable id);// 根据实体(ID)删除
int deleteById(T entity);// 根据 columnMap 条件删除
int deleteByMap(@Param("cm") Map<String, Object> columnMap);// 根据 entity 条件删除
int delete(@Param("ew") Wrapper<T> queryWrapper);// 删除(根据ID 批量删除)
int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);

示例:

// 根据ID删除
userMapper.deleteById(1);// 根据多个ID批量删除
List<Long> ids = Arrays.asList(1L, 2L, 3L);
userMapper.deleteBatchIds(ids);// 根据条件删除
Map<String, Object> map = new HashMap<>();
map.put("age", 18);
userMapper.deleteByMap(map);  // 删除年龄为18的用户// 使用条件构造器
userMapper.delete(new LambdaQueryWrapper<User>().eq(User::getAge, 18).like(User::getName, "张"));
3.1.3 更新操作
// 根据 ID 更新
int updateById(@Param("et") T entity);// 根据 whereEntity 条件,更新记录
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);

示例:

// 根据ID更新
User user = userMapper.selectById(1);
user.setAge(28);
userMapper.updateById(user);// 根据条件更新
User updateUser = new User();
updateUser.setAge(30);// 将所有名字中包含"张"的用户年龄更新为30
userMapper.update(updateUser, new LambdaQueryWrapper<User>().like(User::getName, "张"));// 也可以不创建实体,直接使用UpdateWrapper
userMapper.update(null, new UpdateWrapper<User>().set("age", 35).like("name", "张"));
3.1.4 查询操作
// 根据 ID 查询
T selectById(Serializable id);// 根据 ID 批量查询
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);// 根据 columnMap 条件查询
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);// 根据 entity 条件,查询一条记录
T selectOne(@Param("ew") Wrapper<T> queryWrapper);// 根据 Wrapper 条件,查询总记录数
Long selectCount(@Param("ew") Wrapper<T> queryWrapper);// 根据 entity 条件,查询全部记录
List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);

示例:

// 根据ID查询
User user = userMapper.selectById(1);// 根据多个ID批量查询
List<Long> ids = Arrays.asList(1L, 2L, 3L);
List<User> users = userMapper.selectBatchIds(ids);// 根据条件查询
Map<String, Object> map = new HashMap<>();
map.put("age", 18);
List<User> users = userMapper.selectByMap(map);  // 查询年龄为18的用户// 使用条件构造器查询单条记录
User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getEmail, "zhangsan@example.com"));// 查询符合条件的记录数
Long count = userMapper.selectCount(new LambdaQueryWrapper<User>().gt(User::getAge, 20));  // 查询年龄大于20的用户数量// 条件查询多条记录
List<User> users = userMapper.selectList(new LambdaQueryWrapper<User>().like(User::getName, "张").ge(User::getAge, 20));  // 查询名字包含"张"且年龄大于等于20的用户

3.2 条件构造器

MyBatis-Plus 提供了强大的条件构造器,帮助你构建复杂的 SQL 查询条件。主要有以下几种实现:

  • QueryWrapper:普通查询条件构造器
  • UpdateWrapper:更新条件构造器
  • LambdaQueryWrapper:支持 Lambda 表达式的查询条件构造器
  • LambdaUpdateWrapper:支持 Lambda 表达式的更新条件构造器
3.2.1 QueryWrapper 示例
@Test
public void testQueryWrapper() {QueryWrapper<User> queryWrapper = new QueryWrapper<>();// WHERE name LIKE '%张%' AND age > 20queryWrapper.like("name", "张").gt("age", 20);// WHERE name LIKE '%张%' OR age > 20queryWrapper.like("name", "张").or().gt("age", 20);// WHERE (name LIKE '%张%' AND age < 40) OR email IS NOT NULLqueryWrapper.nested(w -> w.like("name", "张").lt("age", 40)).or().isNotNull("email");// ORDER BY age DESC, id ASCqueryWrapper.orderByDesc("age").orderByAsc("id");// SELECT id, name, age FROM user ...queryWrapper.select("id", "name", "age");List<User> users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);
}
3.2.2 UpdateWrapper 示例
@Test
public void testUpdateWrapper() {UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();// SET name = '小红', age = 30 WHERE name LIKE '%张%'updateWrapper.set("name", "小红").set("age", 30).like("name", "张");userMapper.update(null, updateWrapper);
}
3.2.3 LambdaQueryWrapper 示例
@Test
public void testLambdaQueryWrapper() {LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();// WHERE name LIKE '%张%' AND age > 20wrapper.like(User::getName, "张").gt(User::getAge, 20);// 条件判断String name = "张";wrapper.like(StringUtils.isNotBlank(name), User::getName, name);List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}
3.2.4 LambdaUpdateWrapper 示例
@Test
public void testLambdaUpdateWrapper() {LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();// SET name = '小红', age = 30 WHERE name LIKE '%张%'wrapper.set(User::getName, "小红").set(User::getAge, 30).like(User::getName, "张");userMapper.update(null, wrapper);
}

3.3 分页查询

MyBatis-Plus 内置了分页插件,使用非常简单。

3.3.1 配置分页插件
package com.example.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 {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}
3.3.2 使用分页查询
@Test
public void testPage() {// 创建分页对象,参数分别是:当前页,每页显示条数Page<User> page = new Page<>(1, 2);// 直接传入分页对象即可Page<User> userPage = userMapper.selectPage(page, null);System.out.println("总记录数:" + userPage.getTotal());System.out.println("总页数:" + userPage.getPages());System.out.println("当前页:" + userPage.getCurrent());System.out.println("每页显示条数:" + userPage.getSize());System.out.println("是否有上一页:" + userPage.hasPrevious());System.out.println("是否有下一页:" + userPage.hasNext());// 获取分页数据List<User> records = userPage.getRecords();records.forEach(System.out::println);
}@Test
public void testPageWithCondition() {// 创建分页对象Page<User> page = new Page<>(1, 2);// 构建查询条件LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.ge(User::getAge, 20);  // 年龄大于等于20// 执行带条件的分页查询Page<User> userPage = userMapper.selectPage(page, wrapper);userPage.getRecords().forEach(System.out::println);
}

3.4 自定义 SQL

虽然 MyBatis-Plus 提供了丰富的 CRUD 操作,但在复杂的业务场景下,我们仍然需要自定义 SQL。

3.4.1 在 Mapper 接口中定义方法
@Mapper
public interface UserMapper extends BaseMapper<User> {// 自定义方法@Select("SELECT * FROM user WHERE age > #{minAge}")List<User> selectOlderThan(@Param("minAge") Integer minAge);// 复杂查询可以使用XMLList<User> selectUserWithAddress(Long userId);
}
3.4.2 创建对应的 XML 映射文件

src/main/resources/mapper 目录下创建 UserMapper.xml

<?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.mapper.UserMapper"><select id="selectUserWithAddress" resultType="com.example.entity.User">SELECT u.* FROM user uLEFT JOIN user_address ua ON u.id = ua.user_idWHERE u.id = #{userId}LIMIT 1</select></mapper>

4. 进阶特性

4.1 ActiveRecord 模式

MyBatis-Plus 支持 ActiveRecord 模式,让实体类具有数据库操作能力。

4.1.1 启用 ActiveRecord

让实体类继承 Model 类即可:

@Data
@TableName("user")
public class User extends Model<User> {@TableId(value = "id", type = IdType.AUTO)private Long id;private String name;private Integer age;private String email;@TableLogicprivate Integer deleted;
}
4.1.2 使用 ActiveRecord 操作
@Test
public void testActiveRecord() {User user = new User();user.setName("Active Record");user.setAge(25);user.setEmail("ar@example.com");// 插入boolean success = user.insert();System.out.println("插入结果:" + success);// 查询User found = new User().selectById(user.getId());System.out.println("查询结果:" + found);// 更新found.setAge(28);success = found.updateById();System.out.println("更新结果:" + success);// 删除success = found.deleteById();System.out.println("删除结果:" + success);// 查询所有List<User> users = new User().selectAll();users.forEach(System.out::println);
}

4.2 逻辑删除

逻辑删除是将数据标记为已删除,而不是物理删除。MyBatis-Plus 支持全局设置逻辑删除字段。

4.2.1 全局配置

application.yml 中配置:

mybatis-plus:global-config:db-config:# 逻辑删除字段logic-delete-field: deleted# 已删除值logic-delete-value: 1# 未删除值logic-not-delete-value: 0
4.2.2 实体类配置
@Data
@TableName("user")
public class User {@TableId(value = "id", type = IdType.AUTO)private Long id;// 其他字段...@TableLogicprivate Integer deleted;
}
4.2.3 使用逻辑删除

配置完成后,所有的删除操作都会变成逻辑删除,所有的查询操作都会自动过滤已删除数据。

// 执行逻辑删除,实际上是执行UPDATE语句
userMapper.deleteById(1);// 查询会自动过滤已删除数据
List<User> users = userMapper.selectList(null);

如果想要查询包含已删除数据,可以使用自定义SQL:

@Select("SELECT * FROM user")
List<User> selectAllIncludeDeleted();

4.3 字段自动填充

在实际应用中,某些字段需要在插入或更新时自动填充值,如创建时间、更新时间等。MyBatis-Plus 提供了字段自动填充功能。

4.3.1 实体类配置
@Data
@TableName("user")
public class User {@TableId(value = "id", type = IdType.AUTO)private Long id;private String name;private Integer age;private String email;// 创建时间,插入时自动填充@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;// 更新时间,插入和更新时自动填充@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;@TableLogicprivate Integer deleted;
}
4.3.2 实现元数据处理器
package com.example.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;@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {log.info("start insert fill ...");// 起始版本 3.3.0(推荐)this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());// 或者使用// this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);// this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);}@Overridepublic void updateFill(MetaObject metaObject) {log.info("start update fill ...");// 起始版本 3.3.0(推荐)this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());// 或者使用// this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);}
}
4.3.3 测试自动填充
@Test
public void testAutoFill() {User user = new User();user.setName("测试自动填充");user.setAge(25);user.setEmail("test@example.com");// 插入,createTime 和 updateTime 会自动填充userMapper.insert(user);User found = userMapper.selectById(user.getId());System.out.println("插入后:" + found);// 休眠1秒,以观察时间差异try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 更新,updateTime 会自动更新found.setAge(26);userMapper.updateById(found);User updated = userMapper.selectById(user.getId());System.out.println("更新后:" + updated);
}

4.4 枚举类型处理

MyBatis-Plus 支持将数据库字段映射为 Java 枚举类型。

4.4.1 定义枚举类
package com.example.enums;import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;@Getter
public enum UserStatus {NORMAL(0, "正常"),DISABLED(1, "禁用"),DELETED(2, "已删除");@EnumValue // 标记数据库存的值是codeprivate final int code;private final String desc;UserStatus(int code, String desc) {this.code = code;this.desc = desc;}
}
4.4.2 配置枚举包扫描

application.yml 中配置:

mybatis-plus:# 枚举包扫描路径type-enums-package: com.example.enums
4.4.3 在实体类中使用枚举
@Data
@TableName("user")
public class User {@TableId(value = "id", type = IdType.AUTO)private Long id;private String name;private Integer age;private String email;private UserStatus status; // 使用枚举类型// 其他字段...
}
4.4.4 测试枚举类型
@Test
public void testEnum() {User user = new User();user.setName("枚举测试");user.setAge(30);user.setEmail("enum@example.com");user.setStatus(UserStatus.NORMAL);userMapper.insert(user);User found = userMapper.selectById(user.getId());System.out.println("状态码:" + found.getStatus().getCode());System.out.println("状态描述:" + found.getStatus().getDesc());
}

4.5 乐观锁插件

乐观锁是一种并发控制方法,它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。

4.5.1 数据库添加 version 字段
ALTER TABLE `user` ADD COLUMN `version` INT DEFAULT 1;
4.5.2 实体类添加 version 字段
@Data
@TableName("user")
public class User {@TableId(value = "id", type = IdType.AUTO)private Long id;private String name;private Integer age;private String email;@Version // 乐观锁注解private Integer version;// 其他字段...
}
4.5.3 配置乐观锁插件
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));// 添加乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}
}
4.5.4 测试乐观锁
@Test
public void testOptimisticLocker() {// 查询User user = userMapper.selectById(1);System.out.println("查询到的数据:" + user);// 修改数据user.setAge(user.getAge() + 1);// 更新 (当更新成功, version 会自增)userMapper.updateById(user);// 查询更新后的结果User updated = userMapper.selectById(1);System.out.println("更新后的数据:" + updated);
}@Test
public void testOptimisticLockerFail() {// 线程1查询User user1 = userMapper.selectById(1);// 线程2查询并更新User user2 = userMapper.selectById(1);user2.setAge(user2.getAge() + 1);int result2 = userMapper.updateById(user2);System.out.println("线程2更新结果:" + result2);// 线程1更新,此时版本已变化,更新失败user1.setAge(100);int result1 = userMapper.updateById(user1);System.out.println("线程1更新结果:" + result1);
}

4.6 SQL 性能分析插件

在开发环境下,SQL 性能分析插件可以帮助我们分析 SQL 执行效率。

4.6.1 配置性能分析插件
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加其他插件...// 添加SQL性能分析插件 (开发环境使用)if (SpringProfileUtil.isDev()) {interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());}return interceptor;}
}// 判断环境的工具类
class SpringProfileUtil {public static boolean isDev() {return Arrays.asList(SpringContextHolder.getApplicationContext().getEnvironment().getActiveProfiles()).contains("dev");}
}
4.6.2 执行SQL查看分析结果

执行任何 SQL 操作后,控制台会输出 SQL 性能分析信息。

4.7 多租户插件

多租户是一种软件架构,多个租户共享相同的系统或软件实例。MyBatis-Plus 提供了多租户插件,支持多种策略。

4.7.1 基于字段的多租户
配置多租户插件
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加多租户插件interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {@Overridepublic String getTenantIdColumn() {return "tenant_id";}@Overridepublic boolean ignoreTable(String tableName) {// 不需要租户控制的表return "sys_config".equalsIgnoreCase(tableName);}@Overridepublic Expression getTenantId() {// 获取当前租户IDreturn new StringValue(TenantContextHolder.getTenantId());}}));// 添加其他插件...return interceptor;}
}// 租户上下文,用于存储当前租户ID
class TenantContextHolder {private static final ThreadLocal<String> TENANT_ID = new ThreadLocal<>();public static void setTenantId(String tenantId) {TENANT_ID.set(tenantId);}public static String getTenantId() {return TENANT_ID.get();}public static void clear() {TENANT_ID.remove();}
}
修改实体类
@Data
@TableName("user")
public class User {@TableId(value = "id", type = IdType.AUTO)private Long id;private String name;private Integer age;private String email;private String tenantId; // 租户ID// 其他字段...
}
在请求中设置租户ID
@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserMapper userMapper;@GetMapping("/{tenantId}")public List<User> getUsersByTenant(@PathVariable String tenantId) {// 设置当前租户IDTenantContextHolder.setTenantId(tenantId);try {// 查询数据,会自动追加租户条件return userMapper.selectList(null);} finally {// 清除租户IDTenantContextHolder.clear();}}
}

5. Service 层封装

MyBatis-Plus 提供了 Service 层的封装,进一步简化业务逻辑的编写。

5.1 IService 接口和实现

5.1.1 定义 Service 接口
package com.example.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.example.entity.User;public interface UserService extends IService<User> {// 自定义方法void updateAgeByName(String name, Integer age);
}
5.1.2 实现 Service 接口
package com.example.service.impl;import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Overridepublic void updateAgeByName(String name, Integer age) {LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();wrapper.eq(User::getName, name).set(User::getAge, age);this.update(wrapper);}
}

5.2 使用 Service 方法

Service 接口继承了 IService,提供了丰富的操作方法:

@SpringBootTest
public class ServiceTest {@Autowiredprivate UserService userService;@Testpublic void testSaveBatch() {List<User> users = new ArrayList<>();for (int i = 0; i < 5; i++) {User user = new User();user.setName("批量用户" + i);user.setAge(20 + i);user.setEmail("batch" + i + "@example.com");users.add(user);}// 批量插入boolean success = userService.saveBatch(users);System.out.println("批量插入结果:" + success);}@Testpublic void testChain() {// 链式操作userService.lambdaQuery().eq(User::getAge, 20).like(User::getName, "张").list().forEach(System.out::println);// 链式更新boolean updated = userService.lambdaUpdate().eq(User::getName, "张三").set(User::getAge, 30).update();System.out.println("更新结果:" + updated);}@Testpublic void testCustomMethod() {// 调用自定义方法userService.updateAgeByName("李四", 25);// 查询验证User user = userService.lambdaQuery().eq(User::getName, "李四").one();System.out.println("更新后的用户:" + user);}@Testpublic void testTransaction() {// 测试事务try {userService.updateAgeByName("不存在的用户", 100);// 后续可能会有异常操作,触发事务回滚} catch (Exception e) {System.out.println("操作失败:" + e.getMessage());}}
}

5.3 批量操作

IService 提供了多种批量操作方法:

@Test
public void testBatchOperations() {// 批量查询List<Long> ids = Arrays.asList(1L, 2L, 3L);List<User> users = userService.listByIds(ids);// 批量更新users.forEach(user -> user.setAge(user.getAge() + 5));boolean updated = userService.updateBatchById(users);// 批量保存或更新List<User> mixedUsers = new ArrayList<>();for (int i = 0; i < 5; i++) {User user = new User();if (i < 2) {// 已存在的用户,将会更新user.setId((long) (i + 1));user.setAge(40);}user.setName("SaveOrUpdate" + i);user.setEmail("sou" + i + "@example.com");mixedUsers.add(user);}boolean result = userService.saveOrUpdateBatch(mixedUsers);System.out.println("批量保存或更新结果:" + result);
}

6. 代码生成器

MyBatis-Plus 代码生成器可以快速生成 Entity、Mapper、Service、Controller 等各个模块的代码。

6.1 引入依赖

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.3.1</version>
</dependency><!-- 模板引擎,选择一个 -->
<dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.3</version>
</dependency>

6.2 编写代码生成器

package com.example.generator;import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;import java.sql.Types;
import java.util.Collections;public class CodeGenerator {public static void main(String[] args) {FastAutoGenerator.create("jdbc:mysql://localhost:3306/your_database?serverTimezone=Asia/Shanghai", "root", "password").globalConfig(builder -> {builder.author("Your Name") // 设置作者.enableSwagger() // 开启 swagger 模式.fileOverride() // 覆盖已生成文件.outputDir(System.getProperty("user.dir") + "/src/main/java"); // 指定输出目录}).dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {int typeCode = metaInfo.getJdbcType().TYPE_CODE;if (typeCode == Types.SMALLINT) {// 将数据库中SMALLINT转换为Integerreturn DbColumnType.INTEGER;}return typeRegistry.getColumnType(metaInfo);})).packageConfig(builder -> {builder.parent("com.example") // 设置父包名.moduleName("system") // 设置父包模块名.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/mapper")); // 设置mapperXml生成路径}).strategyConfig(builder -> {builder.addInclude("user", "role") // 设置需要生成的表名.addTablePrefix("t_", "sys_") // 设置过滤表前缀// Entity 策略配置.entityBuilder().enableLombok() // 开启 Lombok.logicDeleteColumnName("deleted") // 逻辑删除字段.enableTableFieldAnnotation() // 开启生成实体时生成字段注解// Mapper 策略配置.mapperBuilder().enableMapperAnnotation() // 开启 @Mapper 注解.formatMapperFileName("%sMapper") // 格式化 mapper 文件名称.formatXmlFileName("%sMapper") // 格式化 xml 文件名称// Service 策略配置.serviceBuilder().formatServiceFileName("%sService") // 格式化 service 接口文件名称.formatServiceImplFileName("%sServiceImpl") // 格式化 service 实现类文件名称// Controller 策略配置.controllerBuilder().enableRestStyle() // 开启生成 @RestController 控制器.formatFileName("%sController"); // 格式化文件名称}).templateEngine(new VelocityTemplateEngine()) // 使用Velocity引擎模板.execute();}
}

6.3 运行代码生成器

直接运行 main 方法,生成代码。生成后你会得到:

  • Entity 类
  • Mapper 接口和 XML
  • Service 接口和实现类
  • Controller 类

7. 动态表名

在某些场景下,我们需要根据不同条件操作不同的表,如分表、租户表等。MyBatis-Plus 提供了动态表名功能。

7.1 配置动态表名插件

@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加动态表名插件DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {// 根据业务动态替换表名,例如表名后缀加上年月if ("user".equals(tableName)) {return tableName + "_" + DateUtil.format(new Date(), "yyyyMM");}return tableName;});interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);// 添加其他插件...return interceptor;}
}

7.2 测试动态表名

@Test
public void testDynamicTableName() {// 假设当前是2023年8月,表名会自动替换为 user_202308List<User> users = userMapper.selectList(null);users.forEach(System.out::println);
}

8. JSON 字段处理

在实际开发中,我们常常需要将 JSON 字段映射为 Java 对象。MyBatis-Plus 提供了自定义类型处理器功能。

8.1 定义类型处理器

package com.example.handler;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;/*** JSON 字段类型处理器* @param <T> 目标类型*/
@MappedTypes({Object.class})
@MappedJdbcTypes({JdbcType.VARCHAR, JdbcType.LONGVARCHAR})
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {private static final ObjectMapper MAPPER = new ObjectMapper();private final Class<T> clazz;public JsonTypeHandler(Class<T> clazz) {if (clazz == null) {throw new IllegalArgumentException("Type argument cannot be null");}this.clazz = clazz;}@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {try {ps.setString(i, MAPPER.writeValueAsString(parameter));} catch (JsonProcessingException e) {throw new SQLException("Error converting JSON to String", e);}}@Overridepublic T getNullableResult(ResultSet rs, String columnName) throws SQLException {return parseJSON(rs.getString(columnName));}@Overridepublic T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return parseJSON(rs.getString(columnIndex));}@Overridepublic T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return parseJSON(cs.getString(columnIndex));}private T parseJSON(String json) {if (json == null) {return null;}try {return MAPPER.readValue(json, clazz);} catch (JsonProcessingException e) {throw new RuntimeException("Error parsing JSON", e);}}
}

8.2 在实体类中使用 JSON 处理器

@Data
@TableName("product")
public class Product {@TableId(value = "id", type = IdType.AUTO)private Long id;private String name;private BigDecimal price;// 使用自定义类型处理器处理 JSON 字段@TableField(typeHandler = JsonTypeHandler.class)private Map<String, Object> attributes;// 或者映射为具体的类@TableField(typeHandler = JsonTypeHandler.class)private ProductSpec specification;
}@Data
public class ProductSpec {private String color;private String size;private Integer weight;private List<String> tags;
}

8.3 注册类型处理器

在 MyBatis 配置中注册类型处理器:

mybatis-plus:configuration:default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandlertype-handlers-package: com.example.handler

9. 高级查询

MyBatis-Plus 支持丰富的高级查询功能。

9.1 聚合查询

@Test
public void testAggregation() {// 查询用户的平均年龄LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.select(User::getAge);List<Object> ages = userMapper.selectObjs(wrapper);double avgAge = ages.stream().mapToInt(obj -> (Integer) obj).average().orElse(0);System.out.println("平均年龄:" + avgAge);// 分组统计各年龄段人数QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.select("age, count(*) as count").groupBy("age");List<Map<String, Object>> result = userMapper.selectMaps(queryWrapper);result.forEach(System.out::println);
}

9.2 子查询

@Test
public void testSubQuery() {// 查询年龄大于平均年龄的用户QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.apply("age > (select avg(age) from user)");List<User> users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);// 或者使用子查询queryWrapper = new QueryWrapper<>();queryWrapper.inSql("id", "select id from user where age > 25");users = userMapper.selectList(queryWrapper);users.forEach(System.out::println);
}

9.3 复杂条件查询

@Test
public void testComplexQuery() {LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();// 构造复杂查询条件wrapper.nested(w -> w.like(User::getName, "张").or().like(User::getName, "李")).and(w -> w.ge(User::getAge, 20).le(User::getAge, 30)).orderByDesc(User::getCreateTime);List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}

9.4 动态条件查询

@Test
public void testDynamicCondition() {// 模拟前端传入的查询参数String name = "张";Integer minAge = 20;Integer maxAge = null;String email = "example.com";// 构建动态条件LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();// 只有当参数不为空时才会加入条件wrapper.like(StringUtils.isNotBlank(name), User::getName, name).ge(minAge != null, User::getAge, minAge).le(maxAge != null, User::getAge, maxAge).like(StringUtils.isNotBlank(email), User::getEmail, email);List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}

10. 性能优化

使用 MyBatis-Plus 时,我们需要注意一些性能优化点。

10.1 减少不必要的查询字段

@Test
public void testSelectFields() {// 只查询需要的字段,减少数据传输量LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.select(User::getId, User::getName, User::getAge);List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}

10.2 批量操作优化

@Test
public void testBatchInsert() {// 批量插入优化List<User> users = new ArrayList<>();for (int i = 0; i < 1000; i++) {User user = new User();user.setName("批量用户" + i);user.setAge(20 + (i % 10));user.setEmail("batch" + i + "@example.com");users.add(user);}// 默认每次提交100条数据userService.saveBatch(users, 100);
}

10.3 避免全表更新和删除

MyBatis-Plus 默认会阻止全表更新和删除操作,但在某些特殊场景下,我们可能需要进行有意义的全表操作。

@Test
public void testSafeUpdate() {try {// 下面这行代码会被阻止执行userMapper.update(new User(), null);} catch (Exception e) {System.out.println("全表更新被阻止:" + e.getMessage());}// 如果确实需要全表更新,可以这样做User updateEntity = new User();updateEntity.setDeleted(0);UpdateWrapper<User> wrapper = new UpdateWrapper<>();wrapper.like("name", "test");userMapper.update(updateEntity, wrapper);
}

10.4 使用索引

确保在常用查询字段上创建适当的索引:

-- 为常用查询字段添加索引
ALTER TABLE `user` ADD INDEX `idx_name` (`name`);
ALTER TABLE `user` ADD INDEX `idx_age` (`age`);
ALTER TABLE `user` ADD INDEX `idx_email` (`email`);

11. 常见问题与解决方案

在使用 MyBatis-Plus 的过程中,可能会遇到各种问题。这里列出一些常见问题及其解决方案。

11.1 ID 生成问题

问题:插入数据时 ID 没有自动生成。

解决方案

  1. 检查实体类中的 ID 字段是否正确配置了 @TableId 注解。
  2. 确保指定了正确的 ID 生成策略。
// 自增策略(依赖数据库的自增功能)
@TableId(value = "id", type = IdType.AUTO)
private Long id;// 雪花算法(适用于分布式系统)
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;// UUID 策略
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;

11.2 字段名映射问题

问题:数据库字段名与实体类属性名不一致,导致查询结果映射错误。

解决方案

  1. 启用驼峰命名转换:
mybatis-plus:configuration:map-underscore-to-camel-case: true
  1. 使用 @TableField 注解显式指定映射关系:
@TableField("user_name")
private String userName;
  1. 对于不需要映射的字段,可以使用 @TableField(exist = false) 排除。

11.3 分页问题

问题:分页查询不起作用或结果不正确。

解决方案

  1. 确保正确配置了分页插件:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;
}
  1. 检查分页参数是否正确:
// 第一页,每页10条
Page<User> page = new Page<>(1, 10);
  1. 对于复杂的分页查询,可能需要自定义SQL:
public interface UserMapper extends BaseMapper<User> {@Select("SELECT u.*, r.name as role_name FROM user u LEFT JOIN role r ON u.id = r.id ${ew.customSqlSegment}")IPage<User> selectUserWithRolePage(Page<UserVO> page, @Param("ew") Wrapper<User> wrapper);
}

11.4 逻辑删除问题

问题:逻辑删除配置后,查询结果异常。

解决方案

  1. 确保正确配置了逻辑删除字段:
mybatis-plus:global-config:db-config:logic-delete-field: deletedlogic-delete-value: 1logic-not-delete-value: 0
  1. 实体类中添加对应注解:
@TableLogic
private Integer deleted;
  1. 如果需要查询被逻辑删除的数据,需要使用自定义SQL:
@Select("SELECT * FROM user WHERE id = #{id}")
User selectByIdIncludeLogicDeleted(Long id);

11.5 复杂查询问题

问题:需要进行复杂连接查询,如多表关联查询。

解决方案

  1. 使用自定义SQL:
@Select("SELECT u.*, d.name as dept_name FROM user u LEFT JOIN department d ON u.id = d.id WHERE u.id = #{id}")
UserVO getUserWithDept(Long id);
  1. 使用 XML 映射文件:
<select id="getUserWithRoles" resultMap="userWithRolesMap">SELECT u.*, r.id as role_id, r.name as role_nameFROM user uLEFT JOIN user_role ur ON u.id = ur.user_idLEFT JOIN role r ON ur.role_id = r.idWHERE u.id = #{userId}AND u.deleted = 0AND r.deleted = 0
</select>
  1. 使用多次查询组合结果:
// 在 Service 层先查询用户,再查询关联数据
public UserVO getUserWithRoles(Long userId) {User user = this.getById(userId);if (user == null) {return null;}List<Role> roles = roleMapper.selectRolesByUserId(userId);UserVO userVO = new UserVO();BeanUtils.copyProperties(user, userVO);userVO.setRoles(roles);return userVO;
}

11.6 XML 映射文件无法加载

问题:自定义的 XML 映射文件没有被识别。

解决方案

  1. 确保 XML 文件放在正确的目录下,并在配置中指定:
mybatis-plus:mapper-locations: classpath*:/mapper/**/*.xml
  1. 检查 XML 文件的命名空间是否与 Mapper 接口匹配:
<mapper namespace="com.example.mapper.UserMapper"><!-- 映射内容 -->
</mapper>
  1. 在使用 Maven 或 Gradle 时,确保 XML 文件会被打包到 classpath 中:
<!-- Maven -->
<build><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource><resource><directory>src/main/resources</directory></resource></resources>
</build>

12. 实战案例:构建完整的用户管理系统

下面我们通过一个实际案例,来展示如何使用 MyBatis-Plus 构建一个完整的用户管理系统。

12.1 数据库设计

-- 用户表
CREATE TABLE `sys_user` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(100) NOT NULL COMMENT '密码',`nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',`email` varchar(100) DEFAULT NULL COMMENT '邮箱',`phone` varchar(20) DEFAULT NULL COMMENT '手机号',`gender` tinyint(1) DEFAULT NULL COMMENT '性别:0-女,1-男',`avatar` varchar(255) DEFAULT NULL COMMENT '头像',`dept_id` bigint(20) DEFAULT NULL COMMENT '部门ID',`status` tinyint(1) DEFAULT '0' COMMENT '状态:0-正常,1-禁用',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',`deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除:0-未删除,1-已删除',PRIMARY KEY (`id`),UNIQUE KEY `uk_username` (`username`),INDEX `idx_dept_id` (`dept_id`),INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';-- 部门表
CREATE TABLE `sys_dept` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '部门ID',`name` varchar(50) NOT NULL COMMENT '部门名称',`parent_id` bigint(20) DEFAULT '0' COMMENT '父部门ID',`ancestors` varchar(100) DEFAULT '' COMMENT '祖级列表',`order_num` int(11) DEFAULT '0' COMMENT '显示顺序',`leader` varchar(50) DEFAULT NULL COMMENT '负责人',`phone` varchar(20) DEFAULT NULL COMMENT '联系电话',`email` varchar(100) DEFAULT NULL COMMENT '邮箱',`status` tinyint(1) DEFAULT '0' COMMENT '状态:0-正常,1-禁用',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',`deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除:0-未删除,1-已删除',PRIMARY KEY (`id`),INDEX `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门表';-- 角色表
CREATE TABLE `sys_role` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',`name` varchar(50) NOT NULL COMMENT '角色名称',`code` varchar(50) NOT NULL COMMENT '角色编码',`description` varchar(255) DEFAULT NULL COMMENT '角色描述',`sort` int(11) DEFAULT '0' COMMENT '排序',`status` tinyint(1) DEFAULT '0' COMMENT '状态:0-正常,1-禁用',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',`deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除:0-未删除,1-已删除',PRIMARY KEY (`id`),UNIQUE KEY `uk_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';-- 用户角色关联表
CREATE TABLE `sys_user_role` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',`user_id` bigint(20) NOT NULL COMMENT '用户ID',`role_id` bigint(20) NOT NULL COMMENT '角色ID',PRIMARY KEY (`id`),UNIQUE KEY `uk_user_role` (`user_id`,`role_id`),INDEX `idx_user_id` (`user_id`),INDEX `idx_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';

12.2 实体类设计

用户实体
@Data
@TableName("sys_user")
public class User extends BaseEntity {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.ASSIGN_ID)private Long id;@NotBlank(message = "用户名不能为空")private String username;@JsonIgnore  // 密码不参与JSON序列化private String password;private String nickName;@Email(message = "邮箱格式不正确")private String email;private String phone;private Integer gender;private String avatar;private Long deptId;private Integer status;@TableField(exist = false)  // 非数据库字段private List<Role> roles;@TableField(exist = false)private Dept dept;
}
部门实体
@Data
@TableName("sys_dept")
public class Dept extends BaseEntity {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.ASSIGN_ID)private Long id;@NotBlank(message = "部门名称不能为空")private String name;private Long parentId;private String ancestors;private Integer orderNum;private String leader;private String phone;private String email;private Integer status;@TableField(exist = false)private List<Dept> children;
}
角色实体
@Data
@TableName("sys_role")
public class Role extends BaseEntity {private static final long serialVersionUID = 1L;@TableId(value = "id", type = IdType.ASSIGN_ID)private Long id;@NotBlank(message = "角色名称不能为空")private String name;@NotBlank(message = "角色编码不能为空")private String code;private String description;private Integer sort;private Integer status;
}
用户角色关联实体
@Data
@TableName("sys_user_role")
public class UserRole {@TableId(value = "id", type = IdType.ASSIGN_ID)private Long id;private Long userId;private Long roleId;
}
基础实体
@Data
public class BaseEntity implements Serializable {private static final long serialVersionUID = 1L;@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;@TableLogicprivate Integer deleted;
}

12.3 Mapper 接口

用户 Mapper
@Mapper
public interface UserMapper extends BaseMapper<User> {@Select("SELECT u.*, d.name as dept_name FROM sys_user u " +"LEFT JOIN sys_dept d ON u.dept_id = d.id " +"WHERE u.id = #{userId}")User getUserWithDept(Long userId);/*** 获取用户及其角色信息*/List<User> getUserWithRoles(@Param("userId") Long userId);
}
用户 Mapper XML
<?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.mapper.UserMapper"><resultMap id="userWithRolesMap" type="com.example.entity.User"><id property="id" column="id"/><result property="username" column="username"/><result property="nickName" column="nick_name"/><result property="email" column="email"/><result property="phone" column="phone"/><result property="gender" column="gender"/><result property="avatar" column="avatar"/><result property="deptId" column="dept_id"/><result property="status" column="status"/><result property="createTime" column="create_time"/><result property="updateTime" column="update_time"/><collection property="roles" ofType="com.example.entity.Role"><id property="id" column="role_id"/><result property="name" column="role_name"/><result property="code" column="role_code"/></collection></resultMap><select id="getUserWithRoles" resultMap="userWithRolesMap">SELECT u.*, r.id as role_id, r.name as role_name, r.code as role_codeFROM sys_user uLEFT JOIN sys_user_role ur ON u.id = ur.user_idLEFT JOIN sys_role r ON ur.role_id = r.idWHERE u.id = #{userId}AND u.deleted = 0AND r.deleted = 0</select></mapper>

12.4 Service 层

用户 Service 接口
public interface UserService extends IService<User> {/*** 获取用户详情,包括部门和角色信息*/User getUserDetail(Long userId);/*** 创建用户,同时关联角色*/boolean createUser(User user, List<Long> roleIds);/*** 更新用户,同时更新角色关联*/boolean updateUser(User user, List<Long> roleIds);/*** 根据部门ID获取用户列表*/List<User> getUsersByDeptId(Long deptId);/*** 根据角色ID获取用户列表*/List<User> getUsersByRoleId(Long roleId);/*** 重置用户密码*/boolean resetPassword(Long userId, String newPassword);/*** 修改用户状态*/boolean changeStatus(Long userId, Integer status);
}
用户 Service 实现
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Autowiredprivate UserRoleService userRoleService;@Autowiredprivate DeptService deptService;@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic User getUserDetail(Long userId) {User user = this.baseMapper.getUserWithRoles(userId);if (user != null && user.getDeptId() != null) {user.setDept(deptService.getById(user.getDeptId()));}return user;}@Overridepublic boolean createUser(User user, List<Long> roleIds) {// 密码加密user.setPassword(passwordEncoder.encode(user.getPassword()));// 保存用户boolean result = this.save(user);// 保存用户角色关联if (result && CollectionUtil.isNotEmpty(roleIds)) {userRoleService.saveUserRoles(user.getId(), roleIds);}return result;}@Overridepublic boolean updateUser(User user, List<Long> roleIds) {// 更新用户基本信息boolean result = this.updateById(user);// 更新用户角色关联if (result && roleIds != null) {// 先删除原来的关联userRoleService.remove(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, user.getId()));// 重新建立关联if (CollectionUtil.isNotEmpty(roleIds)) {userRoleService.saveUserRoles(user.getId(), roleIds);}}return result;}@Overridepublic List<User> getUsersByDeptId(Long deptId) {if (deptId == null) {return Collections.emptyList();}return this.list(new LambdaQueryWrapper<User>().eq(User::getDeptId, deptId));}@Overridepublic List<User> getUsersByRoleId(Long roleId) {if (roleId == null) {return Collections.emptyList();}// 查询拥有该角色的用户IDList<UserRole> userRoles = userRoleService.list(new LambdaQueryWrapper<UserRole>().eq(UserRole::getRoleId, roleId));if (CollectionUtil.isEmpty(userRoles)) {return Collections.emptyList();}// 获取用户ID集合List<Long> userIds = userRoles.stream().map(UserRole::getUserId).collect(Collectors.toList());// 查询用户信息return this.listByIds(userIds);}@Overridepublic boolean resetPassword(Long userId, String newPassword) {User user = new User();user.setId(userId);user.setPassword(passwordEncoder.encode(newPassword));return this.updateById(user);}@Overridepublic boolean changeStatus(Long userId, Integer status) {User user = new User();user.setId(userId);user.setStatus(status);return this.updateById(user);}
}

12.5 Controller 层

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {private final UserService userService;/*** 获取用户列表*/@GetMappingpublic Result<Page<User>> list(UserQuery query, Page<User> page) {LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();// 构建查询条件wrapper.like(StringUtils.isNotBlank(query.getUsername()), User::getUsername, query.getUsername()).like(StringUtils.isNotBlank(query.getNickName()), User::getNickName, query.getNickName()).eq(query.getStatus() != null, User::getStatus, query.getStatus()).eq(query.getDeptId() != null, User::getDeptId, query.getDeptId()).between(query.getBeginTime() != null && query.getEndTime() != null,User::getCreateTime,query.getBeginTime(),query.getEndTime()).orderByDesc(User::getCreateTime);Page<User> userPage = userService.page(page, wrapper);return Result.success(userPage);}/*** 获取用户详情*/@GetMapping("/{id}")public Result<User> detail(@PathVariable Long id) {User user = userService.getUserDetail(id);return Result.success(user);}/*** 创建用户*/@PostMapping@PreAuthorize("hasAuthority('sys:user:add')")public Result<?> create(@RequestBody @Validated UserDTO userDTO) {User user = BeanUtil.copyProperties(userDTO, User.class);boolean result = userService.createUser(user, userDTO.getRoleIds());return result ? Result.success() : Result.failure("创建用户失败");}/*** 更新用户*/@PutMapping("/{id}")@PreAuthorize("hasAuthority('sys:user:edit')")public Result<?> update(@PathVariable Long id, @RequestBody @Validated UserDTO userDTO) {User user = BeanUtil.copyProperties(userDTO, User.class);user.setId(id);boolean result = userService.updateUser(user, userDTO.getRoleIds());return result ? Result.success() : Result.failure("更新用户失败");}/*** 删除用户*/@DeleteMapping("/{id}")@PreAuthorize("hasAuthority('sys:user:delete')")public Result<?> delete(@PathVariable Long id) {if (id.equals(SecurityUtils.getCurrentUserId())) {return Result.failure("不能删除当前登录用户");}boolean result = userService.removeById(id);return result ? Result.success() : Result.failure("删除用户失败");}/*** 修改用户状态*/@PatchMapping("/{id}/status")@PreAuthorize("hasAuthority('sys:user:edit')")public Result<?> changeStatus(@PathVariable Long id, @RequestParam Integer status) {boolean result = userService.changeStatus(id, status);return result ? Result.success() : Result.failure("修改用户状态失败");}/*** 获取当前登录用户信息*/@GetMapping("/me")public Result<User> me() {Long userId = SecurityUtils.getCurrentUserId();User user = userService.getUserDetail(userId);return Result.success(user);}
}

12.6 DTO 和查询对象

@Data
public class UserDTO {@NotBlank(message = "用户名不能为空")private String username;private String password;private String nickName;@Email(message = "邮箱格式不正确")private String email;private String phone;private Integer gender;private String avatar;private Long deptId;private Integer status;private List<Long> roleIds;
}@Data
public class UserQuery extends BaseQuery {private String username;private String nickName;private Integer status;private Long deptId;
}@Data
public class BaseQuery {private LocalDateTime beginTime;private LocalDateTime endTime;
}

12.7 统一返回结果

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {private Integer code;private String message;private T data;public static <T> Result<T> success() {return new Result<>(200, "操作成功", null);}public static <T> Result<T> success(T data) {return new Result<>(200, "操作成功", data);}public static <T> Result<T> success(String message, T data) {return new Result<>(200, message, data);}public static <T> Result<T> failure(String message) {return new Result<>(500, message, null);}public static <T> Result<T> failure(Integer code, String message) {return new Result<>(code, message, null);}
}

12.8 配置文件

server:port: 8080spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/user_management?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: rootpassword: passwordjackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8# MyBatis-Plus 配置
mybatis-plus:configuration:# 开启下划线转驼峰map-underscore-to-camel-case: true# 开启SQL语句打印(开发环境使用)log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 全局配置global-config:db-config:# 主键类型id-type: assign_id# 表前缀table-prefix: sys_# 逻辑删除配置logic-delete-field: deletedlogic-delete-value: 1logic-not-delete-value: 0# XML 映射文件位置mapper-locations: classpath*:/mapper/**/*.xml

13. 总结与最佳实践

13.1 MyBatis-Plus 核心优势

  1. 简化开发:通过继承 BaseMapper 和 IService,减少大量重复的 CRUD 代码编写。
  2. 强大的条件构造器:提供了丰富的条件构造方式,支持 Lambda 表达式,类型安全。
  3. 丰富的功能:内置分页、逻辑删除、自动填充、乐观锁等功能,无需手动实现。
  4. 性能优化:提供了性能分析插件,帮助开发者优化 SQL。
  5. 代码生成:快速生成基础代码,提高开发效率。

13.2 最佳实践

13.2.1 Entity 设计
  1. 使用 Lombok:减少冗余代码,使用 @Data@Builder 等注解。
  2. 规范字段命名:实体类使用驼峰命名,数据库使用下划线命名。
  3. 基础字段抽象:将通用字段抽取到基类中,如 idcreateTimeupdateTime 等。
  4. 使用注解标注特殊字段:如 @TableId@TableLogic@Version 等。
13.2.2 Mapper 设计
  1. 继承 BaseMapper:优先使用 MyBatis-Plus 提供的通用方法。
  2. 自定义方法谨慎使用:仅在复杂场景下添加自定义方法,避免冗余。
  3. XML 与注解结合:简单查询使用注解,复杂查询使用 XML。
13.2.3 Service 设计
  1. 继承 ServiceImpl:获取丰富的批量操作和链式操作能力。
  2. 业务逻辑集中:复杂的业务逻辑应当集中在 Service 层,保持 Controller 的简洁。
  3. 事务管理:在 Service 层管理事务,确保数据一致性。
13.2.4 性能优化
  1. 避免全表扫描:合理使用索引,避免不必要的全表扫描。
  2. 减少不必要的字段查询:使用 select() 方法指定需要查询的字段。
  3. 批量操作:使用批量插入、更新和删除,减少数据库交互次数。
  4. 合理使用分页:避免一次性查询大量数据,影响性能。
13.2.5 安全性
  1. 防止SQL注入:使用参数绑定而非字符串拼接 SQL。
  2. 数据校验:在 DTO 层使用 JSR303 注解进行数据校验。
  3. 权限控制:实施细粒度的权限控制,保障数据安全。

13.3 进阶学习方向

  1. 深入理解 MyBatis 原理:MyBatis-Plus 建立在 MyBatis 之上,了解 MyBatis 的工作原理有助于更好地使用 MyBatis-Plus。
  2. 动态数据源:学习如何在同一应用中连接多个数据库。
  3. 分库分表:了解如何使用 MyBatis-Plus 配合 Sharding-JDBC 等中间件实现分库分表。
  4. SQL性能优化:学习如何分析和优化 SQL 性能。
  5. 缓存策略:学习如何合理使用缓存提高查询性能。

13.4 总结

MyBatis-Plus 作为一款优秀的 ORM 框架,极大地简化了开发过程,提高了开发效率。通过本教程,你已经掌握了 MyBatis-Plus 的核心功能和使用方法,能够在实际项目中熟练应用。

在实际开发中,建议先从基础功能开始,逐步探索高级特性,根据业务需求选择合适的功能。同时,关注官方文档的更新,持续学习新的特性和优化方法。

MyBatis-Plus 的口号是"为简化开发而生",希望它能为你的开发工作带来便利和效率!


附录:常用注解说明

@TableName

指定实体类对应的数据库表名。

@TableName("sys_user")
public class User {// ...
}

@TableId

指定主键字段及生成策略。

@TableId(value = "id", type = IdType.AUTO)
private Long id;

@TableField

指定字段名和其他属性。

@TableField("user_name")
private String userName;

@TableLogic

标识逻辑删除字段。

@TableLogic
private Integer deleted;

@Version

标识乐观锁字段。

@Version
private Integer version;

@EnumValue

标识枚举字段的存储值。

public enum UserStatus {NORMAL(0, "正常"),DISABLED(1, "禁用");@EnumValueprivate final int value;private final String desc;// ...
}

@KeySequence

序列主键策略(Oracle、PostgreSQL等)。

@KeySequence(value = "seq_user", clazz = Long.class)
public class User {// ...
}

@OrderBy

指定默认排序字段。

@OrderBy(asc = false)
private LocalDateTime createTime;logic-not-delete-value: 0# XML 映射文件位置mapper-locations: classpath*:/mapper/**/*.xml

13. 总结与最佳实践

13.1 MyBatis-Plus 核心优势

  1. 简化开发:通过继承 BaseMapper 和 IService,减少大量重复的 CRUD 代码编写。
  2. 强大的条件构造器:提供了丰富的条件构造方式,支持 Lambda 表达式,类型安全。
  3. 丰富的功能:内置分页、逻辑删除、自动填充、乐观锁等功能,无需手动实现。
  4. 性能优化:提供了性能分析插件,帮助开发者优化 SQL。
  5. 代码生成:快速生成基础代码,提高开发效率。

13.2 最佳实践

13.2.1 Entity 设计
  1. 使用 Lombok:减少冗余代码,使用 @Data@Builder 等注解。
  2. 规范字段命名:实体类使用驼峰命名,数据库使用下划线命名。
  3. 基础字段抽象:将通用字段抽取到基类中,如 idcreateTimeupdateTime 等。
  4. 使用注解标注特殊字段:如 @TableId@TableLogic@Version 等。
13.2.2 Mapper 设计
  1. 继承 BaseMapper:优先使用 MyBatis-Plus 提供的通用方法。
  2. 自定义方法谨慎使用:仅在复杂场景下添加自定义方法,避免冗余。
  3. XML 与注解结合:简单查询使用注解,复杂查询使用 XML。
13.2.3 Service 设计
  1. 继承 ServiceImpl:获取丰富的批量操作和链式操作能力。
  2. 业务逻辑集中:复杂的业务逻辑应当集中在 Service 层,保持 Controller 的简洁。
  3. 事务管理:在 Service 层管理事务,确保数据一致性。
13.2.4 性能优化
  1. 避免全表扫描:合理使用索引,避免不必要的全表扫描。
  2. 减少不必要的字段查询:使用 select() 方法指定需要查询的字段。
  3. 批量操作:使用批量插入、更新和删除,减少数据库交互次数。
  4. 合理使用分页:避免一次性查询大量数据,影响性能。
13.2.5 安全性
  1. 防止SQL注入:使用参数绑定而非字符串拼接 SQL。
  2. 数据校验:在 DTO 层使用 JSR303 注解进行数据校验。
  3. 权限控制:实施细粒度的权限控制,保障数据安全。

13.3 进阶学习方向

  1. 深入理解 MyBatis 原理:MyBatis-Plus 建立在 MyBatis 之上,了解 MyBatis 的工作原理有助于更好地使用 MyBatis-Plus。
  2. 动态数据源:学习如何在同一应用中连接多个数据库。
  3. 分库分表:了解如何使用 MyBatis-Plus 配合 Sharding-JDBC 等中间件实现分库分表。
  4. SQL性能优化:学习如何分析和优化 SQL 性能。
  5. 缓存策略:学习如何合理使用缓存提高查询性能。

13.4 总结

MyBatis-Plus 作为一款优秀的 ORM 框架,极大地简化了开发过程,提高了开发效率。通过本教程,你已经掌握了 MyBatis-Plus 的核心功能和使用方法,能够在实际项目中熟练应用。

在实际开发中,建议先从基础功能开始,逐步探索高级特性,根据业务需求选择合适的功能。同时,关注官方文档的更新,持续学习新的特性和优化方法。

MyBatis-Plus 的口号是"为简化开发而生",希望它能为你的开发工作带来便利和效率!


附录:常用注解说明

@TableName

指定实体类对应的数据库表名。

@TableName("sys_user")
public class User {// ...
}

@TableId

指定主键字段及生成策略。

@TableId(value = "id", type = IdType.AUTO)
private Long id;

@TableField

指定字段名和其他属性。

@TableField("user_name")
private String userName;

@TableLogic

标识逻辑删除字段。

@TableLogic
private Integer deleted;

@Version

标识乐观锁字段。

@Version
private Integer version;

@EnumValue

标识枚举字段的存储值。

public enum UserStatus {NORMAL(0, "正常"),DISABLED(1, "禁用");@EnumValueprivate final int value;private final String desc;// ...
}

@KeySequence

序列主键策略(Oracle、PostgreSQL等)。

@KeySequence(value = "seq_user", clazz = Long.class)
public class User {// ...
}

@OrderBy

指定默认排序字段。

@OrderBy(asc = false)
private LocalDateTime createTime;

相关文章:

  • 【专题刷题】双指针(一)
  • 静态站点生成
  • 解决USG5150防火墙web无法连接问题
  • 【AI论文】PixelFlow:基于流的像素空间生成模型
  • 【android bluetooth 协议分析 21】【ble 介绍 1】【什么是RPA】
  • DDS信号发生器设计
  • 自编码网络深度解析:原理、数学推导与实现细节
  • 标易行项目redis内存中放哪些数据
  • linux多线(进)程编程——(7)消息队列
  • 熟悉Linux下的编程
  • MySQL分组查询和子查询
  • secsgem v0.3.0版本使用说明文档
  • 探索 C 与 Java/Kotlin 的语言差异:从指针到高阶函数
  • 深入定制 QSlider——实现精准点击跳转与拖拽区分
  • 用Python手搓一个简单的饭店管理系统(上篇)
  • 依赖注入(DI)与自动装配的深度分析:优势、局限与实践考量
  • 智慧城市:如同为城市装上智能大脑,开启智慧生活
  • 用 Depcheck 去除Vue项目没有用到的依赖
  • GitHub action中的 jq 是什么? 常用方法有哪些
  • 计算机保研机试准备——C++算法题
  • 张小泉:控股股东所持18%股份将被司法拍卖,不会导致控制权变更
  • 全国首个医工交叉“MD+PhD”双博士培养项目在沪启动
  • 中共中央台办、国务院台办在南京举办台商代表座谈会
  • 居然智家:实控人、董事长兼CEO汪林朋被留置、立案,公司经营正常
  • 首季中国经济观察:一季度社融增量超15万亿元传递积极信号
  • 外交部回应美对华关税加征至245%:具体数字可问问美方