MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,旨在简化开发、提升效率。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>mybatis-plus-tutorial</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<properties>
<java.version>8</java.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.18</version>
</dependency>
<!-- 代码生成器 -->
<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>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
数据库准备
-- 创建数据库
CREATE DATABASE mybatis_plus_tutorial DEFAULT CHARSET utf8mb4;
-- 使用数据库
USE mybatis_plus_tutorial;
-- 创建用户表
CREATE TABLE user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
age INT,
email VARCHAR(100) NOT NULL,
manager_id BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
version INT DEFAULT 0,
deleted TINYINT DEFAULT 0,
INDEX idx_manager_id (manager_id)
);
-- 插入测试数据
INSERT INTO user (name, age, email, manager_id) VALUES
('张三', 25, 'zhangsan@example.com', NULL),
('李四', 30, 'lisi@example.com', 1);
('王五', 28, 'wangwu@example.com', 1),
('赵六', 32, 'zhaoliu@example.com', 1),
('孙七', 26, 'sunqi@example.com', 2);
-- 建立商品表
CREATE TABLE product (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
category_id BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted TINYINT DEFAULT 0
);
-- 建立订单表
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
status VARCHAR(20) DEFAULT 'PENDING',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted TINYINT DEFAULT 0
);
-- 建立订单明细表
CREATE TABLE order_item (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus_tutorial?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
min-idle: 5
max-active: 20
test-while-idle: true
validation-query: SELECT 1
# MyBatis-Plus 设置
mybatis-plus:
configuration:
# 记录
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 下划线转驼峰
map-underscore-to-camel-case: true
global-config:
db-config:
# 自动主键策略
id-type: auto
# 逻辑删除字段
logic-delete-field: deleted
# 逻辑删除标记(已删除)
logic-delete-value: 1
# 逻辑删除标记(未删除)
logic-not-delete-value: 0
# 字段填充规则
insert-strategy: not_null
update-strategy: not_null
select-strategy: not_null
# Mapper XML 文件位置
mapper-locations: classpath*:mapper/**/*.xml
# 实体类包路径
type-aliases-package: com.example.entity
# 记录设置
logging:
level:
com.example.mapper: debug
root: info
package com.example.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("user")
public class User {
/**
* 主键ID,自动递增
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 名称
*/
@TableField("name")
private String name;
/**
* 年纪
*/
@TableField("age")
private Integer age;
/**
* 电子邮箱
*/
@TableField("email")
private String email;
/**
* 管理者ID
*/
@TableField("manager_id")
private Long managerId;
/**
* 创建日期,自动填充
*/
@TableField(value = "create_time", fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新日期,自动填充
*/
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 版本号,用于乐观锁定
*/
@Version
@TableField("version")
private Integer version;
/**
* 逻辑删除标记
*/
@TableLogic
@TableField("deleted")
private Integer deleted;
}
3. Mapper接口
创建src/main/java/com/example/mapper/UserMapper.java:
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper {
/**
* 定制查询方法
*/
@Select("SELECT * FROM user WHERE age > #{age}")
List selectByAgeGreaterThan(@Param("age") Integer age);
/**
* 定制分页查询
*/
IPage selectUserPage(Page page, @Param("age") Integer age);
}
4. Service层
创建src/main/java/com/example/service/UserService.java:
package com.example.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.entity.User;
public interface UserService extends IService {
/**
* 按年龄查找用户
*/
List getUsersByAge(Integer age);
/**
* 批量保存用户
*/
boolean saveBatch(List users);
}
创建src/main/java/com/example/service/impl/UserServiceImpl.java:
package com.example.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 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; import java.util.List @Service public class UserServiceImpl extends ServiceImplimplements UserService { @Override public List getUsersByAge(Integer age) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.gt("age", age); return this.list(queryWrapper); } @Override public boolean saveBatch(List users) { // 批量保存,自动管理批次尺寸 return this.saveBatch(users, 1000); } } 5. 控制器层 创建src/main/java/com/example/controller/UserController.java: package com.example.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.entity.User; import com.example.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List @RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; /** * 查询全部用户 */ @GetMapping public List getAllUsers() { return userService.list(); } /** * 按ID查询用户 */ @GetMapping("/{id}") public User getUserById(@PathVariable Long id) { return userService.getById(id); } /** * 分页查询用户 */ @GetMapping("/page") public IPage getUsersPage(@RequestParam(defaultValue = "1") Integer current, @RequestParam(defaultValue = "10") Integer size) { Page page = new Page<>(current, size); return userService.page(page); } /** * 条件查询用户 */ @GetMapping("/search") public List searchUsers(@RequestParam(required = false) String name, @RequestParam(required = false) Integer minAge) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (name != null) {
queryWrapper.like("name", name);
}
if (minAge != null) {
queryWrapper.ge("age", minAge);
}
return userService.list(queryWrapper);
}
/**
* 添加用户
*/
@PostMapping
public boolean addUser(@RequestBody User user) {
return userService.save(user);
}
/**
* 修改用户
*/
@PutMapping("/{id}")
public boolean updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return userService.updateById(user);
}
/**
* 移除用户
*/
@DeleteMapping("/{id}")
public boolean deleteUser(@PathVariable Long id) {
return userService.removeById(id);
}
}
6. 启动文件
创建src/main/java/com/example/Application.java:
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);
}
}
7. 字段填充设定
创建src/main/java/com/example/config/MyMetaObjectHandler.java:
package com.example.config;
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 MyMetaObjectHandler 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());
}
}
核心特性
注释解析
@TableName
// 定义表名称
@TableName("sys_user")
public class User {
// ...
}
// 应用共享前缀
@TableName(value = "user", schema = "mybatis_plus")
public class User {
// ...
}
// 忽略字段
@TableName(value = "user", excludeProperty = {"password"})
public class User {
// ...
}
@TableId
public class User {
// 自增主键
@TableId(type = IdType.AUTO)
private Long id;
// 雪花算法ID
@TableId(type = IdType.ASSIGN_ID)
private Long id;
// UUID
@TableId(type = IdType.ASSIGN_UUID)
private String id;
// 手动输入
@TableId(type = IdType.INPUT)
private Long id;
// 无状态(跟随全局)
@TableId(type = IdType.NONE)
private Long id;
}
@TableField
public class User {
// 指定字段名称
@TableField("user_name")
private String name;
// 字段不用于查询
@TableField(select = false)
private String password;
// 字段不在表中存在
@TableField(exist = false)
private String remark;
// 字段填充策略
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
// 构造条件时忽略
@TableField(condition = SqlCondition.LIKE)
private String name;
}
@Version
public class User {
// 乐观锁版本号
@Version
private Integer version;
}
@TableLogic
public class User {
// 逻辑删除字段
@TableLogic
private Integer deleted;
// 自定义逻辑删除值
@TableLogic(value = "0", delval = "1")
private Integer deleted;
}
主键策略详解
public enum IdType {
AUTO(0), // 数据库自动增长
NONE(1), // 无状态,遵循全局
INPUT(2), // 用户手动输入
ASSIGN_ID(3), // 分配ID(主键类型为数字(Long和Integer)或字符串)(自3.3.0起), 使用接口IdentifierGenerator的方法nextId
ASSIGN_UUID(4); // 分配UUID, 主键类型为字符串(自3.3.0起), 使用接口IdentifierGenerator的方法nextUUID
}
全局配置
@Configuration
public class MybatisPlusConfig {
@Bean
public GlobalConfig globalConfig() {
GlobalConfig globalConfig = new GlobalConfig();
// 数据库配置
GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
dbConfig.setIdType(IdType.AUTO);
dbConfig.setTablePrefix("t_");
dbConfig.setLogicDeleteField("deleted");
dbConfig.setLogicDeleteValue("1");
dbConfig.setLogicNotDeleteValue("0");
globalConfig.setDbConfig(dbConfig);
return globalConfig;
}
}
CRUD接口
BaseMapper接口
MyBatis-Plus提供了BaseMapper接口,包含了一系列常用的CRUD操作方法:
public interface BaseMapper<T> extends Mapper<T> {
// 插入数据
int insert(T entity);
// 根据ID删除记录
int deleteById(Serializable id);
// 根据条件删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 批量删除记录
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 按照ID进行修改
int updateById(@Param(Constants.ENTITY) T entity);
// 根据条件进行修改
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
// 按照ID进行查询
T selectById(Serializable id);
// 批量查询
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 按照条件进行查询
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 分页查询
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 统计总数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
IService接口
MyBatis-Plus提供了IService接口,封装了更多的业务操作:
// 保存
boolean save(T entity);
boolean saveBatch(Collection<T> entityList);
boolean saveBatch(Collection<T> entityList, int batchSize);
// 保存或更新
boolean saveOrUpdate(T entity);
boolean saveOrUpdateBatch(Collection<T> entityList);
// 删除
boolean removeById(Serializable id);
boolean removeByIds(Collection<? extends Serializable> idList);
boolean remove(Wrapper<T> queryWrapper);
// 更新
boolean updateById(T entity);
boolean updateBatchById(Collection<T> entityList);
boolean update(Wrapper<T> updateWrapper);
boolean update(T entity, Wrapper<T> updateWrapper);
// 查询
T getById(Serializable id);
List<T> listByIds(Collection<? extends Serializable> idList);
List<T> list();
List<T> list(Wrapper<T> queryWrapper);
IPage<T> page(IPage<T> page);
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 统计
int count();
int count(Wrapper<T> queryWrapper);
使用示例
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
public void crudExample() {
// 1. 保存
User user = new User();
user.setName("张三");
user.setAge(25);
user.setEmail("zhangsan@example.com");
this.save(user); // 自动填充创建时间
// 2. 批量保存
List<User> userList = Arrays.asList(
new User().setName("李四").setAge(30).setEmail("lisi@example.com"),
new User().setName("王五").setAge(28).setEmail("wangwu@example.com")
);
this.saveBatch(userList);
// 3. 修改
User updateUser = new User();
updateUser.setId(1L);
updateUser.setAge(26);
this.updateById(updateUser); // 仅修改非空字段
// 4. 检索
User queryUser = this.getById(1L);
List<User> allUsers = this.list();
// 5. 条件检索
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "张")
.ge("age", 20)
.orderByDesc("create_time");
List<User> users = this.list(queryWrapper);
// 6. 分页检索
Page<User> page = new Page<>(1, 10);
IPage<User> userPage = this.page(page, queryWrapper);
// 7. 移除
this.removeById(1L);
this.removeByIds(Arrays.asList(2L, 3L));
// 8. 计数
int count = this.count();
int adultCount = this.count(new QueryWrapper<User>().ge("age", 18));
}
}
条件构造工具
QueryWrapper
用于构建查询条件:
@Test
public void testQueryWrapper() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 1. 基础条件
queryWrapper.eq("name", "张三") // name = '张三'
.ne("age", 25) // age != 25
.gt("age", 20) // age > 20
.ge("age", 20) // age >= 20
.lt("age", 30) // age < 30
.le("age", 30) // age <= 30
.between("age", 20, 30) // age BETWEEN 20 AND 30
.notBetween("age", 20, 30) // age NOT BETWEEN 20 AND 30
.like("name", "张") // name LIKE '%张%'
.notLike("name", "张") // name NOT LIKE '%张%'
.likeLeft("name", "张") // name LIKE '%张'
.likeRight("name", "张") // name LIKE '张%'
.isNull("manager_id") // manager_id IS NULL
.isNotNull("manager_id") // manager_id IS NOT NULL
.in("age", Arrays.asList(20, 25, 30)) // age IN (20,25,30)
.notIn("age", Arrays.asList(20, 25, 30)) // age NOT IN (20,25,30)
.inSql("id", "select id from user where age > 25") // id IN (select id from user where age > 25)
.notInSql("id", "select id from user where age > 25"); // id NOT IN (select id from user where age > 25)
// 2. 逻辑条件
queryWrapper.and(wrapper -> wrapper.eq("name", "张三").or().eq("name", "李四")) // (name = '张三' OR name = '李四')
.or(wrapper -> wrapper.gt("age", 30).lt("age", 40)); // OR (age > 30 AND age < 40)
// 3. 排序
queryWrapper.orderByAsc("age") // ORDER BY age ASC
.orderByDesc("create_time") // ORDER BY create_time DESC
.orderBy(true, true, "name"); // ORDER BY name ASC
// 4. 分组
queryWrapper.groupBy("age", "name") // GROUP BY age, name
.having("count(*) > {0}", 2); // HAVING count(*) > 2
// 5. 字段选择
queryWrapper.select("id", "name", "age") // SELECT id, name, age
.select(User.class, info -> !info.getColumn().equals("create_time")); // 排除create_time字段
List<User> users = userService.list(queryWrapper);
}
UpdateWrapper
用于构建更新条件:
@Test
public void testUpdateWrapper() {
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// 设置更新字段和条件
updateWrapper.set("age", 26) // SET age = 26
.set("email", "new@example.com") // SET email = 'new@example.com'
.eq("name", "张三") // WHERE name = '张三'
.gt("age", 20); // AND age > 20
userService.update(updateWrapper);
// 或者使用实体+条件
User user = new User();
user.setAge(27);
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("name", "张三");
userService.update(user, wrapper);
}
LambdaQueryWrapper
类型安全的条件构造器:
@Test
public void testLambdaQueryWrapper() {
LambdaQueryWrapper<User> lambdaQuery = new LambdaQueryWrapper<>();
lambdaQuery.eq(User::getName, "张三")
.ge(User::getAge, 20)
.like(User::getEmail, "@example.com")
.orderByDesc(User::getCreateTime);
List<User> users = userService.list(lambdaQuery);
// 简化写法
List<User> users2 = userService.lambdaQuery()
.eq(User::getName, "张三")
.ge(User::getAge, 20)
.list();
}
LambdaUpdateWrapper
类型安全的更新构造器:
@Test
public void testLambdaUpdateWrapper() {
LambdaUpdateWrapper<User> lambdaUpdate = new LambdaUpdateWrapper<>();
lambdaUpdate.set(User::getAge, 26)
.set(User::getEmail, "new@example.com")
.eq(User::getName, "张三");
userService.update(lambdaUpdate);
// 简化写法
userService.lambdaUpdate()
.set(User::getAge, 27)
.eq(User::getName, "张三")
.update();
@Test
public void testAdvancedWrapper() {
// 1. 动态条件
String name = "张三";
Integer minAge = 20;
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.isNotBlank(name), User::getName, name)
.ge(minAge != null, User::getAge, minAge);
// 2. 函数条件
wrapper.apply("date_format(create_time, '%Y-%m-%d') = '2023-01-01'")
.apply("FIND_IN_SET({0}, name)", "张,李");
// 3. 子查询
wrapper.inSql(User::getId, "select user_id from orders where total_amount > 1000")
.exists("select 1 from orders o where o.user_id = user.id");
// 4. 嵌套条件
wrapper.and(w -> w.eq(User::getName, "张三").or().eq(User::getName, "李四"))
.or(w -> w.gt(User::getAge, 30).isNotNull(User::getManagerId));
List<User> users = userService.list(wrapper);
}
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置单页最大限制数量,默认500,-1不受限制
paginationInterceptor.setMaxLimit(1000L);
// 溢出总页数后是否进行处理
paginationInterceptor.setOverflow(false);
// 生成countSql优化掉join
paginationInterceptor.setOptimizeJoin(true);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
@Test
public void testPage() {
// 1. 基本分页
Page<User> page = new Page<>(1, 5); // 第1页,每页5条
IPage<User> userPage = userService.page(page);
System.out.println("总记录数:" + userPage.getTotal());
System.out.println("总页数:" + userPage.getPages());
System.out.println("当前页:" + userPage.getCurrent());
System.out.println("每页大小:" + userPage.getSize());
}
System.out.println("数据:" + userPage.getRecords());
// 2. 条件分页
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age", 20).orderByDesc("create_time");
Page<User> conditionPage = new Page<>(1, 5);
IPage<User> result = userService.page(conditionPage, queryWrapper);
// 3. 不查询总数(适用于大数据量场景)
Page<User> pageWithoutCount = new Page<>(1, 5, false);
IPage<User> resultWithoutCount = userService.page(pageWithoutCount);
自定义分页查询
在Mapper中自定义分页方法:
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 自定义分页查询
* 第一个参数必须是Page类型
*/
IPage<User> selectUserPage(Page<User> page, @Param("age") Integer age);
/**
* 自定义分页查询返回自定义VO
*/
IPage<UserVO> selectUserVOPage(Page<UserVO> page, @Param("age") Integer age);
}
创建对应的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="selectUserPage" resultType="com.example.entity.User">
SELECT * FROM user
WHERE age > #{age}
ORDER BY create_time DESC
</select>
<select id="selectUserVOPage" resultType="com.example.vo.UserVO">
SELECT
u.id,
u.name,
u.age,
u.email,
m.name as managerName
FROM user u
LEFT JOIN user m ON u.manager_id = m.id
WHERE u.age > #{age}
ORDER BY u.create_time DESC
</select>
</mapper>
分页VO类
package com.example.vo;
import lombok.Data;
@Data
public class UserVO {
private Long id;
private String name;
private Integer age;
private String email;
private String managerName;
}
分页使用示例
@Test
public void testCustomPage() {
// 自定义分页查询
Page<User> page = new Page<>(1, 5);
IPage<User> userPage = userMapper.selectUserPage(page, 20);
// 自定义VO分页查询
Page<UserVO> voPage = new Page<>(1, 5);
IPage<UserVO> userVOPage = userMapper.selectUserVOPage(voPage, 20);
}
分页优化
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 针对大容量数据的优化
paginationInterceptor.setOptimizeJoin(true);
// 定制count查询
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize() {
@Override
public String optimize(String countSql) {
// 定制count SQL优化逻辑
return super.optimize(countSql);
}
});
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
基础代码生成器
package com.example.generator;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.Arrays;
public class CodeGenerator {
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 1. 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("generator");
gc.setOpen(false); // 生成后是否开启输出目录
gc.setFileOverride(false); // 是否替代现有文件
gc.setServiceName("%sService"); // service 命名方式,%s为占位符
gc.setServiceImplName("%sServiceImpl"); // service impl 命名方式
gc.setMapperName("%sMapper"); // mapper 命名方式
gc.setXmlName("%sMapper"); // mapper xml 命名方式
gc.setControllerName("%sController"); // controller 命名方式
gc.setDateType(DateType.TIME_PACK); // 使用java.time包
gc.setSwagger2(true); // 实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
// 2. 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus_tutorial?useSSL=false&serverTimezone=UTC");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);
// 3. 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("system"); // 模块名称
pc.setParent("com.example"); // 父包名称
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setController("controller");
mpg.setPackageInfo(pc);
// 4. 定制配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// 无需操作
}
};
// 定制输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 定制配置将被优先输出
focList.add(new FileOutConfig("/templates/mapper.xml.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
// 定制输出文件名称,如果 Entity 设定了前后缀,请留意此处 xml 名称的变化!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 5. 模板配置
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null); // 配置定制输出模板,模板类别:entity, service, controller, mapper, xml
mpg.setTemplate(templateConfig);
// 6. 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel); // 数据库表映射至实体的命名策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel); // 数据库表字段映射至实体的命名策略
strategy.setEntityLombokModel(true); // 启用lombok
strategy.setRestControllerStyle(true); // RESTful API风格控制器
strategy.setInclude("user", "product", "orders", "order_item"); // 需要生成的表名称
strategy.setControllerMappingHyphenStyle(true); // 驼峰转连字符
strategy.setTablePrefix("t_"); // 表前缀
// 字段填充设定
strategy.setTableFillList(Arrays.asList(
new TableFill("create_time", FieldFill.INSERT),
new TableFill("update_time", FieldFill.INSERT_UPDATE)
));
// 乐观锁字段设定
strategy.setVersionFieldName("version");
// 逻辑删除字段设定
strategy.setLogicDeleteFieldName("deleted");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new VelocityTemplateEngine());
mpg.execute();
}
}
高级代码生成器配置
package com.example.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
public class AdvancedCodeGenerator {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus_tutorial", "root", "123456")
.globalConfig(builder -> {
builder.author("generator") // 设定作者
.enableSwagger() // 启用 Swagger 模式
.fileOverride() // 替换已生成的文件
.outputDir(System.getProperty("user.dir") + "/src/main/java"); // 指定输出目录
})
.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", "product") // 设定需要生成的表名
.addTablePrefix("t_", "c_") // 设定过滤表前缀
// 实体策略配置
.entityBuilder()
.enableLombok() // 启用 Lombok 模型
.enableTableFieldAnnotation() // 启用生成实体时生成字段注解
.enableActiveRecord() // 启用 ActiveRecord 模式
.versionColumnName("version") // 乐观锁字段名(数据库)
.versionPropertyName("version") // 乐观锁属性名(实体)
.logicDeleteColumnName("deleted") // 逻辑删除字段名(数据库)
.logicDeletePropertyName("deleted") // 逻辑删除属性名(实体)
.naming(NamingStrategy.underline_to_camel) // 数据库表映射到实体的命名策略
.columnNaming(NamingStrategy.underline_to_camel) // 数据库表字段映射到实体的命名策略
.addTableFills(new Column("create_time", FieldFill.INSERT))
.addTableFills(new Property("updateTime", FieldFill.INSERT_UPDATE))
// 控制器策略配置
.controllerBuilder()
.enableRestStyle() // 启用生成 @RestController 控制器
.enableHyphenStyle() // 启用驼峰转连字符
// 服务策略配置
.serviceBuilder()
.formatServiceFileName("%sService") // 格式化 service 接口文件名称
.formatServiceImplFileName("%sServiceImpl") // 格式化 service 实现类文件名称
// 映射器策略配置
.mapperBuilder()
.enableMapperAnnotation() // 启用 @Mapper 注解
.enableBaseResultMap() // 启用 BaseResultMap 生成
.enableBaseColumnList(); // 启用 BaseColumnList
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用 Freemarker 引擎模板,默认是 Velocity 引擎模板
.execute();
}
}
自定义模板
在src/main/resources/templates/目录下创建自定义模板:
entity.java.vm:
package ${package.Entity};
#foreach($pkg in ${table.importPackages})
import ${pkg};
#end
#if(${swagger2})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
#if(${entityLombokModel})
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
#end
/**
* <p>
* ${table.comment!}
* </p>
*
* @author ${author}
* @since ${date}
*/
#if(${entityLombokModel})
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
#end
#if(${table.convert})
@TableName("${table.name}")
#end
#if(${swagger2})
@ApiModel(value="${entity}对象", description="${table.comment!}")
#end
#if(${superEntityClass})
public class ${entity} extends ${superEntityClass}#if(${activeRecord})<${entity}>#end {
#elseif(${activeRecord})
public class ${entity} extends Model<${entity}> {
#else
public class ${entity} implements Serializable {
#end
private static final long serialVersionUID = 1L;
## ---------- BEGIN 字段循环遍历 ----------
#foreach($field in ${table.fields})
#if(${field.keyFlag})
#set($keyPropertyName=${field.propertyName})
#end
#if("$!field.comment" != "")
#if(${swagger2})
@ApiModelProperty(value = "${field.comment}")
#else
/**
* ${field.comment}
*/
#end
#end
#if(${field.keyFlag})
## 主键
#if(${field.keyIdentityFlag})
@TableId(value = "${field.name}", type = IdType.AUTO)
#elseif(!$null.isNull(${idType}) && "$!idType" != "")
@TableId(value = "${field.name}", type = IdType.${idType})
#elseif(${field.convert})
@TableId("${field.name}")
#end
## 普通字段
#elseif(${field.fill})
## ----- 存在字段填充配置 -----
#if(${field.convert})
@TableField(value = "${field.name}", fill = FieldFill.${field.fill})
#else
@TableField(fill = FieldFill.${field.fill})
#end
#elseif(${field.convert})
@TableField("${field.name}")
#end
## 乐观锁标注
#if(${versionFieldName}==${field.name})
@Version
#end
## 逻辑删除标注
#if(${logicDeleteFieldName}==${field.name})
@TableLogic
#end
private ${field.propertyType} ${field.propertyName};
#end
## ---------- END 字段循环遍历 ----------
#if(!${entityLombokModel})
#foreach($field in ${table.fields})
#if(${field.propertyType.equals("boolean")})
#set($getprefix="is")
#else
#set($getprefix="get")
#end
public ${field.propertyType} ${getprefix}${field.capitalName}() {
return ${field.propertyName};
}
#if(${entityBuilderModel})
public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
#else
public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
#end
this.${field.propertyName} = ${field.propertyName};
#if(${entityBuilderModel})
return this;
#end
}
#end
#end
#if(${entityColumnConstant})
#foreach($field in ${table.fields})
public static final String ${field.name.toUpperCase()} = "${field.name}";
#end
#end
#if(${activeRecord})
@Override
protected Serializable pkVal() {
#if(${keyPropertyName})
return this.${keyPropertyName};
#else
return null;
#end
}
#end
#if(!${entityLombokModel})
@Override
public String toString() {
return "${entity}{ " +
#foreach($field in ${table.fields})
#if($!{foreach.index} == 0)
"${field.propertyName}=" + ${field.propertyName} +
#else
", ${field.propertyName}=" + ${field.propertyName} +
#end
#end
"}";
}
#end
}
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 租户插件
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 从环境上下文中获取租户标识
Long tenantId = TenantContextHolder.getTenantId();
return new LongValue(tenantId);
}
@Override
public String getTenantIdColumn() {
return "tenant_id"; // 租户列名
}
@Override
public boolean ignoreTable(String tableName) {
// 排除多租户影响的表
return "tenant".equals(tableName) || "user".equals(tableName);
}
});
interceptor.addInnerInterceptor(tenantInterceptor);
return interceptor;
}
}
public class TenantContextHolder {
private static final ThreadLocal<Long> TENANT_ID = new ThreadLocal<>();
public static void setTenantId(Long tenantId) {
TENANT_ID.set(tenantId);
}
public static Long getTenantId() {
return TENANT_ID.get();
}
public static void clear() {
TENANT_ID.remove();
}
}
租户拦截器
@Component
public class TenantInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String tenantId = request.getHeader("Tenant-Id");
if (StringUtils.isNotEmpty(tenantId)) {
TenantContextHolder.setTenantId(Long.parseLong(tenantId));
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
TenantContextHolder.clear();
}
}
数据权限
数据权限拦截器
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限插件
DataPermissionInterceptor dataPermissionInterceptor = new DataPermissionInterceptor();
dataPermissionInterceptor.setDataPermissionHandler(new DataPermissionHandler() {
@Override
public Expression getSqlSegment(Expression where, String mappedStatementId) {
// 获取当前用户信息
UserInfo currentUser = getCurrentUser();
if (currentUser.isAdmin()) {
// 管理员可以查看所有数据
return where;
}
// 普通用户只能查看自己的数据
Expression userCondition = new EqualsTo(new Column("user_id"), new LongValue(currentUser.getId()));
if (where == null) {
return userCondition;
} else {
return new AndExpression(where, userCondition);
}
}
});
interceptor.addInnerInterceptor(dataPermissionInterceptor);
return interceptor;
}
private UserInfo getCurrentUser() {
// 从安全上下文获取当前用户
return SecurityContextHolder.getCurrentUser();
}
}
性能分析
性能分析插件
@Configuration
@Profile({"dev", "test"}) // 只在开发和测试环境启用
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 性能分析插件
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1000); // 设定SQL执行最长时间,超时则中断
performanceInterceptor.setFormat(true); // 整理SQL格式
performanceInterceptor.setWriteInLog(true); // 记录到日志
interceptor.addInnerInterceptor(performanceInterceptor);
return interceptor;
}
}
SQL执行分析
@Component
@Slf4j
public class SqlExecutionAnalyzer implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = invocation.proceed();
return result;
} finally {
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
if (executionTime > 1000) {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]);
log.warn("慢SQL检测 - {}ms - {}", executionTime, boundSql.getSql());
}
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 配置属性
}
}
最佳实践
1. 项目架构
src/
├── main/
│ ├── java/
│ │ └── com/example/
│ │ ├── config/ # 配置类
│ │ ├── controller/ # 控制器
│ │ ├── entity/ # 实体类
│ │ ├── mapper/ # Mapper接口
│ │ ├── service/ # 服务层
│ │ │ └── impl/ # 服务实现
│ │ ├── vo/ # 视图对象
│ │ ├── dto/ # 数据传输对象
│ │ └── util/ # 工具类
│ └── resources/
│ ├── mapper/ # Mapper XML文件
│ └── application.yml # 配置文件
2. 实体类设计准则
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_user")
@ApiModel(value = "用户对象", description = "系统用户表")
public class User extends BaseEntity {
@ApiModelProperty(value = "用户ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "用户名")
@TableField("username")
private String username;
@ApiModelProperty(value = "密码")
@TableField(value = "password", select = false)
private String password;
@ApiModelProperty(value = "邮箱")
@TableField("email")
private String email;
@ApiModelProperty(value = "电话号码")
@TableField("phone")
private String phone;
@ApiModelProperty(value = "状态:0-禁用,1-启用")
@TableField("status")
private Integer status;
@ApiModelProperty(value = "部门标识")
@TableField("dept_id")
private Long deptId;
@ApiModelProperty(value = "角色集合")
@TableField(exist = false)
private List<Role> roles;
}
3. Service层设计规范
// 接口定义
public interface UserService extends IService<User> {
/**
* 分页检索用户列表
*/
IPage<UserVO> pageUsers(UserQueryDTO queryDTO);
/**
* 按用户名检索用户
*/
User getByUsername(String username);
/**
* 重置用户密码
*/
boolean resetPassword(Long userId, String newPassword);
/**
* 更新用户状态
*/
boolean updateStatus(Long userId, Integer status);
}
// 实现类
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
@Transactional(readOnly = true)
public IPage<UserVO> pageUsers(UserQueryDTO queryDTO) {
Page<User> page = new Page<>(queryDTO.getCurrent(), queryDTO.getSize());
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(queryDTO.getUsername()), User::getUsername, queryDTO.getUsername())
.eq(queryDTO.getStatus() != null, User::getStatus, queryDTO.getStatus())
.eq(queryDTO.getDeptId() != null, User::getDeptId, queryDTO.getDeptId())
.between(queryDTO.getStartTime() != null && queryDTO.getEndTime() != null,
User::getCreateTime, queryDTO.getStartTime(), queryDTO.getEndTime())
.orderByDesc(User::getCreateTime);
IPage<User> userPage = this.page(page, wrapper);
// 转换为VO
return userPage.convert(this::convertToVO);
}
@Override
@Transactional(readOnly = true)
public User getByUsername(String username) {
return this.getOne(new LambdaQueryWrapper<User>().eq(User::getUsername, username));
}
@Override
public boolean resetPassword(Long userId, String newPassword) {
User user = new User();
user.setId(userId);
user.setPassword(BCrypt.hashpw(newPassword, BCrypt.gensalt()));
return this.updateById(user);
}
@Override
public boolean updateStatus(Long userId, Integer status) {
return this.lambdaUpdate()
.set(User::getStatus, status)
.eq(User::getId, userId)
.update();
}
private UserVO convertToVO(User user) {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
// 处理特别字段转换
return vo;
}
}
4. 控制器层设计规范
@RestController
@RequestMapping("/api/users")
@Api(tags = "用户管理")
@Validated
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/page")
@ApiOperation("分页检索用户")
public Result> pageUsers(@Valid UserQueryDTO queryDTO) {
IPage page = userService.pageUsers(queryDTO);
return Result.success(page);
}
@GetMapping("/{id}")
@ApiOperation("依据ID检索用户")
public Result getUser(@PathVariable Long id) {
User user = userService.getById(id);
if (user == null) {
return Result.error("用户不存在");
}
UserVO vo = convertToVO(user);
return Result.success(vo);
}
@PostMapping
@ApiOperation("添加新用户")
public Result addUser(@Valid @RequestBody UserSaveDTO saveDTO) {
User user = convertToEntity(saveDTO);
boolean success = userService.save(user);
return Result.success(success);
}
@PutMapping("/{id}")
@ApiOperation("修改用户信息")
public Result updateUser(@PathVariable Long id, @Valid @RequestBody UserSaveDTO saveDTO) {
User user = convertToEntity(saveDTO);
user.setId(id);
boolean success = userService.updateById(user);
return Result.success(success);
}
@DeleteMapping("/{id}")
@ApiOperation("移除用户")
public Result deleteUser(@PathVariable Long id) {
boolean success = userService.removeById(id);
return Result.success(success);
}
@PutMapping("/{id}/status")
@ApiOperation("更新用户状态")
public Result updateStatus(@PathVariable Long id, @RequestParam Integer status) {
boolean success = userService.updateStatus(id, status);
return Result.success(success);
}
}
5. 批量操作优化
@Service
public class BatchOperationService {
@Autowired
private UserService userService;
/**
* 批量保存用户
*/
@Transactional(rollbackFor = Exception.class)
public boolean batchSaveUsers(List<User> users) {
// 分段处理,防止内存溢出
int batchSize = 1000;
return userService.saveBatch(users, batchSize);
}
/**
* 大规模更新用户状态
*/
@Transactional(rollbackFor = Exception.class)
public boolean batchUpdateStatus(List<Long> userIds, Integer status) {
if (CollectionUtils.isEmpty(userIds)) {
return false;
}
return userService.lambdaUpdate()
.set(User::getStatus, status)
.in(User::getId, userIds)
.update();
}
/**
* 大规模移除用户
*/
@Transactional(rollbackFor = Exception.class)
public boolean batchDeleteUsers(List<Long> userIds) {
if (CollectionUtils.isEmpty(userIds)) {
return false;
}
// 逻辑移除
return userService.removeByIds(userIds);
}
问题:数据库字段名称与实体属性名称不符
@TableField("user_name")
private String userName;
mybatis-plus: configuration: map-underscore-to-camel-case: true
@TableName(value = "user", resultMap = "userResultMap")
public class User {
// ...
}
问题:字段不参与序列化
@JsonIgnore
@TableField("password")
private String password;
@TableField(value = "password", select = false) private String password;
问题:自定义主键生成策略
@Component
public class CustomIdGenerator implements IdentifierGenerator {
@Override
public Long nextId(Object entity) {
// 自定义ID生成逻辑
return System.currentTimeMillis() + RandomUtils.nextLong(1000, 9999);
}
@Override
public String nextUUID(Object entity) {
return UUID.randomUUID().toString().replace("-", "");
}
}
使用自定义生成器:
@TableId(type = IdType.ASSIGN_ID) private Long id;
问题:分页查询总数不精确
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 优化count查询
paginationInterceptor.setOptimizeJoin(true);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
问题:大量数据分页性能问题
解决方案:采用游标分页
public List<User> getUsersByCursor(Long lastId, Integer size) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(lastId != null, User::getId, lastId)
.orderByAsc(User::getId)
.last("LIMIT " + size);
return userMapper.selectList(wrapper);
}
4. 乐观锁问题
问题:乐观锁更新不成功
// 设置乐观锁插件
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加乐观锁插件
OptimisticLockerInnerInterceptor optimisticLockerInterceptor = new OptimisticLockerInnerInterceptor();
interceptor.addInnerInterceptor(optimisticLockerInterceptor);
return interceptor;
}
}
// 实体类中加入版本字段
@Version
@TableField("version")
private Integer version;
// 解决乐观锁更新不成功的问题
@Service
public class UserService {
public boolean updateUserWithRetry(User user) {
int maxRetries = 3;
int retryCount = 0;
while (retryCount < maxRetries) {
try {
// 查询最新的版本
User latestUser = this.getById(user.getId());
user.setVersion(latestUser.getVersion());
boolean success = this.updateById(user);
if (success) {
return true;
}
} catch (OptimisticLockingFailureException e) {
retryCount++;
if (retryCount >= maxRetries) {
throw new BusinessException("更新失败,请稍后再试");
}
}
}
return false;
}
}
5. 逻辑删除问题
问题:逻辑删除后的数据仍被查询到
// 全局设置逻辑删除
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
// 实体类设置
@TableLogic
@TableField("deleted")
private Integer deleted;
// 查询时包含已删除的数据
List<User> allUsers = userMapper.selectList(
new QueryWrapper<User>().eq("deleted", 1)
);
6. SQL注入防御
问题:动态SQL存在注入威胁
// 错误的方法
public List<User> getUsersByName(String name) {
return userMapper.selectList(
new QueryWrapper<User>().apply("name = '" + name + "'")
);
}
// 正确的方法
public List<User> getUsersByName(String name) {
return userMapper.selectList(
new LambdaQueryWrapper<User>().eq(User::getName, name)
);
}
// 使用参数化查询
public List<User> getUsersByCondition(String condition) {
return userMapper.selectList(
new QueryWrapper<User>().apply("name = {0}", condition)
);
}
7. 效率提升
问题:N+1查询问题
// 不正确的写法:会引发N+1查询
public List<UserVO> getUsersWithDept() {
List<User> users = userService.list();
return users.stream().map(user -> {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
// 这部分会触发多次查询
Dept dept = deptService.getById(user.getDeptId());
vo.setDeptName(dept.getName());
return vo;
}).collect(Collectors.toList());
}
// 正确的写法:采用联表查询
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT u.*, d.name as dept_name FROM user u LEFT JOIN dept d ON u.dept_id = d.id")
List<UserVO> selectUsersWithDept();
}
问题:大规模操作效率问题
// 优化批量插入
@Service
public class BatchService {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Transactional(rollbackFor = Exception.class)
public void batchInsertOptimized(List<User> users) {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (int i = 0; i < users.size(); i++) {
mapper.insert(users.get(i));
// 每1000条记录提交一次
if (i % 1000 == 0 || i == users.size() - 1) {
sqlSession.flushStatements();
}
}
sqlSession.commit();
} finally {
sqlSession.close();
}
}
}
8. 多数据源设置
问题:配置多个数据源
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/primary/**/*.xml"));
return sessionFactory.getObject();
}
@Bean
public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/secondary/**/*.xml"));
return sessionFactory.getObject();
}
}
// 利用@DS注解转换数据源
@Service
public class UserService {
@DS("primary")
public List<User> getPrimaryUsers() {
return userMapper.selectList(null);
}
@DS("secondary")
public List<User> getSecondaryUsers() {
return userMapper.selectList(null);
}
}
9. 事务管理
问题:声明式事务失灵
// 错误示例:方法必需公开
@Transactional
private void updateUser(User user) {
userService.updateById(user);
}
// 正确示例
@Transactional(rollbackFor = Exception.class)
public void updateUser(User user) {
userService.updateById(user);
}
// 程序化事务
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public boolean updateUserWithTransaction(User user) {
return transactionTemplate.execute(status -> {
try {
boolean success = this.updateById(user);
if (!success) {
status.setRollbackOnly();
return false;
}
// 其他业务逻辑
return true;
} catch (Exception e) {
status.setRollbackOnly();
throw new RuntimeException(e);
}
});
}
}
10. 配置改进
完整的生产环境配置示例
# application-prod.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=true&serverTimezone=UTC&characterEncoding=UTF-8&allowPublicKeyRetrieval=true
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:123456}
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 10
min-idle: 10
max-active: 50
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
test-while-idle: true
test-on-borrow: false
test-on-return: false
validation-query: SELECT 1
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
filters: stat,wall,slf4j
connection-properties:druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000
web-stat-filter:
enabled:true
url-pattern:/*
exclusions:"*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
stat-view-servlet:
enabled:true
url-pattern:/druid/*
login-username:admin
login-password:123456
mybatis-plus:
configuration:
log-impl:org.apache.ibatis.logging.slf4j.Slf4jImpl
map-underscore-to-camel-case:true
cache-enabled:true
local-cache-scope:session
jdbc-type-for-null:null
call-setters-on-nulls:true
return-instance-for-empty-row:true
global-config:
banner:false
db-config:
id-type:auto
table-prefix:t_
logic-delete-field:deleted
logic-delete-value:1
logic-not-delete-value:0
insert-strategy:not_null
update-strategy:not_null
select-strategy:not_null
mapper-locations:classpath*:mapper/**/*.xml
type-aliases-package:com.example.entity
type-handlers-package:com.example.handler
check-config-location:true
executor-type:simple
logging:
level:
com.example.mapper:debug
com.alibaba.druid:info
org.springframework.transaction:debug
pattern:
console:"%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
file:"%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
file:
name:logs/mybatis-plus-app.log
max-size:100MB
max-history:30
总结
MyBatis-Plus作为MyBatis的强化工具,显著提升了开发效率,减少了模板代码。通过本文的学习,你应已掌握:
在实际项目中,建议:
扫码加好友,拉您进群



收藏
