更新记录
20231211:第一版
20241122:重构系列
配置环境
导入数据
-- 创建学生表
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT, -- 学生ID,主键,自增
name VARCHAR(50) NOT NULL, -- 学生姓名
age INT NOT NULL, -- 学生年龄
teacher_id INT -- 所属老师ID
);
-- 创建老师表
CREATE TABLE teacher (
id INT PRIMARY KEY AUTO_INCREMENT, -- 老师ID,主键,自增
name VARCHAR(50) NOT NULL -- 老师姓名
);
INSERT INTO student (id, name, age, teacher_id) VALUES
(1, '小明', 16, 1), -- 小明 对应 张老师
(2, '小红', 17, 1), -- 小红 对应 张老师
(3, '小华', 15, 2), -- 小华 对应 李老师
(4, '小丽', 16, 2), -- 小丽 对应 李老师
(5, '小强', 17, 3), -- 小强 对应 王老师
(6, '小芳', 16, 3), -- 小芳 对应 王老师
(7, '小亮', 15, 4), -- 小亮 对应 赵老师
(8, '小霞', 17, 4); -- 小霞 对应 赵老师
INSERT INTO teacher (id, name) VALUES
(1, '张老师'),
(2, '李老师'),
(3, '王老师'),
(4, '赵老师');
pom.xml
<?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>org.example</groupId>
<!-- 项目ID -->
<artifactId>demo</artifactId>
<!-- 项目版本 -->
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.18</spring-boot.version>
<alibaba.druid.version>1.2.21</alibaba.druid.version>
<mybatis.plus.boot.version>3.5.3.2</mybatis.plus.boot.version>
<mysql.boot.version>8.0.33</mysql.boot.version>
<lombok.version>1.18.30</lombok.version>
</properties>
<!-- 管理依赖版本 -->
<dependencyManagement>
<dependencies>
<!-- SpringBoot的依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${alibaba.druid.version}</version>
</dependency>
<!-- MP -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.boot.version}</version>
</dependency>
<!-- MYSQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.boot.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 应用依赖 -->
<dependencies>
<!-- Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- MP -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MYSQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
添加配置文件
# 应用服务 WEB 访问端口
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/spring_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
Studentname: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
cache-enabled: true
创建 entity 实体类
package org.example.entity;
import lombok.Data;
@Data
public class Student implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private Integer teacherId;
}
package org.example.entity;
import lombok.Data;
@Data
public class Teacher implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
}
Mapper Interface
https://baomidou.com/guides/data-interface/#mapper-interface 建议看文档, 我这里只是简单介绍, 本文重点在条件构造器
首先, 不讲 IService
,我更偏向于使用 mapper
。
Mapper Interface
就是上面 mapper
继承的 extends BaseMapper
。
我们创建一个 StudentMapper.xml
文件, 使他继承 BaseMapper
的 Mapper
@Mapper
public interface StudentMapper extends BaseMapper<Student> {
}
你可以直接通过 studentMapper.insert()
去使用, 接下来看些案例
public interface BaseMapper<T> extends Mapper<T> {
int insert(T entity);
int deleteById(Serializable id);
int deleteByMap(@Param("cm") Map<String, Object> columnMap);
int delete(@Param("ew") Wrapper<T> queryWrapper);
int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);
int updateById(@Param("et") T entity);
int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
T selectById(Serializable id);
List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);
T selectOne(@Param("ew") Wrapper<T> queryWrapper);
Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);
List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
<E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> queryWrapper);
}
这里要注意,给 entity/User
的 ID 属性添加自增注解,不然他默认会以 UUID 作为 ID。上面有说明
那么简单过以下基础操作
@Data
@RestController
@Slf4j
public class HelloController {
@Autowired
private StudentMapper studentMapper;
@GetMapping("/")
public String show() {
Student student = new Student();
student.setName("test");
student.setAge(10);
student.setTeacherId(1);
int result = studentMapper.insert(student);
System.out.println("受影响行数:" + result);
return "show";
}
}
增加
Student student = new Student();
student.setName("test");
student.setAge(18);
student.setTeacherId(1);
int result = studentMapper.insert(student);
System.out.println(student);
修改
// 根据 ID 修改
Student student = new Student();
student.setId(1);
student.setName("小明-update");
int result = studentMapper.updateById(student);
删除
// 删除一个
int result = studentMapper.deleteById(1);
// 根据 ID 删除多个
List<Long> idList = Arrays.asList(5L, 7L);
studentMapper.deleteBatchIds(idList);
// 根据 Map 条件删除多个
Map<String, Object> map = new HashMap<>();
map.put("name", "test"); // 删除所有 name == test 的数据
studentMapper.deleteByMap(map);
查询
// 查询一个
System.out.println(studentMapper.selectById(1));
// 根据 ID 查询多个
List<Long> idList = Arrays.asList(2L, 3L);
System.out.println(studentMapper.selectBatchIds(idList));
// 根据 Map 条件查询多个
Map<String, Object> map = new HashMap<>();
map.put("name", "Jack"); // 删除所有 name == Jack 的数据
System.out.println(studentMapper.selectByMap(map));
// 查询所有
System.out.println(studentMapper.selectList(null));
这里补充下, 你可以通过传递 Student
对象去查询, 比如你传递了id和name, 他就会根据这两个值去查询。这个后面也会讲, 现在提前说下
@Mapper
public interface StudentMapper extends BaseMapper<Student> {
default List<Student> selectStudentList(Student student) {
return selectList(Wrappers.lambdaQuery(student));
}
}
条件构造器 Wapper
Wrapper
条件构造器大致可以分为三类
接下来对着三类做个简单介绍
- 第一个: QueryWrapper, UpdateWrapper
QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "小明");
Student student = studentMapper.selectOne(queryWrapper);
System.out.println(student);
- 第二个: LambdaQueryWrapper, LambdaUpdateWrapper
LambdaQueryWrapper<Student> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(Student::getName, "小明");
Student student = studentMapper.selectOne(lambdaQueryWrapper);
System.out.println(student);
- 第三个: LambdaQueryChainWrapper, LambdaUpdateChainWrapper
- 如果要查询一个, 结尾
.one()
- 如果要查询多个, 结尾
.list()
- 如果要更新, 结尾
.update()
- 如果要查询一个, 结尾
Student student = new LambdaQueryChainWrapper<>(studentMapper)
.eq(Student::getName, "小明")
.one();
System.out.println(student);
- 总结
- 第一种
QueryWrapper/UpdateWrapper
因为字段名称是直接用字符串的, 非常不好维护,因此不推荐使用。 - 第二种
LambdaQueryWrapper/LambdaUpdateWrapper
他在第一种的基础上将字段使用 实体类的属性 来表示, 提高了代码的可读性和可维护性。 - 第三种
LambdaQueryChainWrapper/LambdaUpdateChainWrapper
我非常推荐使用这种方式, 他可以有效减少你需要写的代码。 - 最后我需要提醒下, Wrapper的所有方法都是基于
AbstractWrapper
这个抽象类的
- 第一种
常见方法
下面是一些比较常见的条件
.select("id", "name", "age")——SELECT id, name, age FROM user
.lt()——小于
.le()——小于等于
.gt()——大于
.ge()——大于等于
.eq()——等于
.ne()——不等于
.in("id", Arrays.asList(1, 2, 3));——字段的值在给定的集合中
.notIn("id", Arrays.asList(1, 2, 3));——字段的值不在给定的集合中
.betweeen("age",10,20)——age在值10到20之间
.notBetweeen("age",10,20)——age不在值10到20之间
.like("属性","值")——模糊查询匹配值"%值%"
.notLike("属性","值")——模糊查询不匹配值"%值%"
.likeLeft("属性","值")——模糊查询匹配最后一位值"%值"
.notLikeLeft("属性","值")——模糊查询不匹配最后一位值"%值"
.likeRight("属性","值")——模糊查询匹配第一位值"值%"
.notLikeRight("属性","值")——模糊查询不匹配第一位值"值%"
.isNull("属性")——属性为空或null
.isNotNull("属性")——属性不为空或null
.groupBy("属性","属性")——根据属性分组
.having("sum(age) > {0}", 10)——与groupBy一起用, 返回age和大于10的
.orderByAsc("属性")——根据属性升序排序
.orderByDesc("属性")——根据属性降序排序
setSql
- 自定义的 SQL 片段
lambdaUpdateWrapper.setSql("name = 'New Name'");
// 等价于
UPDATE user SET name = 'New Name'
.inSql
:使用 SQL 语句来生成 IN 子句中的值集合
lambdaQueryWrapper.inSql(Student::getId, "select id from user where id < 3");
// 等价于
SELECT * FROM user WHERE id IN (select id from other_table where id < 3)
- 除了
inSql
还有下面这些- 你只需将上面SQL中
WHERE id
与(select...from)
的连接词换成下面我写好的就可以了 - 比如
notInSql
:SELECT * FROM user WHERE id NOT IN (select id from other_table where id < 3)
- 你只需将上面SQL中
.notInSql——NOT IN
.eqSql——=
.gtSql——>
.geSql——>=
.itSql——<
.leSql——<=
条件连接
or()
/and()
lambdaQueryWrapper.eq(Student::getId, 1).or().eq(Student::getName, "老王");
// 等价于
SELECT * FROM user WHERE id = 1 OR name = '老王'
嵌套
lambdaQueryWrapper.or(i ->
i.and(j -> j.eq(Student::getName, "李白").eq(Student::getStatus, "alive"))
.or(j -> j.eq(Student::getName, "杜甫").eq(Student::getStatus, "alive"))
);
// 等价于
SELECT * FROM user
WHERE (name = '李白' AND status = 'alive')
OR (name = '杜甫' AND status = 'alive')
func
lambdaQueryWrapper.func(i -> {
if (true) {
i.eq(Student::getId, 1);
} else {
i.ne(Student::getId, 1);
}
});
拼接sql片段
apply
/last
last
在最后拼接- 比如:
.last("limit 1")
- 拼接 SQL 片段, 下面是使用占位符的例子
- 并且他有三个参数
condition
可以控制是否启用applySql
SQL片段params
参数
apply(boolean condition, String applySql, Object... params)
lambdaQueryWrapper.apply("date_format(dateColumn, '%Y-%m-%d') = {0}", "2008-08-08");
// 等价于
SELECT * FROM user WHERE date_format(dateColumn, '%Y-%m-%d') = '2008-08-08'
exists
exists
/notExists
lambdaQueryWrapper.exists("select id from table where age = 1");
// 等价于
SELECT * FROM user WHERE EXISTS (select id from table where age = 1)
实例
接下来我会写一些实例方便你理解
查询
- 1、下面两种方式的结果都是一样的。
// 第一种
List<Student> student = studentMapper.selectList(
new LambdaQueryWrapper<Student>()
.like(Student::getName, "小明")
);
// 第二种
LambdaQueryWrapper<Student> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(Student::getName, "小明");
Student student = studentMapper.selectOne(lambdaQueryWrapper);
- 2、判断变量是否存在
condition
- 第一个参数传递:
true/false
,假如为 false 则代表不使用该条件。
- 第一个参数传递:
String name = "小明";
List<Student> student = studentMapper.selectList(
new LambdaQueryWrapper<Student>()
.like(name.isEmpty(), Student::getName, name)
);
关于查询就基本就这点了东西了
增加
这里也不说了,上面写过个例子,这个没什么好说的
修改
- 1、修改ID=1的名称
Student student = new Student();
student.setName("小明-update2");
LambdaQueryWrapper<Student> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Student::getId, 1);
int result = studentMapper.update(student, lambdaQueryWrapper);
- 2、又或是这样也可以
// 根据 ID 修改
Student student = new Student();
student.setId(1);
student.setName("小明-update3");
int result = studentMapper.updateById(student);
- 3、将ID=1的年龄增加1
- 版本需3.5.6+
setIncrBy
:数值字段进行增量操作setDecrBy
:数值字段进行减量操作
lambdaUpdateWrapper.setIncrBy(Student::getAge, 1);
// 等价于
UPDATE student SET age = age + 1
删除
- 1、删除ID=1
int result = studentMapper.delete(new LambdaQueryWrapper<Student>()
.eq(Student::getId, 1)
);
- 2、删除 age 为空的学生
int result = studentMapper.delete(new LambdaQueryWrapper<Student>()
.isNull(Student::getAge)
);
常用注解
@TableName
- 解决实体类名称与数据库表名不一致
@TableName("t_user")
public class User implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String email;
}
@TableId
- 标识主键
- 下面例子中,我们将
ID
标识为主键,并且他是 自增 的。默认是IdType.ASSIGN_ID
代表使用 UUID
public class User implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String email;
}
@TableField
- 解决实体类属性与数据库字段不同
- 下面例子中,数据库的字段是
username
。
public class User implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
@@TableField("username")
private String name;
private Integer age;
private String email;
}
@TableLogic
- 将字段标记为逻辑删除吗,意思是不会在数据库中删除数据,但是查询不到。
先在数据库中添加字段,然后我们再次查询会发现以及查询不到了。
ALTER TABLE user
ADD COLUMN is_deleted TINYINT(1) NOT NULL DEFAULT 0;
# 测试删除功能, 假装被删除
UPDATE user SET is_deleted=1 WHERE id=1 AND is_deleted=0
public class User implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String email;
@TableLogic
private Integer isDeleted;
}
分页
设置分页(版本低于 3.5 可能会报错)
配置
@Configuration
@MapperScan("com.exam.demo.mapper")
public class MybatisPlusConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
//interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
return interceptor;
}
}
简单分页
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
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 |
selectPage
:第二个参数是Wrapper
,你可以传递条件。- 这种方式适用于单表查询, 如果你需要用到多表查询请看下一节
// 设置分页参数, 第二页 每页 2 条数据
Page<Student> page = new Page<>(2, 2);
studentMapper.selectPage(page, null);
// 获取分页数据
List<Student> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("当前页:" + page.getCurrent());
System.out.println("每页显示的条数:" + page.getSize());
System.out.println("总记录数:" + page.getTotal());
System.out.println("总页数:" + page.getPages());
System.out.println("是否有上一页:" + page.hasPrevious());
分页多表查询
Page 是继承于 IPage 的,实现了简单分页模型。如果你需要实现自己的分页模型,可以继承
Page
类或实现IPage
类。
- 自定义
mapper
IPage<SysUserInfoVO> selectSysUserInfo(Page<?> page, @Param("userQueryDTO") SysUserQueryDTO userQueryDTO);
// IPage 和 Page 都可以
Page<SysUserInfoVO> selectSysUserInfo(Page<?> page, @Param("userQueryDTO") SysUserQueryDTO userQueryDTO);
XML
<resultMap id="SysUserInfoResultMap" type="com.eezd.main.web.system.vo.SysUserInfoVO">
<id column="user_id" property="userId"/>
<result column="role_id" property="roleId"/>
<result column="role_name" property="roleName"/>
<result column="role_key" property="roleKey"/>
<result column="role_status" property="roleStatus"/>
<result column="username" property="username"/>
<result column="nickname" property="nickname"/>
<result column="email" property="email"/>
<result column="phonenumber" property="phonenumber"/>
<result column="sex" property="sex"/>
<result column="avatar" property="avatar"/>
<result column="status" property="status"/>
<result column="login_ip" property="loginIp"/>
<result column="login_date" property="loginDate"/>
<result column="create_by" property="createBy"/>
<result column="create_time" property="createTime"/>
<result column="update_by" property="updateBy"/>
<result column="update_time" property="updateTime"/>
<result column="remark" property="remark"/>
</resultMap>
<select id="selectSysUserInfo" resultMap="SysUserInfoResultMap">
SELECT u.*, r.role_key, r.role_name, r.status as role_status
FROM sys_user u
LEFT JOIN sys_role r ON u.role_id = r.role_id
WHERE u.del_flag = 0
<if test="userQueryDTO.username != null">
AND u.username LIKE CONCAT('%', #{userQueryDTO.username}, '%')
</if>
<if test="userQueryDTO.nickname != null">
AND u.nickname LIKE CONCAT('%', #{userQueryDTO.nickname}, '%')
</if>
<if test="userQueryDTO.email != null">
AND u.email LIKE CONCAT('%', #{userQueryDTO.email}, '%')
</if>
<if test="userQueryDTO.phonenumber != null">
AND u.phonenumber LIKE CONCAT('%', #{userQueryDTO.phonenumber}, '%')
</if>
<if test="userQueryDTO.sex != null">
AND u.sex = #{userQueryDTO.sex}
</if>
<if test="userQueryDTO.status != null">
AND u.status = #{userQueryDTO.status}
</if>
</select>
SysUserInfoVO
@Data
public class SysUserInfoVO implements Serializable {
private Long userId;
private Long roleId;
private String roleName;
private String roleKey;
private Long roleStatus;
private String username;
private String nickname;
private String email;
private String phonenumber;
private String sex;
private String avatar;
private String status;
private String loginIp;
private Date loginDate;
private String createBy;
private Date createTime;
private String updateBy;
private Date updateTime;
private String remark;
}
DTO
@ApiModel(value = "用户查询DTO")
@Data
public class SysUserQueryDTO {
@ApiModelProperty(value = "用户ID", position = 1)
private Long userId;
@ApiModelProperty(value = "角色ID", position = 2, example = "2")
private Long roleId;
@ApiModelProperty(value = "用户账号", position = 3, example = "test")
private String username;
@ApiModelProperty(value = "用户昵称", position = 4, example = "测试员")
private String nickname;
@ApiModelProperty(value = "用户邮箱", position = 5, example = "test@qq.com")
private String email;
@ApiModelProperty(value = "手机号码", position = 6, example = "15888888889")
private String phonenumber;
@ApiModelProperty(value = "用户性别(0男 1女 2未知)", position = 7, example = "2")
private String sex;
@ApiModelProperty(value = "帐号状态(0正常 1停用)", position = 10, example = "0")
private String status;
}
调用
Integer pageNum = Convert.toInt(ServletUtils.getParameter("pageNum"), 1);
Integer pageSize = Convert.toInt(ServletUtils.getParameter("pageSize"), 10);
Page<?> page = new Page<>(pageNum, pageSize);
IPage<SysUserInfoVO> pageResult = studentMapper.selectSysUserInfo(page, userQueryDTO);
List<SysUserInfoVO> list = pageResult.getRecords();
long total = page.getTotal();
锁
例如:原本价格是 100 元,A 加 50 元,B 取 30 元。
正常情况:A 和 B 同时操作,如果 A 先提交,结果 150 元,然后 B 再次提交就是 100-30,而不是 150-30。
乐观锁:A 和 B 同时操作,如果 A 先提交,那么 B 则会取 A 提交的结果,150-30。
悲观锁:一条数据只能被一个人操作,当 A 在操作的时候 B 不能进行操作。
乐观锁的插件配置
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));// 如果配置多个插件,切记分页最后添加
// interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
- 我们创建一个数据表试试
- 必须存在
VERSION
字段, 用来保存乐观锁的版本号
- 必须存在
CREATE TABLE product
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
- 实体,添加
@Version
注解。- 表示乐观锁
@Data
public class Product {
private Long id;
private String name;
private Integer price;
@Version
private Integer version;
}
mapper
public interface ProductMapper extends BaseMapper<Product> {
}
测试
// A 取数据
Product p1 = productMapper.selectById(1L);
// B 取数据
Product p2 = productMapper.selectById(1L);
// A 修改 + 50
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("A 修改的结果:" + result1); // 1 修改成功
// B 修改 - 30
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
System.out.println("B 修改的结果:" + result2); // 0 修改错误
if (result2 == 0) {
// 失败重试,重新获取version并更新
p2 = productMapper.selectById(1L);
p2.setPrice(p2.getPrice() - 30);
result2 = productMapper.updateById(p2);
}
System.out.println("A 修改重试的结果:" + result2); // 1 重试
// 老板看价格
Product p3 = productMapper.selectById(1L);
System.out.println("B 看价格:" + p3.getPrice()); // 120