MyBatisPlus简述

news2024/11/17 23:56:31

文章目录

    • 一、MyBatisPlus入门案例与简介
      • 1.入门案例
      • 2.springboot整合mybatis的方式
      • 3.springboot整合mybatisplus
        • 步骤1.创建环境,上面我们已经创建过了
        • 步骤2.创建数据库及表
        • 步骤2.pom.xml补全依赖
        • 步骤3.添加MP的相关配置信息
        • 步骤4.根据数据库表创建实体类
        • 步骤5.创建Dao接口
        • 步骤6.编写引导类
        • 步骤7.编写测试类
    • 二、CRUD使用
      • 1.新增
      • 2.删除
      • 3.修改
      • 4.根据id查
      • 5.查询所有
      • 6.分页查询
    • 三、DQL编程控制
      • 1.条件查询
        • 第一种:QueryWrapper
        • 第二种:QueryWrapper的基础上使用lambda
        • 第三种:LambdaQueryWrapper
      • 2.多条件查询
      • 3.查询投影字段
      • 4.聚合查询
      • 5.分组查询
      • 6.等值查询
      • 7.范围查询
      • 8.模糊查询
      • 9.排序查询
      • 10.映射匹配兼容性
        • 1.表字段与编码属性设计不同步
        • 2.编码中添加了数据库中未定义的属性
        • 3.设置查询权限,对于有的敏感信息不返回前端
        • 4.表名与编码开发设计不同步
    • 四、DML编程控制
      • 1.id生成策略--AUTO
      • 2.id生成策略--INPUT
      • 3.id生成策略--ASSIGN_ID
      • 4.id生成策略--ASSIGN_UUID
      • 5.ID生成策略对比
    • 五、多记录操作
      • 1.批量删除
      • 2.逻辑删除
        • 1.修改数据库表添加`deleted`列
        • 2.实体类添加属性
        • 3.运行测试方法
    • 六、乐观锁
      • 1.数据库表添加列
      • 2.在模型类中添加对应的属性
      • 3.添加乐观锁的拦截器
      • 4.执行更新操作
      • ps:模拟一下加锁的情况
    • 七、结语

主要掌握

基于MyBatisPlus完成标准Dao的增删改查功能

掌握MyBatisPlus中的分页及条件查询构建

掌握主键ID的生成策略

一、MyBatisPlus入门案例与简介

1.入门案例

  • 创建springboot工程
    在这里插入图片描述
    在这里插入图片描述
  • MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率。

  • 开发方式

    • 基于MyBatis使用MyBatisPlus
    • 基于Spring使用MyBatisPlus

2.springboot整合mybatis的方式

参考我的这篇博客–>springboot整合mybatis的方式

3.springboot整合mybatisplus

步骤1.创建环境,上面我们已经创建过了

步骤2.创建数据库及表

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');

步骤2.pom.xml补全依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>
  • druid数据源可以加也可以不加,SpringBoot有内置的数据源,可以配置成使用Druid数据源

步骤3.添加MP的相关配置信息

在这里插入图片描述
说明:serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为Asia/Shanghai

步骤4.根据数据库表创建实体类

public class User {   
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    //setter...getter...toString方法略
}

步骤5.创建Dao接口

@Mapper
public interface UserDao extends BaseMapper<User> {
}

步骤6.编写引导类

@SpringBootApplication
//@MapperScan("com.example.dao")
public class Mybatisplus01Application {
    public static void main(String[] args) {
        SpringApplication.run(Mybatisplus01Application.class, args);
    }
}

**说明:**Dao接口要想被容器扫描到,有两种解决方案:

  • 方案一:在Dao接口上添加@Mapper注解,并且确保Dao处在引导类所在包或其子包中
    • 该方案的缺点是需要在每一Dao接口中添加注解
  • 方案二:在引导类上添加@MapperScan注解,其属性为所要扫描的Dao所在包
    • 该方案的好处是只需要写一次,则指定包下的所有Dao接口都能被扫描到,@Mapper就可以不写。

步骤7.编写测试类

这里以查询所有为例子

@SpringBootTest
class Mybatisplus01ApplicationTests {
    @Autowired
    UserDao userDao;
    @Test
    void testGetAll() {
        List<User> users = userDao.selectList(null);
        System.out.println(users);
    }
}

