谷粒学院16万字笔记+1600张配图(二)——MybatisPlus

项目源码与所需资料
链接:https://pan.baidu.com/s/1azwRyyFwXz5elhQL0BhkCA?pwd=8z59
提取码:8z59

文章目录

demo02-MybatisPlus

1.mybatisplus简介及特性

1.1简介

MybatisPlus(简称MP)是一个Mybatis的增强工具,在Mybatis的基础上只做增强不做改变,为简化开发提高效率而生

1.2特性(先了解混眼熟即可)

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 支持关键词自动转义:支持数据库关键词(order、key…)自动转义,还可自定义关键词
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
  • 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击

2.mybatisplus入门案例

2.1创建并初始化数据库

2.1.1创建数据库mybatis_plus

在这里插入图片描述

注意:这里的基字符集一定要选择utf8,否则中文数据会乱码,这里不设置字符集的话后面你一定会后悔,你会发现后面再重新改变数据库字符集为utf8中文仍然乱码,于是乎你只能删掉数据库一切重来!!!没错,我就是这样!!![哭哭哭]

2.1.2创建user表

DROP TABLE IF EXISTS USER;

CREATE TABLE USER
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    `name` VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);

因为name是关键字,所以第6行我使用`name`而不是使用name

创建user表的步骤依次是:先将上述sql语句复制到sqlyog中,然后①选中这些sql语句②点击数据库mybatis_plus以选中该数据库③点击Execute All Queries执行选中的sql语句

在这里插入图片描述

2.1.3向user表中插入数据

DELETE FROM USER;

