MyBatis-Plus从入门到实战
一、什么是MyBatis-Plus?
MyBatis-Plus(简称MP)是基于MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。它提供了通用的Mapper和Service,通过少量的配置即可实现单表大部分CRUD操作,极大地提升了开发效率。
1.1 核心特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
- 损耗小:启动即会自动注入基本CRUD,性能基本无损耗
- 强大的CRUD操作:内置通用Mapper、通用Service,通过少量配置即可实现单表大部分CRUD操作
- 支持Lambda形式调用:通过Lambda表达式,方便地编写各类查询条件
- 支持主键自动生成:支持多达4种主键策略(内含分布式唯一ID生成器)
- 内置分页插件:基于MyBatis物理分页,开发者无需关心具体操作
二、环境搭建
2.1 Maven依赖配置
<!-- 在pom.xml中添加MyBatis-Plus依赖 -->
<dependencies>
<!-- MyBatis-Plus核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
<!-- Lombok(可选,用于简化实体类) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>
2.2 配置文件
# application.yml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mp_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
# MyBatis-Plus配置
mybatis-plus:
# Mapper XML文件位置
mapper-locations: classpath*:/mapper/**/*.xml
# 实体类包路径
type-aliases-package: com.example.mp.entity
configuration:
# 驼峰转下划线
map-underscore-to-camel-case: true
# 日志输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 主键类型(AUTO自增,ASSIGN_ID雪花算法)
id-type: auto
# 逻辑删除字段
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
2.3 启动类配置
package com.example.mp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* MyBatis-Plus应用启动类
*/
@SpringBootApplication
@MapperScan("com.example.mp.mapper") // 扫描Mapper接口
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
三、核心功能详解
3.1 CRUD接口
MyBatis-Plus提供了通用的BaseMapper接口,内置了常用的CRUD方法。
3.1.1 实体类定义
package com.example.mp.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户实体类
*/
@Data
@TableName("sys_user") // 指定表名
public class User {
/**
* 主键字段,自增
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
@TableField("username") // 指定字段名
private String username;
/**
* 密码
*/
private String password;
/**
* 邮箱
*/
private String email;
/**
* 年龄
*/
private Integer age;
/**
* 状态:0-禁用,1-启用
*/
private Integer status;
/**
* 逻辑删除标记:0-未删除,1-已删除
*/
@TableLogic
private Integer deleted;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT) // 插入时自动填充
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填充
private LocalDateTime updateTime;
/**
* 版本号(乐观锁)
*/
@Version
private Integer version;
}
3.1.2 Mapper接口
package com.example.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mp.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户Mapper接口
* 继承BaseMapper后自动拥有CRUD方法
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
// BaseMapper已提供以下方法:
// insert(User entity) - 插入
// deleteById(Serializable id) - 根据ID删除
// updateById(User entity) - 根据ID更新
// selectById(Serializable id) - 根据ID查询
// selectList(Wrapper<User> wrapper) - 条件查询
// selectPage(IPage<User> page, Wrapper<User> wrapper) - 分页查询
// ... 更多方法
}
3.1.3 Service层
package com.example.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mp.entity.User;
/**
* 用户Service接口
* 继承IService获得更多批量操作方法
*/
public interface UserService extends IService<User> {
}
package com.example.mp.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mp.entity.User;
import com.example.mp.mapper.UserMapper;
import com.example.mp.service.UserService;
import org.springframework.stereotype.Service;
/**
* 用户Service实现类
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
3.1.4 CRUD使用示例
package com.example.mp.test;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.mp.entity.User;
import com.example.mp.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* CRUD操作示例
*/
@Component
public class CrudExample {
@Autowired
private UserService userService;
/**
* 新增用户
*/
public void insertUser() {
User user = new User();
user.setUsername("zhangsan");
user.setPassword("123456");
user.setEmail("zhangsan@example.com");
user.setAge(25);
user.setStatus(1);
// 插入数据,ID会自动回填
boolean success = userService.save(user);
System.out.println("插入成功:" + success);
System.out.println("用户ID:" + user.getId());
}
/**
* 批量新增
*/
public void insertBatchUsers() {
List<User> users = Arrays.asList(
create_user("lisi", "123456", "lisi@example.com", 28),
create_user("wangwu", "123456", "wangwu@example.com", 30)
);
boolean success = userService.saveBatch(users);
System.out.println("批量插入成功:" + success);
}
/**
* 根据ID删除
*/
public void deleteById() {
boolean success = userService.removeById(1L);
System.out.println("删除成功:" + success);
}
/**
* 批量删除
*/
public void deleteBatchIds() {
boolean success = userService.removeByIds(Arrays.asList(1L, 2L, 3L));
System.out.println("批量删除成功:" + success);
}
/**
* 条件删除
*/
public void deleteByCondition() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", 0).lt("age", 18);
boolean success = userService.remove(wrapper);
System.out.println("条件删除成功:" + success);
}
/**
* 根据ID更新
*/
public void updateById() {
User user = userService.getById(1L);
if (user != null) {
user.setAge(26);
user.setEmail("newemail@example.com");
boolean success = userService.updateById(user);
System.out.println("更新成功:" + success);
}
}
/**
* 条件更新
*/
public void updateByCondition() {
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("status", 1).set("age", 30).set("email", "updated@example.com");
boolean success = userService.update(wrapper);
System.out.println("条件更新成功:" + success);
}
/**
* 根据ID查询
*/
public void selectById() {
User user = userService.getById(1L);
System.out.println("查询结果:" + user);
}
/**
* 批量查询
*/
public void selectBatchIds() {
List<User> users = userService.listByIds(Arrays.asList(1L, 2L, 3L));
users.forEach(System.out::println);
}
/**
* 条件查询
*/
public void selectByCondition() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", 1)
.gt("age", 18)
.likeRight("username", "zhang")
.orderByDesc("create_time");
List<User> users = userService.list(wrapper);
users.forEach(System.out::println);
}
/**
* 分页查询
*/
public void selectPage() {
// 创建分页对象(当前页1,每页10条)
Page<User> page = new Page<>(1, 10);
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", 1).orderByDesc("create_time");
Page<User> result = userService.page(page, wrapper);
System.out.println("总记录数:" + result.getTotal());
System.out.println("总页数:" + result.getPages());
System.out.println("当前页数据:" + result.getRecords());
}
/**
* 统计查询
*/
public void selectCount() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", 1).gt("age", 18);
long count = userService.count(wrapper);
System.out.println("符合条件的人数:" + count);
}
private User create_user(String username, String password, String email, int age) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setEmail(email);
user.setAge(age);
user.setStatus(1);
return user;
}
}
四、条件构造器
4.1 QueryWrapper条件查询
package com.example.mp.wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.mp.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* 条件构造器使用示例
*/
@SpringBootTest
public class WrapperTest {
/**
* 基本条件查询
*/
@Test
public void basicQuery() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// eq: 等于 =
wrapper.eq("username", "zhangsan");
// ne: 不等于 <>
wrapper.ne("status", 0);
// gt: 大于 >
wrapper.gt("age", 18);
// ge: 大于等于 >=
wrapper.ge("age", 18);
// lt: 小于 <
wrapper.lt("age", 60);
// le: 小于等于 <=
wrapper.le("age", 60);
// between: BETWEEN值1 AND值2
wrapper.between("age", 18, 60);
// notBetween: NOT BETWEEN
wrapper.notBetween("age", 0, 18);
// like: LIKE '%值%'
wrapper.like("username", "zhang");
// notLike: NOT LIKE '%值%'
wrapper.notLike("username", "admin");
// likeLeft: LIKE '%值'
wrapper.likeLeft("email", "@example.com");
// likeRight: LIKE '值%'
wrapper.likeRight("username", "zhang");
// isNull: 字段 IS NULL
wrapper.isNull("email");
// isNotNull: 字段 IS NOT NULL
wrapper.isNotNull("email");
// in: 字段 IN (v0, v1, ...)
wrapper.in("status", Arrays.asList(0, 1));
// notIn: 字段 NOT IN (v0, v1, ...)
wrapper.notIn("id", Arrays.asList(1, 2, 3));
// inSql: 字段 IN (SQL语句)
wrapper.inSql("id", "select user_id from user_role where role_id = 1");
// orderBy: ORDER BY 字段
wrapper.orderBy(true, true, "create_time");
// orderByAsc: ORDER BY 字段 ASC
wrapper.orderByAsc("age");
// orderByDesc: ORDER BY 字段 DESC
wrapper.orderByDesc("create_time");
List<User> users = userMapper.selectList(wrapper);
}
/**
* 条件拼接(链式调用)
*/
@Test
public void chainQuery() {
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.eq("status", 1)
.gt("age", 18)
.likeRight("username", "zhang")
.orderByDesc("create_time")
.last("LIMIT 10"); // 最后追加SQL片段
List<User> users = userMapper.selectList(wrapper);
}
/**
* 条件判断(动态条件)
*/
@Test
public void conditionQuery() {
String username = "zhang";
Integer minAge = 18;
Integer maxAge = 60;
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 条件不为空时才拼接
wrapper.eq(username != null, "username", username)
.gt(minAge != null, "age", minAge)
.lt(maxAge != null, "age", maxAge)
.eq("status", 1);
List<User> users = userMapper.selectList(wrapper);
}
/**
* Lambda条件构造器(推荐使用)
*/
@Test
public void lambdaQuery() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 使用Lambda表达式,避免硬编码字段名
wrapper.eq(User::getUsername, "zhangsan")
.gt(User::getAge, 18)
.likeRight(User::getEmail, "zhang")
.orderByDesc(User::getCreateTime);
List<User> users = userMapper.selectList(wrapper);
}
/**
* 只查询部分字段
*/
@Test
public void selectFields() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 只查询id、username、email字段
wrapper.select("id", "username", "email")
.eq("status", 1);
List<User> users = userMapper.selectList(wrapper);
}
}
五、实战案例
5.1 用户管理系统
下面是一个完整的用户管理系统实现,包含用户CRUD、角色关联、权限管理等功能。
5.1.1 数据库设计
-- 用户表
CREATE TABLE sys_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT '密码',
nickname VARCHAR(50) COMMENT '昵称',
email VARCHAR(100) COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
avatar VARCHAR(255) COMMENT '头像',
gender TINYINT DEFAULT 0 COMMENT '性别:0-未知,1-男,2-女',
age INT COMMENT '年龄',
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
deleted TINYINT DEFAULT 0 COMMENT '删除标记:0-未删除,1-已删除',
version INT DEFAULT 0 COMMENT '版本号',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_username (username),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 角色表
CREATE TABLE sys_role (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '角色ID',
role_name VARCHAR(50) NOT NULL COMMENT '角色名称',
role_code VARCHAR(50) NOT NULL UNIQUE COMMENT '角色编码',
description VARCHAR(200) COMMENT '描述',
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
deleted TINYINT DEFAULT 0 COMMENT '删除标记',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
-- 用户角色关联表
CREATE TABLE sys_user_role (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL COMMENT '用户ID',
role_id BIGINT NOT NULL COMMENT '角色ID',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
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='用户角色关联表';
5.1.2 实体类定义
package com.example.mp.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户实体
*/
@Data
@TableName("sys_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String nickname;
private String email;
private String phone;
private String avatar;
private Integer gender;
private Integer age;
private Integer status;
@TableLogic
private Integer deleted;
@Version
private Integer version;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
package com.example.mp.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 角色实体
*/
@Data
@TableName("sys_role")
public class Role {
@TableId(type = IdType.AUTO)
private Long id;
private String roleName;
@TableField("role_code")
private String roleCode;
private String description;
private Integer status;
@TableLogic
private Integer deleted;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
package com.example.mp.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户角色关联实体
*/
@Data
@TableName("sys_user_role")
public class UserRole {
@TableId(type = IdType.AUTO)
private Long id;
private Long userId;
private Long roleId;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
5.1.3 自动填充处理器
package com.example.mp.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 字段自动填充处理器
*/
@Component
public class FieldMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时自动填充
*/
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
/**
* 更新时自动填充
*/
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
5.1.4 Service层实现
package com.example.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mp.entity.User;
import java.util.List;
/**
* 用户Service接口
*/
public interface IUserService extends IService<User> {
/**
* 根据用户名查询用户
*/
User getByUsername(String username);
/**
* 为用户分配角色
*/
boolean assignRoles(Long userId, List<Long> roleIds);
/**
* 查询用户的角色列表
*/
List<Long> getUserRoleIds(Long userId);
/**
* 更新用户状态
*/
boolean updateStatus(Long userId, Integer status);
}
package com.example.mp.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mp.entity.User;
import com.example.mp.entity.UserRole;
import com.example.mp.mapper.UserMapper;
import com.example.mp.service.IUserRoleService;
import com.example.mp.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户Service实现
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private IUserRoleService userRoleService;
@Override
public User getByUsername(String username) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, username);
return this.getOne(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean assignRoles(Long userId, List<Long> roleIds) {
// 先删除用户原有角色
LambdaQueryWrapper<UserRole> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.eq(UserRole::getUserId, userId);
userRoleService.remove(deleteWrapper);
// 批量插入新角色
if (roleIds != null && !roleIds.isEmpty()) {
List<UserRole> userRoles = roleIds.stream()
.map(roleId -> {
UserRole ur = new UserRole();
ur.setUserId(userId);
ur.setRoleId(roleId);
return ur;
})
.collect(Collectors.toList());
return userRoleService.saveBatch(userRoles);
}
return true;
}
@Override
public List<Long> getUserRoleIds(Long userId) {
LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserRole::getUserId, userId);
wrapper.select(UserRole::getRoleId);
return userRoleService.list(wrapper).stream()
.map(UserRole::getRoleId)
.collect(Collectors.toList());
}
@Override
public boolean updateStatus(Long userId, Integer status) {
User user = new User();
user.setId(userId);
user.setStatus(status);
return this.updateById(user);
}
}
5.1.5 Controller层
package com.example.mp.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.mp.entity.User;
import com.example.mp.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 用户Controller
*/
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private IUserService userService;
/**
* 分页查询用户列表
*/
@GetMapping("/page")
public Map<String, Object> page(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String username,
@RequestParam(required = false) Integer status) {
Page<User> page = new Page<>(current, size);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 动态条件
wrapper.likeRight(username != null, User::getUsername, username)
.eq(status != null, User::getStatus, status)
.orderByDesc(User::getCreateTime);
Page<User> result = userService.page(page, wrapper);
Map<String, Object> data = new HashMap<>();
data.put("total", result.getTotal());
data.put("pages", result.getPages());
data.put("current", result.getCurrent());
data.put("records", result.getRecords());
return Map.of("success", true, "data", data);
}
/**
* 根据ID查询用户
*/
@GetMapping("/{id}")
public Map<String, Object> getById(@PathVariable Long id) {
User user = userService.getById(id);
return Map.of("success", true, "data", user);
}
/**
* 新增用户
*/
@PostMapping
public Map<String, Object> save(@RequestBody User user) {
boolean success = userService.save(user);
return Map.of("success", success, "message", success ? "新增成功" : "新增失败");
}
/**
* 更新用户
*/
@PutMapping
public Map<String, Object> update(@RequestBody User user) {
boolean success = userService.updateById(user);
return Map.of("success", success, "message", success ? "更新成功" : "更新失败");
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
public Map<String, Object> delete(@PathVariable Long id) {
boolean success = userService.removeById(id);
return Map.of("success", success, "message", success ? "删除成功" : "删除失败");
}
/**
* 分配角色
*/
@PostMapping("/{userId}/roles")
public Map<String, Object> assignRoles(
@PathVariable Long userId,
@RequestBody List<Long> roleIds) {
boolean success = userService.assignRoles(userId, roleIds);
return Map.of("success", success, "message", success ? "分配成功" : "分配失败");
}
/**
* 查询用户角色
*/
@GetMapping("/{userId}/roles")
public Map<String, Object> getUserRoles(@PathVariable Long userId) {
List<Long> roleIds = userService.getUserRoleIds(userId);
return Map.of("success", true, "data", roleIds);
}
}
5.2 数据交互流程
5.3 分页插件配置
package com.example.mp.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis-Plus配置类
*/
@Configuration
public class MybatisPlusConfig {
/**
* 配置分页插件和乐观锁插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置单页最大限制数量,默认500条,-1不限制
paginationInterceptor.setMaxLimit(1000L);
// 设置请求的页面大于最大页后的操作:true返回首页,false继续请求
paginationInterceptor.setOverflow(false);
// 乐观锁插件
OptimisticLockerInnerInterceptor optimisticLockerInterceptor =
new OptimisticLockerInnerInterceptor();
interceptor.addInnerInterceptor(paginationInterceptor);
interceptor.addInnerInterceptor(optimisticLockerInterceptor);
return interceptor;
}
}
六、高级特性
6.1 代码生成器
MyBatis-Plus提供了强大的代码生成器,可以快速生成Entity、Mapper、Service、Controller等代码。
package com.example.mp.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.FreemarkerTemplateEngine;
import java.sql.Types;
import java.util.Collections;
/**
* MyBatis-Plus代码生成器
*/
public class CodeGenerator {
public static void main(String[] args) {
// 数据库配置
String url = "jdbc:mysql://localhost:3306/mp_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
String username = "root";
String password = "123456";
// 代码生成器配置
FastAutoGenerator.create(url, username, password)
// 全局配置
.globalConfig(builder -> {
builder.author("Your Name") // 作者名
.outputDir(System.getProperty("user.dir") + "/src/main/java") // 输出目录
.commentDate("yyyy-MM-dd") // 注释日期
.enableSwagger() // 开启Swagger模式
.fileOverride(); // 覆盖已生成的文件
})
// 包配置
.packageConfig(builder -> {
builder.parent("com.example.mp") // 父包名
.moduleName("system") // 模块名
.entity("entity") // Entity包名
.mapper("mapper") // Mapper包名
.service("service") // Service包名
.serviceImpl("service.impl") // ServiceImpl包名
.controller("controller") // Controller包名
.pathInfo(Collections.singletonMap(OutputFile.xml,
System.getProperty("user.dir") + "/src/main/resources/mapper")); // Mapper XML路径
})
// 策略配置
.strategyConfig(builder -> {
builder.addInclude("sys_user", "sys_role", "sys_user_role") // 设置要生成的表名
.addTablePrefix("sys_") // 设置过滤表前缀
// Entity策略配置
.entityBuilder()
.enableLombok() // 开启Lombok
.enableTableFieldAnnotation() // 开启字段注解
.logicDeleteColumnName("deleted") // 逻辑删除字段名
.versionColumnName("version") // 乐观锁字段名
.formatFileName("%s"); // Entity文件命名格式
// Controller策略配置
.controllerBuilder()
.enableRestStyle() // 开启@RestController
.formatFileName("%sController"); // Controller文件命名格式
// Service策略配置
.serviceBuilder()
.formatServiceFileName("I%sService") // Service接口文件命名格式
.formatServiceImplFileName("%sServiceImpl"); // ServiceImpl文件命名格式
// Mapper策略配置
.mapperBuilder()
.enableMapperAnnotation() // 开启@Mapper注解
.formatMapperFileName("%sMapper") // Mapper接口文件命名格式
.formatXmlFileName("%sMapper"); // Mapper XML文件命名格式
})
// 模板引擎配置(使用Freemarker)
.templateEngine(new FreememarkerTemplateEngine())
.execute();
}
}
6.2 多租户插件
package com.example.mp.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 多租户配置
*/
@Configuration
public class TenantConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(TenantLineHandler tenantLineHandler) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(tenantLineHandler);
interceptor.addInnerInterceptor(tenantInterceptor);
return interceptor;
}
}
package com.example.mp.handler;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.springframework.stereotype.Component;
/**
* 多租户处理器
*/
@Component
public class MyTenantLineHandler implements TenantLineHandler {
/**
* 获取当前租户ID
*/
@Override
public Expression getTenantId() {
// 从ThreadLocal或其他方式获取当前租户ID
return new LongValue(getCurrentTenantId());
}
/**
* 获取租户字段名
*/
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
/**
* 判断是否忽略该表(系统表不需要租户隔离)
*/
@Override
public boolean ignoreTable(String tableName) {
// 忽略系统表
return "sys_config".equals(tableName) || "sys_dict".equals(tableName);
}
private Long getCurrentTenantId() {
// 实际项目中从登录上下文获取
return 1L;
}
}
七、性能优化
7.1 批量操作优化
package com.example.mp.optimize;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 批量操作优化示例
*/
@Service
public class BatchOperationExample {
private static final int BATCH_SIZE = 1000;
/**
* 分批插入大量数据
*/
public void batchInsert(List<Entity> dataList) {
if (dataList == null || dataList.isEmpty()) {
return;
}
List<Entity> batch = new ArrayList<>(BATCH_SIZE);
for (Entity entity : dataList) {
batch.add(entity);
if (batch.size() >= BATCH_SIZE) {
// 执行批量插入
saveBatch(batch);
batch.clear();
}
}
// 插入剩余数据
if (!batch.isEmpty()) {
saveBatch(batch);
}
}
}
7.2 查询优化建议
- 避免全表扫描:始终使用条件查询,限制查询范围
- 合理使用索引:为常用查询字段添加索引
- 分页查询:大数据量查询必须使用分页
- 字段选择:只查询需要的字段,避免SELECT *
- 批量操作:使用批量方法代替循环单条操作
八、常见问题与解决方案
8.1 逻辑删除问题
问题:查询时自动添加了deleted条件,导致某些查询不符合预期
解决方案:
// 单个查询忽略逻辑删除
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM sys_user WHERE id = #{id}")
User selectByIdIgnoreDeleted(@Param("id") Long id);
}
// 或使用原生SQL
List<User> users = userMapper.selectList(
new QueryWrapper<User>().apply("deleted = 0 OR deleted = 1")
);
8.2 乐观锁失效
问题:更新时版本号没有自动增加
解决方案:
// 确保实体类有@Version注解
@Version
private Integer version;
// 使用updateById方法,不要使用update方法
boolean success = userService.updateById(user);
8.3 主键回填问题
问题:插入后ID为null
解决方案:
// 确保主键配置正确
@TableId(type = IdType.AUTO) // 数据库自增
// 或
@TableId(type = IdType.ASSIGN_ID) // 雪花算法
// 使用实体插入,不要使用Wrapper插入
userService.save(user);
九、总结
MyBatis-Plus是一个强大的MyBatis增强工具 在实际项目中使用MyBatis-Plus时,建议:
- 合理使用代码生成器,减少重复工作
- 优先使用Lambda表达式构建条件,避免硬编码
- 充分利用批量操作提高性能
- 注意事务管理,确保数据一致性