结果:
在这里插入图片描述

跟之前整合MyBatis相比,我们不需要在DAO接口中编写方法和SQL语句了,只需要继承BaseMapper接口即可。整体来说简化很多。

二、CRUD使用

对于标准的CRUD功能都有哪些以及MP都提供了哪些方法可以使用呢?

我们先来看张图:
在这里插入图片描述

1.新增

int insert (T t)
  • T:泛型,新增用来保存新增数据

  • int:返回值,新增成功后返回1,没有新增成功返回的是0

在测试类中进行新增操作:

    @Test
    void testSave() {
        User user = new User();
        user.setName("一只呆呆木");
        user.setPassword("!666");
        user.setAge(18);
        user.setTel("001");
        userDao.insert(user);
    }

在这里插入图片描述

但是数据中的主键ID,有点长,那这个主键ID是如何来的?我们更想要的是主键自增,应该是5才对,这个就涉及到mp的id生成策略的配置,后面有说~

2.删除

int deleteById (Serializable id)

Serializable:参数类型

  • 思考:参数类型为什么是一个序列化类?
    在这里插入图片描述
    从这张图可以看出,

  • String和Number是Serializable的子类,

  • Number又是Float,Double,Integer等类的父类,

  • 能作为主键的数据类型都已经是Serializable的子类,

  • MP使用Serializable作为参数类型,就好比我们可以用Object接收任何数据类型一样。

  • int:返回值类型,数据删除成功返回1,未删除数据返回0。

    @Test
    void testDelete() {
        userDao.deleteById(1599248419765915649L);//这里的id后面要加L
    }

3.修改

int updateById(T t);
  • T:泛型,需要修改的数据内容,注意因为是根据ID进行修改,所以传入的对象中需要有ID属性值

  • int:返回值,修改成功后返回1,未修改数据返回0

在测试类中进行修改操作:

    @Test
    void testUpdate() {
        User user = new User();
        user.setId(1L);
        user.setName("Tom888");
        user.setPassword("tom888");
        userDao.updateById(user);
    }

修改前:
在这里插入图片描述

修改后:
在这里插入图片描述
**说明:**修改的时候,只修改实体对象中有值的字段。

4.根据id查

T selectById (Serializable id)
  • Serializable:参数类型,主键ID的值
  • T:根据ID查询只会返回一条数据

在测试类中进行查询操作:

    @Test
    void testGetById() {
        User user = userDao.selectById(4L);
        System.out.println(user);
    }

在这里插入图片描述

5.查询所有

List<T> selectList(Wrapper<T> queryWrapper)
  • Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
  • List:因为查询的是所有,所以返回的数据是一个集合

在测试类中进行查询操作:

    @Test
    void testGetAll() {
        List<User> users = userDao.selectList(null);
        System.out.println(users);
    }

我们所调用的方法都是来自于DAO接口继承的BaseMapper类中。里面的方法有很多,这里列了几个基本的增删改查的。

6.分页查询

使用分页查询我们需要设置分页拦截器,否则将会查询所有

IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
  • IPage:用来构建分页查询条件
  • Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
  • IPage:返回值,你会发现构建分页条件和方法的返回值都是IPage

IPage是一个接口,我们需要找到它的实现类来构建它,具体的实现类,可以进入到IPage类中按ctrl+h,会找到其有一个实现类为Page
在这里插入图片描述
测试类:

//分页查询
    @Test
    void testSelectPage(){
        //1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
        IPage<User> page=new Page<>(1,3);
        //2 执行分页查询
        userDao.selectPage(page,null);
        //3 获取分页结果
        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());
    }

使用分页查询我们需要设置分页拦截器,否则将会查询所有
在这里插入图片描述

我们明明查的一页中有三条数据,但是却给我们查到了所有的数据,这显然没有达到分页的目的,这个时候我们就需要设置分页拦截器。

设置分页拦截器:

这个拦截器MP已经为我们提供好了,我们只需要将其配置成Spring管理的bean对象即可。

创建一个config配置包,然后创建拦截器

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        //2 添加分页拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}

运行测试程序:
在这里插入图片描述

三、DQL编程控制

1.条件查询

