条件构造器
MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。

QueryWrapper & UpdateWrapper
java
package com.light.mybatisplus;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.light.mybatisplus.mapper.StudentMapper;
import com.light.mybatisplus.pojo.Student;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class TestWrapper {
@Autowired
private StudentMapper studentMapper;
@Test
public void testWrapperQuery(){
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
//这里的第一个参数应该是数据库表字段,而非映射实体类属性
queryWrapper.like("name", 'l')
//也可以使用between()
.ge("age", 12)
.le("age", 100)
.isNull("email");
List<Student> list = studentMapper.selectList(queryWrapper);
System.out.println(list);
}
@Test
public void testWrapperQueryOrder(){
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
// 先使用age升序排序
queryWrapper.orderByAsc("age")
//如果年龄相同,按照id降序排列
.orderByDesc("id");
List<Student> students = studentMapper.selectList(queryWrapper);
students.forEach(System.out::println);
}
@Test
public void testWrapperDel(){
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
System.out.println(studentMapper.delete(queryWrapper));
}
@Test
public void testWrapperUpdate(){
QueryWrapper<Student> wrapper = new QueryWrapper<>();
wrapper.like("name", 'z')
.gt("age", 12)
.or()
.isNotNull("email");
Student student = new Student();
student.setEmail("test@101100.com");
int update = studentMapper.update(student, wrapper);
System.out.println(update);
}
@Test
public void testWrapperUpdateConditionLevel(){
// 将name中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
QueryWrapper<Student> wrapper = new QueryWrapper<>();
wrapper.like("name", 'a')
//这里查看and的参数是Consumer类型,所以这里是使用lambda表达式,w就表示我们当前的wrapper
.and(w->w.gt("age", 12)
.or()
.isNull("email"));
Student student = new Student();
student.setEmail("test@101100.com");
int update = studentMapper.update(student, wrapper);
System.out.println(update);
// UPDATE student SET email=? WHERE is_delete='0' AND (name LIKE ? AND (age > ? OR email IS NULL))
}
@Test
public void testWrapperSelectSubField(){
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
queryWrapper.select("name", "age", "email").gt("age", 18);
List<Student> students = studentMapper.selectList(queryWrapper);
students.forEach(System.out::println);
// SELECT name,age,email FROM student WHERE is_delete='0' AND (age > ?)
}
@Test
public void testWrapperSelectSubSelect(){
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
// 查询d小于等于100的用户信息
queryWrapper.inSql("id", "select id from student where id < 100");
List<Student> students = studentMapper.selectList(queryWrapper);
students.forEach(System.out::println);
// SELECT id,name,age,email,is_delete FROM student WHERE is_delete='0' AND (id IN (select id from student where id < 100))
}
@Test
public void testUpdateWrapper(){
// UpdateWrapper 可以设置查询条件,也可以设置更改的数据
UpdateWrapper<Student> wrapper = new UpdateWrapper<>();
wrapper.like("name", 'a')
//这里查看and的参数是Consumer类型,所以这里是使用lambda表达式,w就表示我们当前的wrapper
.and(w->w.gt("age", 12)
.or()
.isNull("email"));
wrapper.set("name", "小黑").set("email", "xiaohei@163.com");
int update = studentMapper.update(null, wrapper);
System.out.println("结果:"+update);
// UPDATE student SET name=?,email=? WHERE is_delete='0' AND (name LIKE ? AND (age > ? OR email IS NULL))
}
@Test
public void testSelectCondition1(){
String name = "a";
Integer ageStart = null;
Integer ageEnd = 30;
QueryWrapper<Student> wrapper = new QueryWrapper<>();
// isNotBlank判断某个字符创是否不为空字符串、不为null、不为空白符
if (StringUtils.isNotBlank(name)){
wrapper.eq("name", "张小花");
}
if (ageStart != null && ageStart > 0){
wrapper.ge("age", ageStart);
}
if (ageEnd != null && ageEnd > 0){
wrapper.le("age", ageEnd);
}
List<Student> students = studentMapper.selectList(wrapper);
students.forEach(System.out::println);
// SELECT id,name,age,email,is_delete FROM student WHERE is_delete='0' AND (name = ? AND age <= ?)
}
@Test
public void testSelectCondition2(){
String name = "a";
Integer ageStart = null;
Integer ageEnd = 30;
QueryWrapper<Student> wrapper = new QueryWrapper<>();
// isNotBlank判断某个字符创是否不为空字符串、不为null、不为空白符
// eq、ge、le等方法第一个参数都是条件语句
wrapper.eq(StringUtils.isNotBlank(name), "name", "张小花")
.ge(ageStart != null && ageStart > 0, "age", ageStart)
.le(ageEnd != null && ageEnd > 0, "age", ageEnd);
List<Student> students = studentMapper.selectList(wrapper);
students.forEach(System.out::println);
// SELECT id,name,age,email,is_delete FROM student WHERE is_delete='0' AND (name = ? AND age <= ?)
}
}
LambdaQueryWrapper & LambdaUpdateWrapper
java
package com.light.mybatisplus;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.light.mybatisplus.mapper.StudentMapper;
import com.light.mybatisplus.pojo.Student;
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 TestLambdaWrapper {
@Autowired
private StudentMapper studentMapper;
@Test
public void testLambdaQueryWrapper(){
String name = "a";
Integer ageStart = null;
Integer ageEnd = 30;
LambdaQueryWrapper<Student> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//其实区别是lambda主要通过Student::getName各种形式获取数据库字段名
lambdaQueryWrapper.eq(StringUtils.isNotBlank(name), Student::getName, name)
.ge(ageStart != null && ageStart > 0, Student::getAge, ageStart)
.le(ageEnd != null && ageEnd > 0, Student::getAge, ageEnd);
List<Student> students = studentMapper.selectList(lambdaQueryWrapper);
students.forEach(System.out::println);
//SELECT id,name,age,email,is_delete FROM student WHERE is_delete='0' AND (name = ? AND age <= ?)
}
@Test
public void testLambdaUpdateWrapper(){
// UpdateWrapper 可以设置查询条件,也可以设置更改的数据
LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
wrapper.like(Student::getName, 'a')
//这里查看and的参数是Consumer类型,所以这里是使用lambda表达式,w就表示我们当前的wrapper
.and(w->w.gt(Student::getAge, 12)
.or()
.isNull(Student::getEmail));
wrapper.set(Student::getName, "小黑").set(Student::getEmail, "xiaohei@163.com");
int update = studentMapper.update(null, wrapper);
System.out.println("结果:"+update);
// UPDATE student SET name=?,email=? WHERE is_delete='0' AND (name LIKE ? AND (age > ? OR email IS NULL))
}
}
插件
MyBatis-Plus 提供了一系列强大的插件来增强 MyBatis 的功能,这些插件通过 MybatisPlusInterceptor 来实现对 MyBatis 执行过程的拦截和增强。
MybatisPlusInterceptor 是 MyBatis-Plus 的核心插件,它代理了 MyBatis 的 Executor#query、Executor#update 和 StatementHandler#prepare 方法,允许在这些方法执行前后插入自定义逻辑。
MybatisPlusInterceptor 有一个关键属性 interceptors,它是一个 List<InnerInterceptor> 类型的集合,用于存储所有要应用的内部拦截器。
步骤:
- 首先在spring boot中添加一个配置类
- 然后在配置类中编写添加插件
分页插件
MyBatisPlus自带分页插件,只要简单的配置即可实现分页功能。下面是添加插件:
java
package com.light.mybatisplus.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
// @ComponentScan("com.light.mybatisplus.mapper") //扫描带 @Component/@Service/@Repository 的类
@MapperScan("com.light.mybatisplus.mapper") // 扫描指定包下的接口,生成 MyBatis 的动态代理并注册为 Spring Bean
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//创建拦截器对象
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页拦截器 - 标记为mysql类型数据库
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
PaginationInnerInterceptor 提供了以下属性来定制分页行为:
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| overflow | boolean | false | 溢出总页数后是否进行处理 |
| maxLimit | Long | 单页分页条数限制 | |
| dbType | DbType | 数据库类型 | |
| dialect | IDialect | 方言实现类 |
建议单一数据库类型的均设置 dbType
使用示例:
java
public class TestPlugin {
@Autowired
private StudentMapper studentMapper;
@Test
public void testPagePlugin(){
Page<Student> page = new Page<>(2, 3);
studentMapper.selectList(page, null);
System.out.println(page);
//当前查询列表
System.out.println(page.getRecords());
//总页数
System.out.println(page.getPages());
//总记录数
System.out.println(page.getTotal());
//是否有上一页
System.out.println(page.hasNext());
//是否有下一页
System.out.println(page.hasPrevious());
}
}
输出结果:
TEXT
==> Preparing: SELECT COUNT(*) AS total FROM student WHERE is_delete = '0'
==> Parameters:
<== Columns: total
<== Row: 13
<== Total: 1
==> Preparing: SELECT id,name,age,email,is_delete FROM student WHERE is_delete='0' LIMIT ?,?
==> Parameters: 3(Long), 3(Long)
<== Columns: id, name, age, email, is_delete
<== Row: 2102299891458961409, light0, 30, null, 0
<== Row: 2102299891660288001, light1, 31, null, 0
<== Row: 2102299891660288002, light2, 32, null, 0
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@48cb2d73]
com.baomidou.mybatisplus.extension.plugins.pagination.Page@7e94e331
[]
5
13
true
true
Page 类继承了 IPage 类,实现了简单分页模型。如果你需要实现自己的分页模型,可以继承 Page 类或实现 IPage 类。
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| records | List | emptyList | 查询数据列表 |
| total | Long | 0 | 查询列表总记录数 |
| size | Long | 10 | 每页显示条数,默认 10 |
| current | Long | 1 | 当前页 |
| orders | List | emptyList | 排序字段信息 |
| optimizeCountSql | boolean | true | 自动优化 COUNT SQL |
| optimizeJoinOfCountSql | boolean | true | 自动优化 COUNT SQL 是否把 join 查询部分移除 |
| searchCount | boolean | true | 是否进行 count 查询 |
| maxLimit | Long | 单页分页条数限制 | |
| countId | String | XML 自定义 count 查询的 statementId |
乐观锁
问题: A和B同一时间点获取某一个数据去更改它,A先更改,B后更改。B覆盖A,此时A以为自己改成功了,其实被B覆盖了。
乐观锁是一种并发控制机制,用于确保在更新记录时,该记录未被其他事务修改。
乐观锁的实现原理
乐观锁的实现通常包括以下步骤:
- 读取记录时,获取当前的版本号(version)。
- 在更新记录时,将这个版本号一同传递。
- 执行更新操作时,设置
version = newVersion的条件为version = oldVersion。 - 如果版本号不匹配,则更新失败。
MyBatis-Plus 提供了 OptimisticLockerInnerInterceptor 插件,使得在应用中实现乐观锁变得简单。
修改bean对象
java
package com.light.mybatisplus.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@TableName("student")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private String name;
private Integer age;
private String email;
@TableLogic // 软删除
private String isDelete;
@Version //标记乐观锁版本号
private Integer version;
public Student(Long id, String name, Integer age, String email) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
}
}
然后直接修改就可以了,每次更改都会更改版本
通用枚举
给数据库表中添加 sex字段 int类型
添加枚举类
java
package com.light.mybatisplus.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
@Getter
public enum SexEnum {
MALE(1, "男"),
FEMALE(2, "女");
@EnumValue // 注意标记这个字段的值写入数据库
private Integer sex;
private String sexName;
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
注意一定要标注这个@EnumValue注解 修改实体类
java
package com.light.mybatisplus.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.light.mybatisplus.enums.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@TableName("student")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student {
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
private String name;
private Integer age;
private String email;
private SexEnum sex;
@TableLogic // 软删除
private String isDelete;
@Version //标记乐观锁版本号
private Integer version;
public Student(Long id, String name, Integer age, String email) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
}
}
多数据源环境
适用于多种场景:纯粹多库、读写分离、一主多从、混合模式等
dynamic-datasource
dynamic-datasource 是一个开源的 Spring Boot 多数据源启动器,提供了丰富的功能,包括数据源分组、敏感信息加密、独立初始化表结构等
特性
- 数据源分组:适用于多种场景,如读写分离、一主多从等。
- 敏感信息加密 :使用
ENC()加密数据库配置信息。 - 独立初始化:支持每个数据库独立初始化表结构和数据库。
- 自定义注解 :支持自定义注解,需继承
DS。 - 简化集成:提供对 Druid、HikariCP 等连接池的快速集成。
- 组件集成:支持 Mybatis-Plus、Quartz 等组件的集成方案。
- 动态数据源:支持项目启动后动态增加或移除数据源。
- 分布式事务:提供基于 Seata 的分布式事务方案。
约定
- 本框架专注于数据源切换,不限制具体操作。
- 配置文件中以下划线
_分割的数据源首部为组名。 - 切换数据源可以是组名或具体数据源名。
- 默认数据源名为
master,可通过spring.datasource.dynamic.primary修改。 - 方法上的注解优先于类上的注解。
引入依赖
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${version}</version>
</dependency>
配置
yml
spring:
datasource:
dynamic:
# 设置默认的数据源或者数据源组,默认值即为master
primary: master
# 严格匹配数据源,默认false, true未匹配到指定数据源时抛异常,false使用默认数据源
strict: false
datasource:
master:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
slave_2:
url: ENC(xxxxx)
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: com.mysql.jdbc.Driver
操作指定数据源
java
@DS("master") //指定所操作的数据源
@Service
public class UserSeryiceImpl extends ServiceImpl<UserMapper, User> implements Userservice{
}
MybatisX


这样就可以快速创建Pojo、Service、ServiceImpl、Mapper、MapperImpl包及文件。
快速创建curd方法

选择提示后,然后alt+enter:

生成代码

自动生成的xml
xml
<insert id="insertSelective">
insert into login
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="username != null">username,</if>
<if test="password != null">password,</if>
<if test="nickname != null">nickname,</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">#{id,jdbcType=NUMERIC},</if>
<if test="username != null">#{username,jdbcType=VARCHAR},</if>
<if test="password != null">#{password,jdbcType=VARCHAR},</if>
<if test="nickname != null">#{nickname,jdbcType=VARCHAR},</if>
</trim>
</insert>