MyBatisPlus
学习目标
- 基于MyBatisPlus完成标准Dao开发
一、MyBatisPlus简介
1.1 入门案例
(1)创建数据库及表
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user (
id bigint(20) primary key auto_increment,
name varchar(32) not null,
password varchar(32) not null,
age int(3) not null ,
tel varchar(32) not null
);
insert into user values(1,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
insert into user values(3,'Jock','123456',41,'18812345678');
insert into user values(4,'li','itcast',15,'4006184000');
(2)创建SpringBoot工程 - 配置使用技术
(3)pom.xml(添加druid数据源)
- druid数据源可以加也可以不加,SpringBoot有内置的数据源,可以配置成使用Druid数据源。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
(4)application.yml(配置数据库连接)
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/mybatisplus_db?serverTimezone=UTC
username: root
password: 1234
(5)创建实体类
package com.li.domain;
public class User {
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
", age=" + age +
", tel='" + tel + '\'' +
'}';
}
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
(6)创建Dao接口
- 继承
BaseMapper<User>
@Mapper
//继承“extends BaseMapper<User>”
public interface UserDao extends BaseMapper<User> {
}
(7)编写测试类
@SpringBootTest
class MpDemo1ApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void testGetAll(){
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
(8)运行测试类
1.2 MyBatisPlus特性
- 无侵入:只做增强不做改变,不会对现有工程产生影响
- 强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD操作
- 支持 Lambda:编写查询条件无需担心字段写错
- 支持主键自动生成
- 内置分页插件
二、标准数据层开发
2.1 标准数据层CRUD功能
2.1.1 标准的CRUD功能(mybatisplus接口)
2.1.2 实例
(1)新增
@Test
// 新增
void testSave(){
User user = new User();
user.setName("wangwu");
user.setPassword("99999999");
user.setAge(820);
user.setTel("189");
userDao.insert(user);
}
(2)删除
// 1567064852546011137
//刚刚新增的ID
@Test
//删除
void tsetDelete(){
//提示数值过大,则在数后加“L”
userDao.deleteById(1567064852546011137L);
}
(3)修改
@Test
//修改
void testUpadte(){
User user = new User();
user.setId(1L);
user.setName("gogogo");
user.setPassword("9993434343999");
user.setAge(991);
userDao.updateById(user);
}
(4)根据ID查询
@Test
//根据ID查询
void testGetById(){
User user = userDao.selectById(2L);
System.out.println(user);
}
(5)查询所有
@Test
//查询所有
public void testGetAll(){
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
2.1.3 Lombok
简写模型类的编写。
- 私有属性
- setter...getter...方法
- toString方法
- 构造函数
- 利用Lombok就可以不用编写了。
- Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。
(1)添加lombok依赖
- 版本可以不用写,因为SpringBoot中已经管理了lombok的版本。
- 如果删除setter和getter方法程序有报红,则需要安装Lombok插件。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--<version>1.18.12</version>-->
</dependency>
(2)模型类上添加注解
Lombok常见的注解有:(加粗的是比较常用的)
- @Setter:为模型类的属性提供setter方法
- @Getter:为模型类的属性提供getter方法
- @ToString:为模型类的属性提供toString方法
- @EqualsAndHashCode:为模型类的属性提供equals和hashcode方法
- @Data:是个组合注解,包含上面的注解的功能
- @NoArgsConstructor:提供一个无参构造函数
- @AllArgsConstructor:提供一个包含所有参数的构造函数
(3)实例
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
//@Data是个组合注解,包含上面的注解的功能
//@NoArgsConstructor:提供一个无参构造函数
//@AllArgsConstructor:提供一个包含所有参数的构造函数
}
(4)如果我只想要某个字段的构造函数-实例
- 例如name和 password的构造函数。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
2.2 分页功能
(1)分页查询使用的方法
(2)调用方法传入参数获取返回值
@Test
//分页查询
void testSelectPage(){
//创建IPage分页对象,设置分页参数,1为当前页码,2为每页显示的记录数
IPage<User> page = new Page<>(1,2);
//2 执行分页查询
userDao.selectPage(page,null);
//获取分页结果
System.out.println("当前页码值" + page.getCurrent());
System.out.println("每页显示多少数" + page.getSize());
System.out.println("一共多少页" + page.getPages());
System.out.println("一共多少数据" + page.getTotal());
System.out.println("数据" + page.getRecords());
}
(3)设置分页拦截器
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加分页拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
(4)运行测试程序
(5)如果想查看MP执行的SQL语句
- 如果想查看MP执行的SQL语句,可以修改application.yml配置文件
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台
三、DQL控制
3.1 条件查询方式
3.1.1 条件查询的类
3.1.2 取消初始化spring日志打印(可选)
- resources目录下新建添加
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
3.1.3 构建条件查询
(1)QueryWrapper
@Test
//查询所有-QueryWrapper
public void testGetAll_two(){
QueryWrapper qw = new QueryWrapper();
//lt: 小于(<)
qw.lt("age",50);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
lt
: 小于(<) 。- 最终的sql语句为:
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
gt
: 大于(>) 。- 最终的sql语句为:
SELECT id,name,password,age,tel FROM user WHERE (age < ? AND age > ?)
(2)QueryWrapper的基础上使用lambda(推荐)
@Test
//QueryWrapper的基础上使用lambda
public void testGetAll_three(){
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.lambda().lt(User::getAge,50);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
}
- User::getAget,为lambda表达式中的,类名::方法名。
- 最终的sql语句为:
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
- 注意:构建LambdaQueryWrapper的时候泛型不能省。
(3)LambdaQueryWrapper(推荐)
@Test
//LambdaQueryWrapper
public void testGetAll_four(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge,50);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
3.1.4 多条件构建
- 需求:查询数据库表中,年龄在10岁到30岁之间的用户信息。
@Test
// 多条件构建
public void testGetAll_five(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//lt: 小于(<)
//gt:大于(>)
lqw.lt(User::getAge,50);
lqw.gt(User::getAge,20);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
- 构建多条件的时候,可以支持链式编程。
@Test
// 多条件构建 - 链式编程
public void testGetAll_six(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//lt: 小于(<)
//gt:大于(>)
lqw.lt(User::getAge,50).gt(User::getAge,20);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
- 需求:查询数据库表中,年龄小于10或年龄大于30的数据。
@Test
// 多条件构建 - 链式编程-“或者”条件
public void testGetAll_seven(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//lt: 小于(<)
//gt:大于(>)
lqw.lt(User::getAge,50).or().gt(User::getAge,20);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
or()
就相当于我们sql语句中的or关键字,不加默认是and。- 最终的sql语句为:
SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
3.1.5 null判定
(1)案例
@Data
public class UserQuery extends User {
private Integer age2;
}
(2)实例
@Test
//null判定
public void testGetAll_eight(){
//模拟页面传递过来的查询数据
UserQuery uq = new UserQuery();
uq.setAge(10);
uq.setAge2(30);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());
lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
3.2 查询投影
3.2.1 查询指定字段
- 目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容,我们所说的查询投影,即不查询所有字段,只查询出指定内容的数据。
实例(1)
select(...)
方法用来设置查询的字段列,可以设置多个- 最终的sql语句为:
SELECT id,name,age FROM user
@Test
//查询指定字段
public void testGetAll_nine(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//select(...)方法用来设置查询的字段列,可以设置多个
lqw.select(User::getAge,User::getName,User::getAge);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
实例(2)
- 如果使用的不是lambda,就需要手动指定字段。
QueryWrapper
@Test
//查询指定字段-非lambda(QueryWrapper)
//需要手动指定字段
public void testGetAll_ten(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("id","name","age","tel");
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
3.2.2 聚合查询
- 聚合函数查询
count、max、min、avg、sum的使用
- count:总记录数
- max:最大值
- min:最小值
- avg:平均值
- sum:求和
- count:
SELECT count(*) as count FROM user
- max:
SELECT max(age) as maxAge FROM user
- min:
SELECT min(age) as minAge FROM user
- avg:
SELECT avg(age) as avgAge FROM user
- sum:
SELECT sum(age) as sumAge FROM user
@Test
//聚合查询
//count、max、min、avg、sum
public void testGetAll_eleven(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
// lqw.select("count(*) as count");
// lqw.select("max(age) as maxAge");
// lqw.select("min(age) as minAge");
// lqw.select("sum(age) as sumAge");
lqw.select("avg(age) as avgAge");
List<Map<String, Object>> userList = userDao.selectMaps(lqw);
System.out.println(userList);
}
3.2.3 分组查询
- 需求:分组查询,完成
group by
的查询使用。 - groupBy为分组。
- 最终的sql语句为:
SELECT count(*) as count,tel FROM user GROUP BY tel
@Test
//分组查询
//group by
public void testGetAll_twelve(){
QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("count(*) as count,tel");
lqw.groupBy("tel");
List<Map<String, Object>> userList = userDao.selectMaps(lqw);
System.out.println(userList);
}
- 注意:
- 聚合与分组查询,无法使用lambda表达式来完成。
- MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实现。
3.3 查询条件设定
MP的查询条件有很多:
- 范围匹配(> 、 = 、between)
- 模糊匹配(like)
- 空判定(null)
- 包含性匹配(in)
- 分组(group)
- 排序(order)
3.3.1 等值查询
- 需求:根据用户名和密码查询用户信息。
- eq()
SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
- selectList:查询结果为多个或者单个。
- selectOne:查询结果为单个。
@Test
//查询条件
//等值查询
//需求:根据用户名和密码查询用户信息
//eq()
public void testGetAll_thirteen(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.eq(User::getName,"cds").eq(User::getPassword,"4123");
User loginUser = userDao.selectOne(lqw);
System.out.println(loginUser);
}
3.3.2 范围查询
- 需求:对年龄进行范围查询,使用
lt()、le()、gt()、ge()、between()
进行范围查询 - gt():大于(>)
- ge():大于等于(>=)
- lt():小于(<)
- lte():小于等于(<=)
- between():
between ? and ?
@Test
//查询条件
//范围查询
//需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
//between():between ? and ?
public void testGetAll_fourteen(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.between(User::getAge,10,50);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
3.3.3 模糊查询
- 需求:查询表中name属性的值以J开头的用户信息,使用
like
进行模糊查询 - like():前后加百分号,如
%A%
。 - likeLeft():前面加百分号,如
%J
。 - likeRight():后面加百分号,如
J%
。
@Test
//查询条件
//模糊查询
//需求:查询表中name属性的值以J开头的用户信息,使用like进行模糊查询
// like():前后加百分号,如 %J%
// likeLeft():前面加百分号,如 %J
// likeRight():后面加百分号,如 J%
public void testGetAll_fifteen(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.like(User::getName,"4");
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
}
3.3.4 排序查询
3.4 字段映射与表名映射(映射匹配兼容性)
3.4.1 问题
- 表字段与编码属性设计不同步?
- 编码中添加了数据库中未定义的属性?
- 采用默认查询开放了更多的字段查看权限?
- 表名与编码开发设计不同步?
3.4.2 实例
(1)修改数据库表user为tbl_user
解决方案 —— 模型类添加@TableName
注解
@Data
@AllArgsConstructor
@NoArgsConstructor
//@TableName注解
@TableName("tbl_user")
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
public User(String name, String password) {
this.name = name;
this.password = password;
}
//@Data是个组合注解,包含上面的注解的功能
//@NoArgsConstructor:提供一个无参构造函数
//@AllArgsConstructor:提供一个包含所有参数的构造函数
}
(2)将字段password修改成pwd
解决方案 —— 使用@TableField映射关系
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tbl_user")
public class User {
private Long id;
private String name;
//@TableField映射关系
@TableField(value="pwd")
private String password;
private Integer age;
private String tel;
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
(3)添加一个数据库表不存在的字段
解决方案 —— 使用@TableField排除字段
@Data
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField(value="pwd")
private String password;
private Integer age;
private String tel;
//使用@TableField排除字段
@TableField(exist=false)
//添加一个数据库表不存在的字段
private Integer online;
}
(4)查询时将pwd隐藏
@Data
@TableName("tbl_user")
public class User {
private Long id;
private String name;
//查询时将pwd隐藏
//password=null
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
3.4.3 知识点 —— @TableField
、@TableName
@TableField
@TableName
四、DML控制
4.1 id生成策略控制(Insert)
4.1.1 知识点:@TableId
4.1.2 初始测试环境
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave(){
User user = new User();
user.setName("11L");
user.setPassword("1111");
user.setAge(10);
user.setTel("11110");
userDao.insert(user);
}
@Test
void testDelete(){
userDao.deleteById(8888);
}
@Test
void testUpdate(){
User user = new User();
user.setId(3L);
user.setName("safety");
user.setPassword("468");
user.setAge(17);
user.setTel("189");
userDao.updateById(user);
}
}
4.1.3 AUTO策略
(1)设置生成策略为AUTO
- AUTO的作用是使用数据库ID自增。
- 在使用该策略的时候一定要确保对应的数据库表设置了ID主键自增,否则无效。
@Data
@TableName("tbl_user")
public class User {
//设置生成策略为AUTO
@TableId(type = IdType.AUTO)
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
(2)其他生成策略
NONE
:不设置id生成策略INPUT
:用户手工输入idASSIGN_ID
:雪花算法生成id(可兼容数值型与字符串型)。ASSIGN_UUID
:以UUID生成算法作为id生成策略。
(3)分布式ID
- 当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储。
- 比如订单表就有可能被存储在不同的服务器上,如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突。
- 这个时候就需要一个全局唯一ID,这个ID就是分布式ID。
4.1.4 INPUT策略
(1)设置生成策略为INPUT
@Data
@TableName("tbl_user")
public class User {
//设置生成策略为INPUT
@TableId(type = IdType.INPUT)
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
(2)添加数据手动设置ID
@Test
void testSave(){
User user = new User();
//添加数据手动设置ID
//设置主键ID的值
user.setId(9L);
user.setName("11L");
user.setPassword("1111");
user.setAge(10);
user.setTel("11110");
userDao.insert(user);
}
4.1.5 ASSIGN_ID策略
(1)设置生成策略为ASSIGN_ID
@Data
@TableName("tbl_user")
public class User {
//设置生成策略为ASSIGN_ID
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
(2)添加数据不设置ID
- 注意:这种生成策略,不需要手动设置ID,如果手动设置ID,则会使用自己设置的值。
- 生成的ID就是一个Long类型的数据。
@Test
void testSave(){
User user = new User();
user.setName("13L");
user.setPassword("1111");
user.setAge(10);
user.setTel("11110");
userDao.insert(user);
}
4.1.6 ASSIGN_UUID策略
(1)设置生成策略为ASSIGN_UUID
- 使用uuid需要注意的是,主键的类型不能是Long,而应该改成String类型。
@Data
@TableName("tbl_user")
public class User {
//设置生成策略为ASSIGN_UUID
@TableId(type = IdType.ASSIGN_UUID)
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
(2)修改表的主键类型
(3)添加数据不设置ID
@Test
void testSave(){
User user = new User();
user.setName("13L");
user.setPassword("1111");
user.setAge(10);
user.setTel("11110");
userDao.insert(user);
}
4.1.7 雪花算法
4.1.8 ID生成策略对比
NONE
:不设置id生成策略,MP不自动生成,约等于INPUT。- 所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突。
- 为了保证主键不冲突就需要做很多判定,实现起来比较复杂。
AUTO
:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用。ASSIGN_UUID
:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢。ASSIGN_ID
:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键。
4.1.9 简化配置
- 如何在项目中的每一个模型类上都使用相同的生成策略?
解决方案
- 在
application.yml
中配置id-type: assign_id
- 每个模型类的主键ID策略都将成为
assign_id
# assign_id
# 每个模型类的主键ID策略都将成为assign_id
mybatis-plus:
global-config:
db-config:
id-type: assign_id
4.1.10 数据库表与模型类的映射关系
- MP会默认将模型类的类名名首字母小写作为表名使用,假如数据库表的名称都以tbl_开头,那么我们就需要将所有的模型类上添加
@TableName
。 - 麻烦。
- 那么该如何简化呢?
- 在
application.yml
中配置table-prefix: tbl_
- 设置表的前缀内容,这样MP就会拿
tbl_
加上模型类的首字母小写,就刚好组装成数据库的表名。
# assign_id
# 每个模型类的主键ID策略都将成为assign_id
mybatis-plus:
global-config:
db-config:
id-type: assign_id
# 在application.yml中配置id-type: assign_id
table-prefix: tbl_
4.2 多记录操作(删除-查询)(Delete-Selete)
4.2.1 删除指定多条数据
(1)对应的API方法
- 删除(根据ID 批量删除),参数是一个集合,可以存放多个id值。
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
(2)需求:根据传入的id集合将数据库表中的数据删除掉
- 执行成功后,数据库表中的数据就会按照指定的id进行删除。
@Test
void testDelete(){
//删除指定多条数据
List<Long> list = new ArrayList<>();
list.add(1567880975352897538L);
list.add(9l);
list.add(8L);
userDao.deleteBatchIds(list);
}
4.2.2 查询指定多条数据
(1)对应的API方法
- 查询(根据ID 批量查询),参数是一个集合,可以存放多个id值。
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
(2)需求:根据传入的ID集合查询用户信息
@Test
void testGetByIds(){
//查询指定多条数据
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(2l);
list.add(3L);
userDao.selectBatchIds(list);
}
4.3 逻辑删除(Delete/Update)
- 物理删除:业务数据从数据库中丢弃,执行的是delete操作。
- 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的是update操作。
4.3.1 MP中逻辑删除具体实现
(1)修改数据库表添加deleted列
- 字段名可以任意,内容也可以自定义,比如0代表正常,1代表删除,可以在添加列的同时设置其默认值为0正常。
(2)实体类添加属性
- 添加与数据库表的列对应的一个属性名,名称可以任意,如果和数据表列名对不上,可以使用
@TableField
进行关系映射,如果一致,则会自动对应。 - 标识新增的字段为逻辑删除字段,使用
@TableLogic
。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
//@TableLogic
@TableLogic(value = "0",delval = "1")
//value为正常数据的值,delval为删除数据的值
private Integer deleted;
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
(3)运行删除方法
- 逻辑删除最后走的是
update
操作,会将指定的字段修改成删除状态对应的值。
@Test
void testDelete2(){
userDao.deleteById(7L);
}
(4)逻辑删除,对查询的影响?
- 执行查询操作
@Test
void testFind(){
System.out.println(userDao.selectList(null));
}
- 打印出来的sql语句中会多一个查询条件。
SELECT id,name,password,age,tel,deleted FROM user WHERE deleted=0
- WHERE deleted=0
- MP的逻辑删除会将所有的查询都添加一个未被删除的条件,也就是已经被删除的数据是 不应该被查询出来的。
(5)已经删除的数据都查询出来该如何实现(UserDao)
@Mapper
public interface UserDao extends BaseMapper<User> {
//查询所有数据包含已经被删除的数据
@Select("select * from user")
public List<User> selectAll();
}
(6)全局配置
- 如果每个表都要有逻辑删除,那么就需要在每个模型类的属性上添加
@TableLogic
注解 - 麻烦
- 在配置文件中添加全局配置:
global-config:
db-config:
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-delete-value: 0
# 逻辑删除字面值:删除为1
logic-not-delete-value: 1
4.3.2 逻辑删除的本质
- 逻辑删除的本质其实是修改操作。
- 如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
- 实际的 SQL语句:
UPDATE user SET deleted=1 where id = ? AND deleted=0
4.3.3 知识点:@TableLogic
4.4 乐观锁(Update)
4.4.0 乐观锁插件
- 想不明白了去这里。
- 乐观锁插件官方文档:https://baomidou.com/pages/0d93c0/#optimisticlockerinnerinterceptor
4.4.1 实现思路(乐观锁的实现方式)
4.4.2 实现步骤
(1)数据库表添加列
(2)在模型类中添加对应的属性
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
@TableLogic(value = "0",delval = "1")
//value为正常数据的值,delval为删除数据的值
private Integer deleted;
//在模型类中添加对应的属性
@Version
private Integer version;
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
(3)添加乐观锁的拦截器
@Configuration
一定要注意!
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
(4)执行更新操作-添加version数据
- 传递的是1,MP会将1进行加1,然后,更新回到数据库表中。
@Test
void testUpdate1(){
User user = new User();
user.setId(3L);
user.setName("Jock666");
//添加version数据
user.setVersion(1);
userDao.updateById(user);
}
- 实现乐观锁,首先第一步应该是拿到表中的version;
- 然后拿version当条件在将version加1更新回到数据库表中;
- 所以我们在查询的时候,需要对其进行查询。
@Test
void testUpdate3(){
//1.先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(3L);
//2.将要修改的属性逐一设置进去
user.setName("Jock888");
userDao.updateById(user);
}
(5)模拟加锁情况
- 实现多个人修改同一个数据的时候,只能有一个人修改成功。
@Test
void testUpdate4(){
//1.先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(3L); //version=3
User user2 = userDao.selectById(3L); //version=3
user2.setName("Jock aaa");
userDao.updateById(user2); //version=>4
user.setName("Jock bbb");
userDao.updateById(user); //version=3?条件还成立吗?不成立
}
Comments | NOTHING