在进行查询的时候,我们的入口是在Wrapper这个类上,因为它是一个接口,所以我们需要去找它对应的实现类,关于实现类也有很多,说明我们有多种构建查询条件对象的方式,
在这里插入图片描述

第一种:QueryWrapper

    @Test
    void testGetByCon(){
        QueryWrapper qw = new QueryWrapper();
        qw.lt("age",18);
        List<User> userList = userDao.selectList(qw);
        System.out.println(userList);
    }
  • lt: 小于(<) ,最终的sql语句为
SELECT id,name,password,age,tel FROM user WHERE (age < ?)

在这里插入图片描述

第二种:QueryWrapper的基础上使用lambda

    @Test
    void testGetByCon2(){
        QueryWrapper<User> qw = new QueryWrapper<User>();
        qw.lambda().lt(User::getAge, 18);//添加条件
        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的时候泛型不能省。

第三种:LambdaQueryWrapper

    @Test
    void testGetByCon3(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(User::getAge, 18);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

2.多条件查询

需求:查询数据库表中,年龄在10岁到30岁之间的用户信息

    @Test
    void testGetByCons(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(User::getAge, 30).gt(User::getAge, 10);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

需求:查询数据库表中,年龄小于10或年龄大于30的数据

@Test
    void testGetAll(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
        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.查询投影字段

查询投影即不查询所有字段,只查询出指定内容的数据。

    @Test
    void testGetSome(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.select(User::getId,User::getName,User::getAge);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

在这里插入图片描述

  • select(…)方法用来设置查询的字段列,可以设置多个,最终的sql语句为:
SELECT id,name,age FROM user
  • 如果使用的不是lambda,就需要手动指定字段
    @Test
    void testGetSome(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        lqw.select("id","name","age","tel");
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

4.聚合查询

count:总记录数

max:最大值

min:最小值

avg:平均值

sum:求和

    @Test
    void testGetByFunc(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        //lqw.select("count(*) as count");
        //SELECT count(*) as count FROM user
        //lqw.select("max(age) as maxAge");
        //SELECT max(age) as maxAge FROM user
        //lqw.select("min(age) as minAge");
        //SELECT min(age) as minAge FROM user
        //lqw.select("sum(age) as sumAge");
        //SELECT sum(age) as sumAge FROM user
        lqw.select("avg(age) as avgAge");
        //SELECT avg(age) as avgAge FROM user
        List<Map<String, Object>> userList = userDao.selectMaps(lqw);
        System.out.println(userList);
    }

5.分组查询

    @Test
    void testGetGroup(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        lqw.select("count(*) as count,tel");
        lqw.groupBy("tel");
        List<Map<String, Object>> list = userDao.selectMaps(lqw);
        System.out.println(list);
    }
  • groupBy为分组,最终的sql语句为
SELECT count(*) as count,tel FROM user GROUP BY tel

在这里插入图片描述

注意:

  • 聚合与分组查询,无法使用lambda表达式来完成
  • MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实现

6.等值查询

需求:根据用户名和密码查询用户信息

    @Test
    void testGetEqu(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
        User loginUser = userDao.selectOne(lqw);
        System.out.println(loginUser);
    }

在这里插入图片描述

  • eq(): 相当于 =,对应的sql语句为
SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
  • selectList:查询结果为多个或者单个

  • selectOne:查询结果为单个

7.范围查询

对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询

  • gt():大于(>)
  • ge():大于等于(>=)
  • lt():小于(<)
  • lte():小于等于(<=)
  • between():between ? and ?
    @Test
    void testGetByRange(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.between(User::getAge, 18, 30);
        //SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

在这里插入图片描述

8.模糊查询

查询表中name属性的值以开头的用户信息,使用like进行模糊查询

    @Test
    void testGetFuzzy(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.likeLeft(User::getName, "一");
        //SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

在这里插入图片描述

  • like():前后加百分号,如 %一%
  • likeLeft():前面加百分号,如 %一
  • likeRight():后面加百分号,如 一%

9.排序查询

查询所有数据,然后按照id降序

    @Test
    void testGetSort() {
        LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
        /**
         * condition :条件,返回boolean,
         当condition为true,进行排序,如果为false,则不排序
         * isAsc:是否为升序,true为升序,false为降序
         * columns:需要操作的列
         */
        lwq.orderBy(true, false, User::getId);
        List<User> users = userDao.selectList(lwq.orderBy(true, false, User::getId));
        System.out.println(users);
    }

在这里插入图片描述

  • orderBy排序
    • condition:条件,true则添加排序,false则不添加排序
    • isAsc:是否为升序,true升序,false降序
    • columns:排序字段,可以有多个
  • orderByAsc/Desc(单个column):按照指定字段进行升序/降序
  • orderByAsc/Desc(多个column):按照多个字段进行升序/降序
  • orderByAsc/Desc
    • condition:条件,true添加排序,false不添加排序
    • 多个columns:按照多个字段进行排序

除了上面介绍的这几种查询条件构建方法以外还会有很多其他的方法,比如isNull,isNotNull,in,notIn等等方法可供选择,具体参考官方文档

的条件构造器来学习使用,具体的网址为:

<条件构造器 | MyBatis-Plus (baomidou.com)>

10.映射匹配兼容性

前面我们已经能从表中查询出数据,并将数据封装到模型类中,这整个过程涉及到一张表和一个模型类:
在这里插入图片描述

之所以数据能够成功的从表中获取并封装到模型对象中,原因是表的字段列名和模型类的属性名一样。

1.表字段与编码属性设计不同步

当表的列名和模型类的属性名发生不一致,就会导致数据封装不到模型对象,这个时候就需要其中一方做出修改,那如果前提是两边都不能改又该如何解决?

MP给我们提供了一个注解@TableField,使用该注解可以实现模型类属性名和表的列名之间的映射关系
在这里插入图片描述

2.编码中添加了数据库中未定义的属性

当模型类中多了一个数据库表不存在的字段,就会导致生成的sql语句中在select的时候查询了数据库不存在的字段,程序运行就会报错,错误信息为:

Unknown column ‘多出来的字段名称’ in ‘field list’

具体的解决方案用到的还是@TableField注解,它有一个属性叫exist,设置该字段是否在数据库表中存在,如果设置为false则不存在,生成sql语句查询的时候,就不会再查询该字段了。
在这里插入图片描述

3.设置查询权限,对于有的敏感信息不返回前端

@TableField注解的一个属性叫select,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段。

public class User {
    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;
}

4.表名与编码开发设计不同步

我们在查询时,其实并不是我们指定的查询表明,而是当我们设计好实体类时,在查询时,会默认把实体类名首字母变小写然后作为查询的表名,比如这里创建的User实体类,而我们的数据库表正好也是user,索引转换后刚好对应,就成功查询到啦,但是入过数据库表不是user呢?再转换就会出错,所以需要我们设置一个别名
在这里插入图片描述
解决方案是使用MP提供的另外一个注解@TableName来设置表与模型类之间的对应关系。
在这里插入图片描述

四、DML编程控制

前面新增成功后,主键ID是一个很长串的内容,我们更想要的是按照数据库表字段进行自增长,在解决这个问题之前,我们先来分析下ID该如何选择:

不同的表应用不同的id生成策略

  • 日志:自增(1,2,3,4,……)
  • 购物订单:特殊规则(FQ23948AK3843)
  • 外卖单:关联地区日期等信息(10 04 20200314 34 91)
  • 关系表:可省略id

MP中主键生成策略需要使用一个MP注解@TableId

1.id生成策略–AUTO

public class User {
    @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;
}

因为之前生成主键ID的值比较长,会把MySQL的自动增长的值变的很大,所以需要将其调整为目前最新的id值。

在修改前先把这个自增勾去掉点一下保存然后再勾上,然后再整为目前最新的id值,如果不这样可能会出现修改了新的id值后插入仍然没用的情况
在这里插入图片描述
在这里插入图片描述

根据你数据表中的数据设置新的id值

    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;

2.id生成策略–INPUT

	@TableId(type = IdType.INPUT)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;

把这里的自动递增去掉,然后保存设置
在这里插入图片描述

需要自己手动设置id,不设置会报错

    @Test
    void testSave() {
        User user = new User();
        user.setId(6L);
        user.setName("一只小可爱~");
        user.setPassword("!666");
        user.setAge(18);
        user.setTel("001");
        userDao.insert(user);
    }

3.id生成策略–ASSIGN_ID

	@TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @Test
    void testSave() {
        User user = new User();
        user.setName("一只小可爱~");
        user.setPassword("!666");
        user.setAge(18);
        user.setTel("001");
        userDao.insert(user);
    }

**注意:**这种生成策略,不需要手动设置ID,如果手动设置ID,则会使用自己设置的值。生成的ID就是一个Long类型的数据。
在这里插入图片描述

4.id生成策略–ASSIGN_UUID

使用uuid需要注意的是,主键的类型不能是Long,而应该改成String类型,否则报错,注意实体类中也要做出修改!
在这里插入图片描述

所以需要修改id为varchar类型的,并且长度要大于32,因为UUID生成的主键为32位,如果长度小的话就会导致插入失败。
在这里插入图片描述

	@TableId(type = IdType.ASSIGN_UUID)
    private String id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @Test
    void testSave() {
        User user = new User();
        user.setName("一只小可爱~");
        user.setPassword("!666");
        user.setAge(18);
        user.setTel("001");
        userDao.insert(user);
    }

在这里插入图片描述

5.ID生成策略对比

介绍了这些主键ID的生成策略,我们以后该用哪个呢?

  • NONE: 不设置id生成策略,MP不自动生成,约等于INPUT,所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突,为了保证主键不冲突就需要做很多判定,实现起来比较复杂
  • AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用
  • ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢
  • ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键
  • 综上所述,每一种主键策略都有自己的优缺点,根据自己项目业务的实际情况来选择使用才是最明智的选择。

五、多记录操作

1.批量删除

nt deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    @Test
    void testDeleteByIds(){
        //删除指定多条数据
        List<Long> list = new ArrayList<>();
        list.add(3L);
        list.add(4L);
        userDao.deleteBatchIds(list);
    }

在这里插入图片描述

2.逻辑删除

对于删除操作业务问题来说有:

  • 物理删除:业务数据从数据库中丢弃,执行的是delete操作
  • 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的是update操作

MP中逻辑删除具体该如何实现?

1.修改数据库表添加deleted

字段名可以任意,内容也可以自定义,比如0代表正常,1代表删除,可以在添加列的同时设置其默认值为0正常
在这里插入图片描述

2.实体类添加属性

标识新增的字段为逻辑删除字段,使用@TableLogic

private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @TableLogic(value = "0", delval = "1")
    private Integer deleted;

3.运行测试方法

    @Test
    void testDeleteById(){
        userDao.deleteById(1L);
    }

在这里插入图片描述

  • MP的逻辑删除会将所有的查询都添加一个未被删除的条件,也就是已经被删除的数据是不应该被查询出来的。

  • 如果还是想把已经删除的数据都查询出来该如何实现呢?

这就需要我们手动书写查询sql了

@Mapper
public interface UserDao extends BaseMapper<User> {
    //查询所有数据包含已经被删除的数据
    @Select("select * from user")
    public List<User> selectAll();
}	
    @Test
    void testGetAll() {
List<User> users = userDao.selectAll();
        System.out.println(users);
    }

在这里插入图片描述

六、乐观锁

乐观锁主要解决的问题是当要更新一条记录的时候,希望这条记录没有被别人更新。

乐观锁的实现方式:

  • 数据库表中添加version列,比如默认值给1
  • 第一个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
  • 第二个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
  • 第一个线程执行更新时,set version = newVersion where version = oldVersion
    • newVersion = version+1 [2]
    • oldVersion = version [1]
  • 第二个线程执行更新时,set version = newVersion where version = oldVersion
    • newVersion = version+1 [2]
    • oldVersion = version [1]
  • 假如这两个线程都来更新数据,第一个和第二个线程都可能先执行
    • 假如第一个线程先执行更新,会把version改为2,
    • 第二个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第二个线程会修改失败
    • 假如第二个线程先执行更新,会把version改为2,
    • 第一个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第一个线程会修改失败
    • 不管谁先执行都会确保只能有一个线程更新数据,这就是MP提供的乐观锁的实现原理分析。

具体的实现步骤如下:

1.数据库表添加列

列名可以任意,比如使用version,给列设置默认值为1
在这里插入图片描述

2.在模型类中添加对应的属性

根据添加的字段列名,在模型类中添加对应的属性值

    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @TableLogic(value = "0", delval = "1")
    private Integer deleted;
    @Version
    private Integer version;

3.添加乐观锁的拦截器

@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        //1.定义Mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        //2.添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}

4.执行更新操作

要想实现乐观锁,首先第一步应该是拿到表中的version,然后拿version当条件在将version加1更新回到数据库表中,所以我们在查询的时候,需要对其进行查询

    @Test
    void testUpdate() {
        User user = userDao.selectById(2L);
        user.setName("mumu");
        user.setPassword("666");
        userDao.updateById(user);
    }

在这里插入图片描述

ps:模拟一下加锁的情况

看看能不能实现多个人修改同一个数据的时候,只能有一个人修改成功。

    @Test
    void testUpdate() {
        User user = userDao.selectById(2L);
        User user1 = userDao.selectById(2L);
        
        user.setName("mumu");
        user.setPassword("666");
        userDao.updateById(user);
        
        user1.setName("mumu");
        user1.setPassword("666999");
        userDao.updateById(user1);
    }

运行前:
在这里插入图片描述

运行后:
在这里插入图片描述

我们发现这个时候只执行了user的修改,而user1的并没成功修改,说明乐观锁实现了

七、结语

感谢各位大佬观看,如有问题可以评论私信哦,互相学习~谢谢

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/63338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux安装redis哨兵

安装环境 服务器一台&#xff1a; 服务器IP&#xff1a;172.169.3.251主从端口&#xff1a;6379、6380、6381哨兵端口&#xff0c;26379、26380、26381安装目录&#xff1a;/usr/local/redis配置文件目录&#xff1a;/usr/loca/redis/conf redis安装 1、下载redis wget ht…

设计模式--策略模式

文章目录前言一、未使用设计模式二、策略模式1.定义2.结构三、应用场景四、优缺点优缺参考资料前言 需求&#xff1a; 一天&#xff0c;产品经理走过来对你说。猫啊&#xff08;自称&#xff09;&#xff0c;帮我设计一个计算器&#xff0c;需要的功能有求最大值&#xff0c;最…

【JavaEE-Servlet】Filter过滤器详解

Filter过滤器熟悉的关键字-Filter&#xff08;回顾-联系-可以不看&#xff09;Filter概述何时使用&#xff1f;Filter生命周期过滤器单个实现doFilter方法关于Filter的配置路径Filter的执行顺序在web.xml文件中进行配置的时候&#xff0c;Filter的执行顺序是什么&#xff1f;使…

HTML期末学生大作业-班级校园我的校园网页设计与实现html+css+javascript

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

[附源码]Python计算机毕业设计Django停车场管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

使用 JavaScript 检测用户是否在线

有时您可能希望增强您的应用程序以通知用户他们可能已经失去了互联网连接。 用户可能正在访问您的网站并收到缓存版本&#xff0c;因此通常看起来他们的互联网仍在工作。 然而&#xff0c;他们失去了引擎盖下的连接&#xff0c;并且不会加载任何新内容。 在这里向他们显示一些…

博客系统(页面设计)

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录前言一、【博客列表页】blog_list.html参考代码&#xff1a;二、【博客详情页】 blog_detail.html参考代码&#xff1a;三、【博客登录页】login.html参考代码&#xff1a;四、【博客编辑页】参考代码&#xff1a;【…

QFtp的使用

1. 环境 Win10 QT5.9.9 2. 相关说明 Qt5.0之后移除了QFtp类&#xff08;基于FTP协议实现的一个类&#xff09;&#xff0c;并且使用 QNetworkAccessManager 实现了 Ftp 的上传/下载功能。尽管后者在性能上和稳定性上有所提升&#xff0c;但有些原本 QFtp 有的功能 QNetwork…

什么是微服务?

文章目录什么是微服务微服务技术栈单体架构分布式架构认识微服务微服务技术框架SpringCloud什么是微服务 当我们提到微服务&#xff0c;很多人第一反应就是SpringCloud&#xff0c;但是微服务技术并不能与SpringCloud完全划等号&#xff1a; 微服务是分布式架构的一种&#x…

AI视频监控在畜牧养殖中的技术应用解决方案

一、方案概况 随着养殖业迅猛发展的同时也给养殖业主带来了严峻挑战。对养殖业来说&#xff0c;养殖场大多建立在偏远地区&#xff0c;给集中管理带来不便&#xff1b;畜禽养殖成本大&#xff0c;丢失、偷盗等情况时有发生&#xff0c;容易造成巨大的经济损失。建立一套远程视…

013. N 皇后

1.题目链接&#xff1a; 51. N 皇后 2.解题思路&#xff1a; 2.1.题目要求&#xff1a; 给一个数字 n &#xff0c;要求返回所有 n 个 皇后能在 n X n 的棋盘上 不相互攻击 的情况。 能攻击到的情况&#xff1a;以皇后自身为原点&#xff0c;横、竖、同斜线&#xff08;45度…

Linux搭建单机多进程zookeeper集群

01 ZooKeeper是什么 ZooKeeper 是一个开放源码的分布式协调服务&#xff0c;它是集群的管理者&#xff0c;监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终&#xff0c;将简单易用的接口和性能高效、功能稳定的系统提供给用户。 分布式应用程序可以基…

用数字隔离器取代传统的光耦合器

介绍 光耦合器是一种已有几十年历史的技术&#xff0c;广泛用于信号隔离&#xff0c;通常提供安全隔离、信号电平移位和地面回路缓解。它们通常用于广泛的终端应用中&#xff0c;包括数据通信电路、开关模式电源系统、测量和测试系统&#xff0c;以及孤立的数据采集系统。光耦…

【入门】初识深度学习

文档背景 机器学习和深度学习的概念十分火热。听上去也很难&#xff0c;不慌&#xff0c;有时候就需要行动在前脑子在后。不管&#xff0c;干就完啦。 前言 人工智能&#xff08;ArtificialIntelligence&#xff0c;AI&#xff09;是最宽泛的概念&#xff0c;是研发用于模拟、延…

数据库基本语法

SQL常用语句总结 mysql -u root -p mysql会提示你输入密码&#xff0c;输入安装配置MySQL服务时设置的密码即可。 输入如下命令生成样例数据库&#xff1a; CREATE DATABASE university; USE university; SOURCE <DLL.sql文件路径>; SOURCE <InsertStatements.sql文…

Jenkins实践指南--pipeline概述

1.pipeline概述 1.1 什么是pipeline 从某种抽象层次上讲&#xff0c;部署流水线&#xff08;Deployment pipeline&#xff09;是指从软件版本控制库到用户手中这一过程的自动化表现形式。——《持续交付-发布可靠软件的系统方法》 pipeline 英语愿意为管道&#xff0c;在Jen…

Python之路—200行Python代码搞了个打飞机游戏!!

早就知道pygame模块&#xff0c;就是没怎么深入研究过&#xff0c;恰逢这周未没约到妹子&#xff0c;只能自己在家玩自己啦&#xff0c;一时兴起&#xff0c;花了几个小时写了个打飞机程序。 很有意思&#xff0c;跟大家分享下。 先看一下项目结构 1 2 3 4 5 6 7 8 9 10 11 1…

盒子模型-css

个人学习笔记 文章目录1.什么是盒子模型&#xff1f;2.外边距3.css边框4.内边距1.什么是盒子模型&#xff1f; HTML文档中的每个元素都被描绘成矩形盒子&#xff0c;这些矩形盒子通过一个模型来描述其占用的空间&#xff0c;这个模型称为盒子模型。 盒子模型用四个边界描述&am…

打字的哪阿空扥

打字的那些事之快速移动光标 一## 、提出快速移动光标的原因 &#xff08;1&#xff09;键盘移动光标方向键不合理 情景再现&#xff1a; 当我们在打出一句话时&#xff0c;其中一个字打错&#xff0c;我们就需要去删除它&#xff0c;此时我们做的动作&#xff0c;无非有两种…

Linux知识结构体系简述

Linux 是一套免费使用和自由传播的类 Unix 操作系统&#xff0c;是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多 CPU 的操作系统。严格来讲&#xff0c;Linux 这个词本身只表示 Linux 内核&#xff0c;但实际上人们已经习惯了用 Linux 来形容整个基于 Linux 内核&…