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 时,我们经常需要进行以下重复工作:
- 编写大量的 XML 映射文件,即使是最简单的 CRUD 操作
- 在执行分页查询时,需要手写分页相关的 SQL 语句
- 没有统一的 Service 层封装,导致代码重复
- SQL 语句容易写错,且不同数据库的 SQL 语法存在差异
MyBatis-Plus 的优势
相比于原生 MyBatis,使用 MyBatis-Plus 可以带来以下优势:
- 减少代码量:内置通用 Mapper、Service,单表 CRUD 操作无需编写 SQL
- 提高开发效率:避免了手动编写基础 CRUD 操作,团队开发更加规范高效
- 增强功能特性:内置主键生成、分页、性能分析、全局拦截等功能
- 支持 Lambda 表达式:使用 Lambda 编写查询条件,类型安全,避免字段名称错误
- 多种主键策略:内置多种主键生成策略,支持自定义主键生成器
- 代码生成器:可快速生成 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 没有自动生成。
解决方案:
- 检查实体类中的 ID 字段是否正确配置了
@TableId
注解。 - 确保指定了正确的 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 字段名映射问题
问题:数据库字段名与实体类属性名不一致,导致查询结果映射错误。
解决方案:
- 启用驼峰命名转换:
mybatis-plus:configuration:map-underscore-to-camel-case: true
- 使用
@TableField
注解显式指定映射关系:
@TableField("user_name")
private String userName;
- 对于不需要映射的字段,可以使用
@TableField(exist = false)
排除。
11.3 分页问题
问题:分页查询不起作用或结果不正确。
解决方案:
- 确保正确配置了分页插件:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;
}
- 检查分页参数是否正确:
// 第一页,每页10条
Page<User> page = new Page<>(1, 10);
- 对于复杂的分页查询,可能需要自定义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 逻辑删除问题
问题:逻辑删除配置后,查询结果异常。
解决方案:
- 确保正确配置了逻辑删除字段:
mybatis-plus:global-config:db-config:logic-delete-field: deletedlogic-delete-value: 1logic-not-delete-value: 0
- 实体类中添加对应注解:
@TableLogic
private Integer deleted;
- 如果需要查询被逻辑删除的数据,需要使用自定义SQL:
@Select("SELECT * FROM user WHERE id = #{id}")
User selectByIdIncludeLogicDeleted(Long id);
11.5 复杂查询问题
问题:需要进行复杂连接查询,如多表关联查询。
解决方案:
- 使用自定义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);
- 使用 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>
- 使用多次查询组合结果:
// 在 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 映射文件没有被识别。
解决方案:
- 确保 XML 文件放在正确的目录下,并在配置中指定:
mybatis-plus:mapper-locations: classpath*:/mapper/**/*.xml
- 检查 XML 文件的命名空间是否与 Mapper 接口匹配:
<mapper namespace="com.example.mapper.UserMapper"><!-- 映射内容 -->
</mapper>
- 在使用 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 核心优势
- 简化开发:通过继承 BaseMapper 和 IService,减少大量重复的 CRUD 代码编写。
- 强大的条件构造器:提供了丰富的条件构造方式,支持 Lambda 表达式,类型安全。
- 丰富的功能:内置分页、逻辑删除、自动填充、乐观锁等功能,无需手动实现。
- 性能优化:提供了性能分析插件,帮助开发者优化 SQL。
- 代码生成:快速生成基础代码,提高开发效率。
13.2 最佳实践
13.2.1 Entity 设计
- 使用 Lombok:减少冗余代码,使用
@Data
、@Builder
等注解。 - 规范字段命名:实体类使用驼峰命名,数据库使用下划线命名。
- 基础字段抽象:将通用字段抽取到基类中,如
id
、createTime
、updateTime
等。 - 使用注解标注特殊字段:如
@TableId
、@TableLogic
、@Version
等。
13.2.2 Mapper 设计
- 继承 BaseMapper:优先使用 MyBatis-Plus 提供的通用方法。
- 自定义方法谨慎使用:仅在复杂场景下添加自定义方法,避免冗余。
- XML 与注解结合:简单查询使用注解,复杂查询使用 XML。
13.2.3 Service 设计
- 继承 ServiceImpl:获取丰富的批量操作和链式操作能力。
- 业务逻辑集中:复杂的业务逻辑应当集中在 Service 层,保持 Controller 的简洁。
- 事务管理:在 Service 层管理事务,确保数据一致性。
13.2.4 性能优化
- 避免全表扫描:合理使用索引,避免不必要的全表扫描。
- 减少不必要的字段查询:使用
select()
方法指定需要查询的字段。 - 批量操作:使用批量插入、更新和删除,减少数据库交互次数。
- 合理使用分页:避免一次性查询大量数据,影响性能。
13.2.5 安全性
- 防止SQL注入:使用参数绑定而非字符串拼接 SQL。
- 数据校验:在 DTO 层使用 JSR303 注解进行数据校验。
- 权限控制:实施细粒度的权限控制,保障数据安全。
13.3 进阶学习方向
- 深入理解 MyBatis 原理:MyBatis-Plus 建立在 MyBatis 之上,了解 MyBatis 的工作原理有助于更好地使用 MyBatis-Plus。
- 动态数据源:学习如何在同一应用中连接多个数据库。
- 分库分表:了解如何使用 MyBatis-Plus 配合 Sharding-JDBC 等中间件实现分库分表。
- SQL性能优化:学习如何分析和优化 SQL 性能。
- 缓存策略:学习如何合理使用缓存提高查询性能。
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 核心优势
- 简化开发:通过继承 BaseMapper 和 IService,减少大量重复的 CRUD 代码编写。
- 强大的条件构造器:提供了丰富的条件构造方式,支持 Lambda 表达式,类型安全。
- 丰富的功能:内置分页、逻辑删除、自动填充、乐观锁等功能,无需手动实现。
- 性能优化:提供了性能分析插件,帮助开发者优化 SQL。
- 代码生成:快速生成基础代码,提高开发效率。
13.2 最佳实践
13.2.1 Entity 设计
- 使用 Lombok:减少冗余代码,使用
@Data
、@Builder
等注解。 - 规范字段命名:实体类使用驼峰命名,数据库使用下划线命名。
- 基础字段抽象:将通用字段抽取到基类中,如
id
、createTime
、updateTime
等。 - 使用注解标注特殊字段:如
@TableId
、@TableLogic
、@Version
等。
13.2.2 Mapper 设计
- 继承 BaseMapper:优先使用 MyBatis-Plus 提供的通用方法。
- 自定义方法谨慎使用:仅在复杂场景下添加自定义方法,避免冗余。
- XML 与注解结合:简单查询使用注解,复杂查询使用 XML。
13.2.3 Service 设计
- 继承 ServiceImpl:获取丰富的批量操作和链式操作能力。
- 业务逻辑集中:复杂的业务逻辑应当集中在 Service 层,保持 Controller 的简洁。
- 事务管理:在 Service 层管理事务,确保数据一致性。
13.2.4 性能优化
- 避免全表扫描:合理使用索引,避免不必要的全表扫描。
- 减少不必要的字段查询:使用
select()
方法指定需要查询的字段。 - 批量操作:使用批量插入、更新和删除,减少数据库交互次数。
- 合理使用分页:避免一次性查询大量数据,影响性能。
13.2.5 安全性
- 防止SQL注入:使用参数绑定而非字符串拼接 SQL。
- 数据校验:在 DTO 层使用 JSR303 注解进行数据校验。
- 权限控制:实施细粒度的权限控制,保障数据安全。
13.3 进阶学习方向
- 深入理解 MyBatis 原理:MyBatis-Plus 建立在 MyBatis 之上,了解 MyBatis 的工作原理有助于更好地使用 MyBatis-Plus。
- 动态数据源:学习如何在同一应用中连接多个数据库。
- 分库分表:了解如何使用 MyBatis-Plus 配合 Sharding-JDBC 等中间件实现分库分表。
- SQL性能优化:学习如何分析和优化 SQL 性能。
- 缓存策略:学习如何合理使用缓存提高查询性能。
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;