INSERT INTO USER (id, `name`, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');

同理,第3行我也是使用`name`而不是使用name,然后我们执行这些sql语句完成数据插入

2.2创建一个Spring Boot工程

接下来我们使用Spring Initializr快速初始化一个Spring Boot工程

1.File–>New–>Project…

在这里插入图片描述

2.选中Spring Initializr然后按照下图进行填写

在这里插入图片描述

3.这里的Spring Boot我们暂且不需要管,随便选择一个版本即可,工程创建完成后我们可以在代码中进行修改

在这里插入图片描述

4.接下来我们修改Spring Boot的版本:打开工程的pom.xml文件,并将2.6.10改为2.2.1.RELEASE

在这里插入图片描述

5.此时是爆红的,我们点击"Load Maven Changes"进行更新,等待进度条跑完就不会爆红了

在这里插入图片描述

在这里插入图片描述

6.删掉下图中方框的部分

在这里插入图片描述

2.3引入相关依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.5</version>
    </dependency>
    
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
    <!--lombok用来简化实体类-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

将上述代码复制到pom.xml中

在这里插入图片描述

2.4安装lombok插件

1.点击File–>Settings…

在这里插入图片描述

2.点击Plugins,然后在搜索栏搜索lombok,在"Marketplace"找到辣椒图标的这个插件进行安装(如果没找到那就是曾经安装过这个插件)

你看,我的在"Marketplace"就没有这个辣椒图标的插件,然后在"Installed"中可以看到曾经已经安装过了

在这里插入图片描述

在这里插入图片描述

2.5配置

在application.properties配置文件中添加MySQL数据库的相关配置:

#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root

在这里插入图片描述

注意:

1.上述配置中数据库的账号密码填自己的

2.有人可能会疑问:老师并没有加useUnicode=true&characterEncoding=utf-8这行代码:

  • 我是后面发现数据库中文数据乱码才加的这句话,既然你还能看到这句话,那就说明我加上这句话可以正常做完整个项目,至于你想不想加看你自己了,只要以后如果可能出现中文乱码你能想到这种解决办法就行

3.有人可能会疑问为什么以前驱动字符串com.mysql.jdbc.Driver而这里的驱动字符串com.mysql.cj.jdbc.Driver呢?

这是因为**Spring Boot2.1集成了8.0版本的jdbc驱动,**所以我们使用2.1版本(包括2.1)之后Spring Boot时需要作出如下两处修改:

  • 用新的驱动字符串com.mysql.cj.jdbc.Driver
  • 以前建立连接jdbc:mysql://localhost:3306/mybatis_plus,这里我们需要在最后还加上serverTimezone=GMT%2B8表示时区

2.6编写代码

1.在mpdemo1010包下分别创建包entity和mapper

在这里插入图片描述

2.在entity包下创建实体类User并进行编写

@Data
public class User {
    
    
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

在这里插入图片描述

3.在mapper包下创建UserMapper接口并使其继承BaseMapper<>接口

我们之前写项目时会对应的再来一个UserMapper.xml文件以编写增删改查代码,但是MybatisPlus给我们进行了封装,我们不再需要UserMapper.xml文件,只需要让UserMapper接口继承BaseMapper<>接口(泛型就选择我们的实体类User),继承后我们就可以使用BaseMapper接口为我们提供的增删改查方法

在这里插入图片描述

4.我们刚刚创建的UserMapper是一个接口,springboot启动时默认会找UserMapper接口的实现类的对象,但是UserMapper接口并没有实现类,所以就会找不到,我们需要在启动类上加注解@MapperScan,这样的话springboot启动时就会在编译后生成接口相应的实现类的对象(即代理对象)

@MapperScan("com.atguigu.mpdemo1010.mapper")

在这里插入图片描述

2.7测试

1.先给测试类Mpdemo1010ApplicationTests及测试方法contextLoads都加上public修饰

在这里插入图片描述

在这里插入图片描述

2.将mapper注入到我们的测试类中

我们以前写项目都是mapper注入到service,service注入到controller对吧,但是我们这里只是先测试一下,简单认识一下mybatisplus,现在只有mapper层,还没有service层和controller层,所以这里直接将mapper注入到我们的测试类中

@Autowired
private UserMapper userMapper;

我们会发现UserMapper下有红色波浪线并且报错"Could not autowire.No beans of ‘UserMapper’ type found."首先,这并不是一个真正的报错,不用管它项目一样正常运行

在这里插入图片描述

如果有强迫症想要解决这个"报错"我们需要先明白为什么会出现这样的情况:因为idea有自动检测的功能,在java接口中接口是不能够直接创建bean的,所以idea认为这个语法不合理,但本质上在项目启动时mybatisplus自动创建了接口的动态代理实现类,所以从项目的运行角度讲这不能算是错

解决办法有两种(我这里使用的方法一):

**方法一:**给UserMapper接口添加注解以表示将其交给spring管理(有很多种注解都可以使用,比如:@Component、@Service、@Repository)

在这里插入图片描述

方法二:

①先打开File->Settings…

②找到Editor->Inspections

③找到Spring->Spring Core->Code->Autowiring for bean class

在这里插入图片描述

④将Severity的Error改为Warning并依次点击Apply、OK

在这里插入图片描述

⑤当然,因为我这里用的方法一,所以进行到第④步就没有再进行下去了


3.在测试方法中编写代码

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

在这里插入图片描述

其中selectList方法的参数是查询条件,我们这里暂且不考虑查询条件,所以传入的参数是null

4.执行测试方法,可以看到查询成功

在这里插入图片描述

至此,我们就完成了一个简单的mybatisplus案例

2.8配置日志

刚刚我们做的时候既然从数据库中查询到了数据,就说明了一定执行了sql语句,那我们怎么看到底层执行的sql语句呢:通过配置日志来实现:

在application.properties配置文件中加上这段代码即可:

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

在这里插入图片描述

然后再次执行测试方法就可以看到一些日志信息,其中就包括了底层执行的sql语句

在这里插入图片描述

2.9添加操作

我们刚刚已经进行了数据库查询操作,接下来我们进行数据库插入操作:

在测试类编写测试方法addUser:

//添加操作
@Test
public void addUser() {
    
    
    User user = new User();
    user.setName("lucy");
    user.setAge(30);
    user.setEmail("[email protected]");

    int result = userMapper.insert(user);
    System.out.println("insert:" + result); //影响的行数
    System.out.println(user); //id自动回填
}

在这里插入图片描述

执行测试方法可以我们可以看到控制台有两个输出语句,并且数据库中成功插入了这条数据

在这里插入图片描述

在这里插入图片描述

有人可能会问了:

  • 我在插入数据时并没有设置id值,为什么数据库中的这条数据有id值呢:
    • 因为mybatisplus会自动生成19位的id值,所以我们在插入数据时不需要设置id值(主键)
  • 那为什么测试方法中的第二条输出语句System.out.println(user);打印user对象时这个对象有id值呢(正常情况下,插入一条数据后(注意我们插入这条user数据时并没有给user对象设置id值),我们认为肯定要从数据库中查询出这条新增的数据,然后将该条数据赋值给user对象,这样的话打印user对象时才会有id值)
    • 这是因为id自动回填

3.主键策略

3.1自增策略

要想主键自增需要配置如下主键策略:

  • 需要在创建数据表时设置主键自增
  • 实体字段中配置@TableId(type = IdType.AUTO)

自增策略的缺点:如果数据是分库/分表的就比较麻烦。比如:

分表的情况:用户数据太庞大了,我想把用户数据放到三张表中进行存储,每张表存储100条数据,那么按照id的自增策略,第一张表中的id是1-100;第二张表中的id是101-200;第三张表中的id是201-300;这样的话第一张表存储满了,准备向第二张表存储数据时,需要先得到第一张表最后一条数据的id并对其进行+1,得到的数字(101)作为第二张表第一条数据的id.

不过呢,虽然分库/分表时自增策略麻烦,但是想要实现的功能一样可以实现

3.2UUID

每次生成随机的值(可以利用数据库也可以利用程序生成),一般来说生成的这个值全球唯一

优点:

  • 因为每次生成的id没有任何关系,所以即使是分库分表也可以直接生成id,不需要关注上张表的id
  • 全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对

缺点:

  • 没有排序,无法保证趋势递增
  • UUID往往是使用字符串存储,查询的效率比较低

3.3Redis生成ID

当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID.这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID.可以用Redis的原子操作INCR和INCRBY来实现.

可以使用Redis集群来获取更高的吞吐量.假如一个集群中有5台Redis,可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5.各个Redis生成的ID为:

A:1,6,11,16,21

B:2,7,12,17,22

C:3,8,13,18,23

D:4,9,14,19,24

E:5,10,15,20,25

优点:

  • 不依赖于数据库,灵活方便,且性能优于数据库
  • 数字ID天然排序,对分页或者需要排序的结果很有帮助

缺点:

  • 如果系统中没有Redis,还需要引入新的组件,增加系统复杂度
  • 需要编码和配置的工作量比较大

3.4ID_WORKER

在"2.9添加操作"中mybatisplus为我们生成id值时使用的是mybatisplus自带的主键策略:ID_WORKER

这个策略使用的是snowflake算法(雪花算法)

3.5使用主键策略

使用主键策略需要在主键上加@TableId注解:

在这里插入图片描述

  • AUTO:自动增长
  • ID_WORKER:mybatisplus自带策略,生成19位值,数字类型使用这种策略,比如long
  • ID_WORKER_STR:mybatisplus自带策略,生成19位值,字符串类型使用这种策略
    • 如果我们没有在主键上添加@TableId注解,那么mybatisplus就会自动识别主键类型,然后根据主键类型自动使用@TableId(type = IdType.ID_WORKER)或@TableId(type = IdType.ID_WORKER_STR),例如"2.9添加操作"就是的
  • INPUT:需要自己设置id值
  • NONE:不使用任何策略,也需要自己设置id值,一般用INPUT而不用NONE
  • UUID:随机唯一值

4.根据ID更新数据

BaseMapper接口为什么提供的更新数据的方法是updateById:

  • 见名知意,这个方法肯定是根据id来定位到某条数据并进行修改,底层的sql语句肯定是UPDATE USER SET age=30 WHERE id=2
  • 这个方法的参数是user对象,所以我们需要将id封装到user对象中,再将user对象传给方法updateById作为参数

在测试类中编写测试方法:

//修改操作
@Test
public void updateUser() {
    
    
    User user = new User();
    user.setId(2L); //参数不能是2:因为id是long类型
    user.setAge(120);
    int row = userMapper.updateById(user);
    System.out.println(row);
}

在这里插入图片描述

执行测试方法后去数据库查看发现修改成功

看一下底层的sql语句:

在这里插入图片描述

我们在测试方法中只给user对象赋值了id和age,然后这里的sql语句的参数就只有id和age,并没有name,email,这是因为底层做了优化,我们给user对象赋值了几个属性,sql语句的参数就有几个

5.自动填充

5.1给user表添加字段

我们先给user表添加两个字段create_time和update_time:

1.右键点击user表选择"改变表"

在这里插入图片描述

2.作出如下修改后保存

在这里插入图片描述

说一下:你们这里的"字符集"肯定是"utf8",“核对"肯定是"utf8_general_ci”,那么恭喜你们是对的.我这里因为创建数据库时没有设置字符集为utf8所以才会这样,等你们后面看到我笔记中哪张截图"字符集"是"utf8","核对"是"utf8_general_ci"那说明我刚苦逼地删库重新建库建表了

5.2给实体类添加属性

给实体类User添加属性createTime和updateTime

在这里插入图片描述

5.3实现自动填充

我们增加了create_time和update_time这两个字段后,执行UserMapper接口提供的插入方法insert,如果传入的user对象没有赋值createTime和updateTime,那么数据库中创建时间和修改时间的字段都是null,解决办法是给user对象赋值createTime和updateTime然后传给insert方法

刚刚说的方法是可行的,不过呢,现在我们用了mybatisplus,可以用mp的方式(自动填充)添加时间

自动填充:不需要set到对象里面值,使用mp方法实现数据添加

自动填充实现过程:

5.3.1在实体类里给自动填充的属性添加注解

@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

就比如@TableField(fill = FieldFill.INSERT)实际上就是做插入操作时实体类对象的createTime属性会被赋值,只是以前是通过setCreateTime方法赋值的,现在通过注解赋值

在这里插入图片描述

5.3.2创建类,实现接口

1.在mpdemo1010包下创建handler包,然后在handler包下创建类MyMetaObjectHandler(类名随便起)并使其实现接口MetaObjectHandler

在这里插入图片描述

注意一定要给这个类加注解@Component或@Service或@Repository以表示将该类交给Spring管理

5.3.3实现接口的方法

然后实现接口的方法就完成了对这个类的编写,类MyMetaObjectHandler的完整代码:

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    
    //使用mp实现添加操作时这个方法会执行
    @Override
    public void insertFill(MetaObject metaObject) {
    
    
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    //使用mp实现修改操作时这个方法会执行
    @Override
    public void updateFill(MetaObject metaObject) {
    
    
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

在这里插入图片描述

5.3.4测试

1.测试类中的测试方法addUser无需做任何修改,直接执行就可以了,执行完毕后去数据库可以看到自动填充成功

在这里插入图片描述

2.接下来执行修改的测试方法updateUser,该方法唯一做的修改是:刚刚添加的数据的id是1552545045196918785,所以我们给setId方法传参1552545045196918785L

在这里插入图片描述

3.执行updateUser测试方法后去数据库可以看到自动填充成功

在这里插入图片描述

4.看一下控制台的日志输出(不需要在意时间,因为这一条是我做到后面的时候回头补充的):

在这里插入图片描述

我们可以看到底层sql语句是UPDATE user SET age=?, update_time=? WHERE id=?也就是说mp底层更新数据时会自动更新update_time字段

6.乐观锁

6.1认识乐观锁

如果不考虑事务隔离性,会产生什么读问题:

  • 脏读
  • 不可重复读
  • 幻读/虚读

如果不考虑事务隔离性,会产生什么写问题:丢失更新问题

乐观锁就是为了解决丢失更新的问题

6.2认识"丢失更新"

在这里插入图片描述

这个员工的工资目前是500,lucy和mary都准备去修改这个员工的工资,lucy先将工资改为8000后提交事务,然后mary又将工资改为200后提交事务,那么lucy修改的8000就被mary修改的200覆盖了,lucy过来一看:我不是修改为8000了吗怎么会变成200了?这就是丢失更新

一句话概括丢失更新:多个人同时修改同一条记录,最后提交的把之前提交的数据覆盖

6.3解决"丢失更新"问题

解决"丢失更新"问题有两种办法:

  • 悲观锁:lucy操作数据时别人都不能操作这条数据,只有当lucy操作完这条数据别人才可以操作,这种是串行操作,因为这种操作方式lucy操作数据时别人不能操作这条数据只能等待,所以效率很低

  • 乐观锁:给数据库中添加一个version字段,执行更新数据时会比较此时手里拿的version和数据库中的version是否一致,如果一致,就更新成功并将version+1重新存储到数据库中;如果不一致就更新失败

    • 就比如12306抢票,一万个人抢一张票,只有先支付成功的那个人才可以抢到票

6.4实现乐观锁

6.4.1给user表添加字段

给user表添加字段作为乐观锁版本号

在这里插入图片描述

6.4.2给实体类添加属性

给实体类User添加属性version,并给该属性添加注解@Version

在这里插入图片描述

6.4.3配置乐观锁插件

在启动类Mpdemo1010Application中写如下代码

/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
    
    
    return new OptimisticLockerInterceptor();
}

在这里插入图片描述

这样做是没问题的,但我们更建议将这些配置放到一个专门的配置类中,接下来我们先删除掉刚刚在启动类中编写的代码,按照步骤来编写一个配置类:

1.在mpdemo1010包下创建包config,在该包下创建MpConfig类作为配置类,我们要给该类加注解@Configuration使其成为配置类

在这里插入图片描述

2.将乐观锁的插件写到配置类中

在这里插入图片描述

3.我们说过了,尽量将配置放到一个专门的配置类中,所以我们将启动类的注解@MapperScan("com.atguigu.mpdemo1010.mapper")删掉,将这个注解写到我们的配置类中

在这里插入图片描述

在这里插入图片描述

完整的配置类代码:

@Configuration
@MapperScan("com.atguigu.mpdemo1010.mapper")
public class MpConfig {
    
    
    /**
     * 乐观锁插件
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
    
    
        return new OptimisticLockerInterceptor();
    }
}

4.进行完善:我们插入数据时可以给version赋值一个初始值(插入数据时不给version赋值使其为null也是对的):

①给实体类的version属性添加注解@TableField(fill = FieldFill.INSERT)

在这里插入图片描述

②在类MyMetaObjectHandler的insertFill方法中添加代码this.setFieldValByName(“version”, 1, metaObject);

在这里插入图片描述

6.4.4测试

1.修改测试类中的addUser方法:

//添加操作
@Test
public void addUser() {
    
    
    User user = new User();
    user.setName("东方不败");
    user.setAge(60);
    user.setEmail("[email protected]");

    int result = userMapper.insert(user);
    System.out.println("insert:" + result); //影响的行数
    System.out.println(user); //id自动回填
}

2.执行该测试方法去数据库看该数据插入成功,并且version被赋值为1(我这里中文乱码是因为创建数据库时没有设置字符集为utf8,你们的肯定没有乱码【你们一定一定要设置,忘了设置的朋友我建议现在就回去删库重新建库建表,否则学到后面你还是要回来删库重新建库建表】)

在这里插入图片描述

3.以前修改数据时直接设置user对象的id值和age值就可以进行修改了,但是因为我们现在用了乐观锁,所以需要先查询到这条数据,然后再进行修改

在测试类中编写testOptimisticLocker测试方法:

//测试乐观锁
@Test
public void testOptimisticLocker() {
    
    
    //根据id查询数据
    User user = userMapper.selectById(1552581715216678913L);
    //进行修改
    user.setAge(200);
    userMapper.updateById(user);
}

selectById方法中的参数是我们刚刚插入的数据的id值,且因为实体类的id属性是long类型,所以需要在最后加一个L

在这里插入图片描述

4.执行测试方法testOptimisticLocker去数据库可以看到age和version都出现了变更,所以乐观锁测试成功!

在这里插入图片描述

看一下控制台日志输出:

在这里插入图片描述

因为这张图是我后来补上去的,所以时间、用户id等处和你想象的不一样,这无所谓的,我们主要看这两条sql语句(sql语句中的deleted字段是后期做别的需求时加的,这里也不用管,我用黄色填充的字符,就当没看到就可以了):

  • SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE id=?
  • UPDATE user SET name=?, age=?, email=?, create_time=?, update_time=?, version=?, deleted=? WHERE id=? AND version=?

7.mp简单查询

7.1根据id查询

User user = userMapper.selectById(1552581715216678913L);

7.2多个id批量查询

selectBatchIds(Collection<? extends Serializable> collection);

在测试类中编写测试方法testSelectDemo1

//多个id批量查询
@Test
public void testSelectDemo1() {
    
    
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
    System.out.println(users);
}

在这里插入图片描述

执行测试方法,可以在控制台看到查询结果:

在这里插入图片描述

并且我们也可以看到底层的sql语句是:SELECT id,name,age,email,create_time,update_time,version FROM user WHERE id IN ( ? , ? , ? )参数是1(Long), 2(Long), 3(Long)

7.3条件查询

条件查询是通过map封装查询条件,这种查询方式我们一般用不到,了解即可

在测试类中编写测试方法testSelectByMap:

//条件查询
@Test
public void testSelectByMap(){
    
    
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "Jone");
    map.put("age", 18);
    List<User> users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}

在这里插入图片描述

执行测试方法,可以在控制台看到查询结果:

在这里插入图片描述

注意:map中的key对应的是数据库中的列名,例如数据库user_id,实体类是userId,这时map的key需要填写user_id

7.4分页查询

首先需要在配置类MpConfig中配置分页插件

/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
    
    
    return new PaginationInterceptor();
}

在这里插入图片描述

然后在测试类中编写测试方法testPage:

//分页查询
@Test
public void testPage() {
    
    
    //1.创建page对象
    Page<User> page = new Page<>(1, 3);
    //2.调用mp分页查询的方法
    IPage<User> userIPage = userMapper.selectPage(page, null);


    System.out.println("当前页" + page.getCurrent());
    System.out.println("该页数据的list集合" + page.getRecords());
    System.out.println("每页显示记录数" + page.getSize());
    System.out.println("总记录数" + page.getTotal());
    System.out.println("总页数" + page.getPages());

    System.out.println("是否有下一页" + page.hasNext());
    System.out.println("是否有上一页" + page.hasPrevious());
}

在这里插入图片描述

  • 创建page对象时传入的两个参数分别是当前页和每页显示记录数
  • 调用selectPage方法时第二个参数是查询条件,这里我们暂时不设查询条件,所以第二个参数为null
  • 调用mp的selectPage方法时底层会帮我们做一个封装:底层会将分页的所有数据封装到Page类型的对象page中
  • 之所以说第12行的getSize方法是"每页显示记录数"而不是"该页显示记录数"是因为这个结果只和创建page对象时的第二个参数有关,第二个参数是3,那么getSize方法的返回值永远都是3,即使:表中一共有5条数据,创建page对象的代码是Page<User> page = new Page<>(2, 3);那么getSize方法返回值仍为3,并不会因为这一页只有两条数据而使getSize方法返回值为2(我实验过了)

执行测试方法,可以在控制台看到输出:

在这里插入图片描述

我们也可以看到,底层会先执行sql语句SELECT COUNT(1) FROM user查询出来表中一共有几条数据

8.删除

8.1删除方式分类

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为"被删除状态",之后在数据库中仍旧能看到此条数据记录

8.2物理删除

在测试类中编写测试方法testDeleteById

//物理删除
@Test
public void testDeleteById() {
    
    
    int result = userMapper.deleteById(1L);
    System.out.println(result);
}

在这里插入图片描述

执行测试方法,去数据库可以看到id为1的这条数据被成功删除了

看一下控制台日志中的sql语句:

在这里插入图片描述

8.3批量删除(也是物理删除)

在测试类中编写测试方法testDeleteBatchIds

//批量删除
@Test
public void testDeleteBatchIds() {
    
    
    int result = userMapper.deleteBatchIds(Arrays.asList(2,3));
    System.out.println(result);
}

在这里插入图片描述

执行测试方法,去数据库可以看到id为2和3的这两条数据都被成功删除了

看一下控制台日志中的sql语句:

在这里插入图片描述

8.4逻辑删除

8.4.1给user表添加字段

给user表添加字段deleted

在这里插入图片描述

想实现插入数据时就给某字段字段赋初值有两种方法(注意:这两种方法不能一起使用,只能使用一种!):

  • 使用自动填充(version、create_time、update_time字段使用的就是这种方法)
  • 如上图所示,将deleted字段的"默认"设为0,这样插入的每一条数据的deleted字段初始值都会被赋值为0

8.4.2给实体类添加属性

给实体类User添加属性deleted,并给该属性添加注解@TableLogic

在这里插入图片描述

8.4.3配置逻辑删除插件

在配置类MpConfig中配置逻辑删除插件

/**
* 逻辑删除插件
*/
@Bean
public ISqlInjector sqlInjector() {
    
    
    return new LogicSqlInjector();
}

在这里插入图片描述

8.4.4application.properties加入配置

在application.properties配置文件中添写如下代码

mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

注意:这两行代码是mp默认的配置,所以如果我们的字段本来就是:1表示已删除;0表示未删除,那么可以不需要再写这两行代码所以我在application.properties配置文件并没有写这两行代码(我注释掉了)

在这里插入图片描述

8.4.5测试

8.4.5.1插入数据

对测试类中的测试方法addUser进行修改,修改后的完整代码:

//添加操作
@Test
public void addUser() {
    
    
    User user = new User();
    user.setName("Tom");
    user.setAge(66);
    user.setEmail("[email protected]");

    int result = userMapper.insert(user);
    System.out.println("insert:" + result); //影响的行数
    System.out.println(user); //id自动回填
}

在这里插入图片描述

执行该测试方法,去数据库可以看到成功插入了这条数据

在这里插入图片描述

8.4.5.2逻辑删除数据

1.在测试类中编写测试方法testDeleteById2

//逻辑删除
@Test
public void testDeleteById2() {
    
    
    int result = userMapper.deleteById(1552666191380635650L);
    System.out.println(result);
}

在这里插入图片描述

我们发现这里逻辑删除的代码和"8.2物理删除"的代码是一样的,所以是物理删除还是逻辑删除只是取决于我们是否做了8.4.1~8.4.4

2.执行测试方法,查看数据库:

在这里插入图片描述

我们可以看到:

  • 这条数据的deleted子段由0(未删除状态)变为了1(已删除状态)
  • 这条数据的update_time字段并没有改变,这应该是mp底层自己做的优化,就是说如果逻辑删除数据,那么这条数据的update_time字段就不发生改变

3.看一下控制台的日志输出:

在这里插入图片描述

可以知道实际上这条sql语句是UPDATE user SET deleted=1 WHERE id=? AND deleted=0通过该sql语句我们知道了:

  • 逻辑删除实际上是将字段deleted的值由0改为1
  • 同时该sql语句也说明了为什么update_time字段没有改变,这是因为底层sql语句根本就没有对update_time字段进行更新
8.4.5.3查询数据

既然有了逻辑删除这个说法,那么以后查询数据时我们肯定要判定deleted是否为0,比如:SELECT * FROM USER WHERE DELETED=0,这一点mybatisplus也为我们考虑过了,我们只要给某个实体类属性deleted添加了@TableLogic注解,并且在配置类中配置了逻辑删除插件,以后再执行查询、更新语句mp会自动帮我们考虑到deleted是否为0

已知我们的数据表中的数据有6条,接着我们直接执行2.7中编写的测试方法contextLoads,看一下控制台的日志输出:

在这里插入图片描述

可以看到只查询到了5条数据,这是因为数据库中有一条数据的deleted值为1(已删除状态),而底层的sql语句是SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0所以deleted为1的数据会被过滤掉,这也说明了我们前面说过的:只要给某个实体类属性deleted添加了@TableLogic注解,并且在配置类中配置了逻辑删除插件,以后再执行查询、更新语句mp会自动帮我们考虑到deleted是否为0

8.4.6注意点

配置了逻辑删除插件后mp只能查询到没有被逻辑删除的数据,想要查询已经被逻辑删除的数据,只能用原始的方法:写一个xml映射文件,在xml文件中写sql语句进行查询

9.性能分析插件

9.1作用

性能分析拦截器,用于输出每条SQL语句及其执行时间

SQL性能执行分析,开发环境使用超过指定时间,停止运行.有助于发现问题

9.2配置插件

1.在配置类中添加性能分析插件的代码

/**
* SQL 执行性能分析插件
* 开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
*/
@Bean
@Profile({
    
    "dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
    
    
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    performanceInterceptor.setMaxTime(500);//ms,超过此处设置的ms则sql不执行
    performanceInterceptor.setFormat(true);
    return performanceInterceptor;
}

在这里插入图片描述

其中@Profile({"dev","test"})表示当前插件在什么环境起作用

在一个项目中有三种环境:

  • dev:开发环境(就是我们写代码的环境)
  • test:测试环境
  • prod:生产环境(项目已经部署好了,给用户去使用)

项目既然都已经部署,肯定以及测试好了,所以这个插件不需要在生产环境使用,而且,如果生产环境使用了这个插件,不仅是多次一举,甚至会影响我们项目的性能

2.将当前Spring Boot的环境设置为dev环境:

在application.properties中添加如下代码:

#环境设置:dev、test、prod
spring.profiles.active=dev

在这里插入图片描述

这行代码的作用是:设置当前Spring Boot环境为dev环境,这样的话就对应了上面的@Profile({"dev","test"}),我们的性能分析插件就会起作用。等到项目开发测试完成,再将spring.profiles.active=dev改为spring.profiles.active=prod,这样的话性能分析插件就不会再起作用了

9.3测试

执行测试类中添加操作的测试方法addUser,观察控制台中的输出:

在这里插入图片描述

Time:8 ms表示执行该SQL语句用了8ms,没有超过我们在配置类中设置的最大执行时间(500ms),所以SQL语句会正常执行,假如这条SQL语句用了501ms,那么就不会执行,并且会抛出异常The SQL execution time is too large, please optimize !

10.mybatisplus实现复杂的条件查询

10.1wapper介绍

在这里插入图片描述

  • Wrapper:条件构造抽象类,最顶端父类
  • AbstractWrapper:用于查询条件封装,生成sql的where条件
  • QueryWrapper:Entity对象封装操作类,不是用lambda语法
  • UpdateWrapper:Update条件封装,用于Entity对象更新操作
  • AbstractLambdaWrapper:Lambda语法使用Wrapper统一处理解析lambda获取 column
  • LambdaQueryWrapper:看名称也能明白就是用于Lambda语法使用的查询Wrapper
  • LambdaUpdateWrapper :Lambda更新封装Wrapper

理论上在mp中上述的类都可以用来构造条件但我们一般用QueryWrapper来构建条件,因为父类中有的方法子类也有,但子类特有的方法父类中就没有了,你要说你非要用Wrapper那也没问题,只是QueryWrapper的功能更强大

10.2使用QueryWrapper

使用QueryWrapper的步骤是:

  • 先创建QueryWrapper对象
  • 然后通过QueryWrapper设置条件
  • 最后调用方法实现各种条件查询

10.2.1设置查询条件的方法

通过QueryWrapper设置条件我这里只列举常用的方法:

  • ge、gt、le、lt:分别是大于等于、大于、小于等于、小于【这4个方法第一个参数是数据库中的字段,第二个参数是要设置的值】
  • eq、ne:分别是等于、不等于【这2个方法第一个参数是数据库中的字段,第二个参数是要设置的值】
  • between、notBetween:分别是在哪个范围内(包含左右边界),比如[2, 5]、不再哪个范围内【这2个方法第一个参数是数据库中的字段,第二个参数是左闭区间,第三个参数是右闭区间】
  • like:模糊查询【第一个参数是数据库中的字段,第二个参数是要设置的值】
  • orderByAsc、orderByDesc:分别是做升序、降序【只有一个参数,是数据库中的字段】
  • last:直接拼接到sql的最后(只能调用一次,多次调用以最后一次为准,有sql注入的风险,请谨慎使用)【只有一个参数,要往sql语句最后拼接的字符串】
  • select:指定要查询的列

10.2.2重新建user表

按照我的笔记一步一步执行代码,现在数据表中的数据应该是这样的:

在这里插入图片描述

为了和老师的一样,本来我是准备删除多余数据、修改数据,然后呢,也就是在这里我意识到自己中文乱码的严重性了,索性直接删库重新建库建表

嗯…我只是有强迫症想要自己的数据和老师的一样,也想要自己数据和老师一样的朋友按下面的步骤来(没有这个想法的可以直接去看"10.2.3ge、gt、le、lt"):

①我是删库重新建库建表,你们如果没有中文乱码,只需要将user表中的数据全部删掉即可

②执行五次添加操作的测试方法addUser(注意每次执行该测试方法插入的数据的名字、年龄字段最好和下图一样)

在这里插入图片描述

③将"岳不群"的deleted字段修改为1;将"东方不败"的version字段修改为2:

  • 我们看过逻辑删除的底层sql语句,知道了逻辑删除的底层机制实际上就是更新这条数据的deleted字段,所以我们可以放心大胆地直接在数据库中将"岳不群"的deleted字段修改为1
  • 我们前面说过乐观锁的底层机制:执行更新数据时会比较此时手里拿的version和数据库中的version是否一致,如果一致,就更新成功并将version+1重新存储到数据库中;如果不一致就更新失败,结合我们之前看过的乐观锁的底层sql语句,可以也可以放心大胆地直接在数据库中将"东方不败"的version字段修改为2
  • 不要认为上面的分析没啥用,如果你不知道逻辑删除和乐观锁的底层原理,你敢直接去数据库中修改某条数据的字段吗?不怕后面一堆bug等着你?
  • 修改后如下图所示(因为这两个字段都是我们是直接在数据库中修改的,并不是经过java程序修改的,所以"东方不败"和"岳不群"的update_time字段都不会发生改变)

在这里插入图片描述

10.2.3ge、gt、le、lt

在测试类中编写测试方法testSelectQuery1

//mp实现复杂查询操作
@Test
public void testSelectQuery1() {
    
    
    //1.创建对象
    QueryWrapper<User> wrapper = new QueryWrapper<>();

    //2.通过QueryWrapper设置条件
    //ge、gt、le、lt
    //查询age>=30的数据
    wrapper.ge("age", 30);

    //3.调用方法实现条件查询
    List<User> users = userMapper.selectList(wrapper);
    System.out.println("age>=30" + users);
}

在这里插入图片描述

执行测试方法,观察控制台日志输出的底层sql语句:

SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND age >= ?

在这里插入图片描述

10.2.4eq、ne

在测试类中编写测试方法testSelectQuery2

//mp实现复杂查询操作
@Test
public void testSelectQuery2() {
    
    
    //1.创建对象
    QueryWrapper<User> wrapper = new QueryWrapper<>();

    //2.通过QueryWrapper设置条件
    //eq、ne
    wrapper.eq("name", "lilei");

    //3.调用方法实现条件查询
    List<User> users = userMapper.selectList(wrapper);
    System.out.println(users);
}

在这里插入图片描述

执行测试方法,观察控制台日志输出的底层sql语句:

SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND name = ?

在这里插入图片描述

将该测试方法的wrapper.eq(“name”, “lilei”);改为wrapper.ne(“name”, “lilei”);执行修改后的测试方法,我们来看一下条件为name不等于lilei时底层sql语句:

在这里插入图片描述

SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND name <> ?

sql语句中的<>和我们以前使用的!=是等价的

10.2.5between、notBetween

在测试类中编写测试方法testSelectQuery3

//mp实现复杂查询操作
@Test
public void testSelectQuery3() {
    
    
    //1.创建对象
    QueryWrapper<User> wrapper = new QueryWrapper<>();

    //2.通过QueryWrapper设置条件
    //between、notBetween
    //查询年龄[20,30]
    wrapper.between("age", 20, 30);

    //3.调用方法实现条件查询
    List<User> users = userMapper.selectList(wrapper);
    System.out.println(users);
}

在这里插入图片描述

先像老师那样直接在数据库中将任意一个"岳不群1"的age字段改为25,然后执行测试方法,观察控制台日志输出的底层sql语句:

SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND age BETWEEN ? AND ?

在这里插入图片描述

10.2.6like

在测试类中编写测试方法testSelectQuery4

//mp实现复杂查询操作
@Test
public void testSelectQuery4() {
    
    
    //1.创建对象
    QueryWrapper<User> wrapper = new QueryWrapper<>();

    //2.通过QueryWrapper设置条件
    //like
    wrapper.like("name", "岳");

    //3.调用方法实现条件查询
    List<User> users = userMapper.selectList(wrapper);
    System.out.println(users);
}

在这里插入图片描述

执行测试方法,观察控制台日志输出的底层sql语句:

SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND name LIKE ?

在这里插入图片描述

10.2.7orderByAsc、orderByDesc

在测试类中编写测试方法testSelectQuery5

//mp实现复杂查询操作
@Test
public void testSelectQuery5() {
    
    
    //1.创建对象
    QueryWrapper<User> wrapper = new QueryWrapper<>();

    //2.通过QueryWrapper设置条件
    //orderByAsc、orderByDesc
    //按照id降序排序
    wrapper.orderByDesc("id");

    //3.调用方法实现条件查询
    List<User> users = userMapper.selectList(wrapper);
    System.out.println(users);
}

在这里插入图片描述

执行测试方法,观察控制台日志输出的底层sql语句:

SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 ORDER BY id DESC

在这里插入图片描述

10.2.8last

在测试类中编写测试方法testSelectQuery6

//mp实现复杂查询操作
@Test
public void testSelectQuery6() {
    
    
    //1.创建对象
    QueryWrapper<User> wrapper = new QueryWrapper<>();

    //2.通过QueryWrapper设置条件
    //last
    wrapper.last("limit 1");

    //3.调用方法实现条件查询
    List<User> users = userMapper.selectList(wrapper);
    System.out.println(users);
}

在这里插入图片描述

执行测试方法,观察控制台日志输出的底层sql语句:

SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 limit 1

在这里插入图片描述

10.2.9指定要查询的列(select)

在测试类中编写测试方法testSelectQuery7

//mp实现复杂查询操作
@Test
public void testSelectQuery7() {
    
    
    //1.创建对象
    QueryWrapper<User> wrapper = new QueryWrapper<>();

    //2.通过QueryWrapper设置条件
    //select
    wrapper.select("id", "name");

    //3.调用方法实现条件查询
    List<User> users = userMapper.selectList(wrapper);
    System.out.println(users);
}

在这里插入图片描述

执行测试方法,观察控制台日志输出的底层sql语句:

SELECT id,name FROM user WHERE deleted=0

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/maxiangyu_/article/details/127018719