Mybatis-Plus学透?一篇足够(持续更新中)

01、Mybatis-Plus入门

一、简介

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。如果你想对自己的项目进行技术升级,不妨尝试将mybatis换成Mybatis-Plus,你将从此在无比强大的mp大法中沉迷而无法自拔日渐精神!mp 润物无声,只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。并且mp 遵循效率至上,你只需简单配置,即可快速进行 CRUD 操作,从而节省大量时间。同时mp拥有 丰富的功能,热加载、代码生成、分页、性能分析等功能一应俱全。让我们从本篇出发,开启真正的mp开发之旅吧!
在这里插入图片描述

二、创建并初始化数据库

为了更加清晰直观地进行讲解,这里我们使用Navicat,创建了mp_practice数据库,同时在数据库中创建了User表,并将初始数据添加

1、创建数据库

在这里插入图片描述

2、创建User表

(1)对应的数据库 Schema 脚本如下:
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)
);
(2)对应的数据库 Data 脚本如下:
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

最后在Navicat中结果呈:
在这里插入图片描述

三、创建项目

1、初始化工程

使用 Spring Initializr 快速初始化一个 Spring Boot 工程
在这里插入图片描述
settings中将group,artifact,type,Java Version,name换成自己需要的
在这里插入图片描述
在这里插入图片描述

2、引入依赖

在pom.xml文件中引入依赖

<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.3.1</version>
    </dependency>

    <!--mysql运行时依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!--lombok用来简化实体类-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

注意:引入 MyBatis-Plus 之后请不要再次引入 MyBatis,以避免因版本差异导致的问题。

3、在idea中安装lombok插件

不同版本安装lombok插件方式不同,可以根据自己idea的版本在csdn中搜索自己对应版本的lombok插件安装方法(博主自己的2019版本当初在安装的时候出现过应用商店没有对应版本的lombok的问题,大体解决思路是去官网下载lombok包然后导入本地,可参照此博客

四、代码

1、配置

springboot默认建立的就是application.properties,直接在 application.properties 配置文件中添加 MySQL 数据库的相关配置:

(1)spring boot 2.0(内置jdbc5驱动)
#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mp_practice?characterEncoding=utf-8&useSSL=false
spring.datasource.username=你的username
spring.datasource.password=你的password
(2)spring boot 2.1及以上(内置jdbc8驱动)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mp_practice?serverTimezone=GMT%2B8
spring.datasource.username=你的username
spring.datasource.password=你的password	

注意:
1、这里的 url 使用了 ?serverTimezone=GMT%2B8 后缀,因为8.0版本的jdbc驱动需要添加这个后缀,否则运行测试用例报告如下错误:java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more
2、这里的 driver-class-name 使用了 com.mysql.cj.jdbc.Driver ,在 jdbc 8 中 建议使用这个驱动,否则运行测试用例的时候会有 WARN 信息

2、主类

在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹

@SpringBootApplication
@MapperScan("com.xuhao.mp.mapper")
public class MybatisPlusApplication {
	......
}

3、实体

创建报entity编写实体类User.java(此处使用lombok的@Data)

@Data//自动生成对应的实体类如get、set、equals、hashCode、toString等方法
public class User {
    private Long id;//Long切勿写成long
    private String name;
    private Integer age;
    private String email;
}

在这里插入图片描述

4、接口

创建包mapper编写Mapper接口:UserMapper.java

 package com.xuhao.mp.mapper;/*
  @author: 徐昊
  @since: 2023/3/5
 */

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xuhao.mp.entity.User;

public interface UserMapper extends BaseMapper<User> {
}

5、测试

在这里插入图片描述

此时红色波浪线并不代表出现错误,在UserMapper中使用@Repository注解即可

在这里插入图片描述

此时红色波浪线消失(强迫症狂喜)

在这里插入图片描述

编写测试方法:

    @Test
    void testSelectList(){
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);

    }

控制台打印出如下内容,查询成功
在这里插入图片描述

02、CURD接口-主键策略

一、插入操作

    @Test
    public void testInsert(){
        User user = new User();
        user.setName("xubanxian");
        user.setEmail("1008611@qq.com");
        user.setAge(8848);

        int result = userMapper.insert(user);//返回值为影响的行数
        System.out.println("影响的行数为:"+result);
        System.out.println("user id:"+user.getId());
    }

这里我们并未对id进行赋值但是却由于mp的主键策略,自动生成了一个全局唯一id在这里插入图片描述
那么,生成了这么长的一个id到底有什么意义呢?

二、数据库分库分表策略

背景:

随着业务规模的不断扩大,需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。数据库的扩展方式主要包括:业务分库、主从复制,数据库分表

1、业务分库

业务分库指的是按照业务模块将数据分散到不同的数据库服务器。例如,一个简单的电商网站,包括用户、商品、订单三个业务模块,我们可以将用户数据、商品数据、订单数据分开放到三台不同的数据库服务器上,而不是将所有数据都放在一台数据库服务器上。这样的就变成了3个数据库同时承担压力,系统的吞吐量自然就提高了。
在这里插入图片描述
虽然业务分库能够分散存储和访问压力,但同时也带来了新的问题:

(1)join 操作问题
业务分库后,原本在同一个数据库中的表分散到不同数据库中,导致无法使用 SQL 的 join 查询。
(2)事务问题
原本在同一个数据库中不同的表可以在同一个事务中修改,业务分库后,表分散到不同的数据库中,无法通过事务统一修改。
(3)成本问题
业务分库同时也带来了成本的代价,本来 1 台服务器搞定的事情,现在要 3 台,如果考虑备份,那就是 2 台变成了 6 台

2、主从复制和读写分离

读写分离的基本原理是将数据库读写操作分散到不同的节点上。读写分离的基本实现是:

数据库服务器搭建主从集群,一主一从、一主多从都可以。 数据库主机负责读写操作,从机只负责读操作。
数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。 业务服务器将写操作发给数据库主机,将读操作发给数据库从机。
在这里插入图片描述

注意:
这里用的是“主从集群”,而不是“主备集群”。
“从机”的“从”可以理解为“仆从”,仆从是要帮主人干活的,“从机”是需要提供读数据的功能的;
而“备机”一般被认为仅仅提供备份功能,不提供访问功能。
所以使用“主从”还是“主备”,是要看场景的,这两个词并不是完全等同。

3、数据库分表(重点)

将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。
在这里插入图片描述

(1)分表方式
①、垂直分表

垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。
例如,前面示意图中的 nickname 和 description字段,假设我们是一个婚恋网站,用户在筛选其他用户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和description 两个字段主要用于展示,一般不会在业务查询中用到。description本身又比较长,因此我们可以将这两个字段独立到另外一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升。

②、水平分表

水平分表适合表行数特别大的表,有的公司要求单表行数超过 5000 万就必须进行分表,这个数字可以作为参考,但并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过 1000 万就要分表了;而对于一些简单的表,即使存储数据超过 1 亿行,也可以不分表。
但不管怎样,当看到表的数据量达到千万级别时,作为架构师就要警觉起来,因为这很可能是架构的性能瓶颈或者隐患。

(2)策略

水平分表相比垂直分表,会引入更多的复杂性,例如数据id,一般有以下几种策略:

①、主键自增

以最常见的用户 ID 为例:
按照 1000000 的范围大小进行分段,1 ~ 999999 放到表 1中,1000000 ~ 1999999 放到表2中,以此类推。

复杂点: 分段大小的选取。分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会导致单表依然存在性能问题,一般建议分段大小在 100 万2000 万之间,具体需要根据业务选取合适的分段大小。
优点: 可以随着数据的增加平滑地扩充新的表。例如,现在的用户是 100 万,如果增加到 1000 万,只需要增加新的表就可以了,原有的数据不需要动。
缺点: 分布不均匀,假如按照 1000 万来进行分表,有可能某个分段实际存储的数据量只有 1000 条,而另外一个分段实际存储的数据量有 900 万条。

②、Hash策略

同样以用户 ID 为例:
假如我们一开始就规划了 10 个数据库表,路由算法可以简单地用 user_id % 10 的值来表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中,ID 为 10086 的用户放到编号为 6 的字表中。

复杂点: 初始表数量的选取。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。
优点: 表分布比较均匀。
缺点: 扩充新的表很麻烦,所有数据都要重分布。

③、雪花算法

雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性
核心思想:
a、长度共64bit(一个long型)。
b、首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
c、41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。
d、10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
e、12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。

在这里插入图片描述

三、MP的主键策略

进入原码中我们发现mp的主键策略中有如下多个策略
在这里插入图片描述

1、ASSIGN_ID

MyBatis-Plus默认的主键策略是:ASSIGN_ID (使用了雪花算法)

@TableId(type = IdType.ASSIGN_ID)
private String id;

2、AUTO自增策略

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

@TableId(type = IdType.AUTO)
private Long id;

3、全局主键配置

#全局设置主键生成策略
mybatis-plus.global-config.db-config.id-type=auto

03、CURD接口-自动填充和乐观锁

一、更新

    @Test
    public void testUpdateById(){
        User user = new User();
        user.setId(1L);
        user.setAge(28);

        int result = userMapper.updateById(user);
        System.out.println("影响行数:"+result);
    }

注意:update时生成的sql自动是动态sql:UPDATE user SET age=? WHERE id=?

二、自动填充

应用场景:

在未使用自动填充之前,相应的insert和update方法应该如下:

    @Test
    public void testInsert(){
        User user = new User();
        user.setName("xubanxian");
        user.setEmail("1008611@qq.com");
        user.setAge(8867);
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());

        int result = userMapper.insert(user);//返回值为影响的行数
        System.out.println("影响的行数为:"+result);
        System.out.println("user id:"+user.getId());
    }

    @Test
    public void testUpdateById(){
        User user = new User();
        user.setId(1L);
        user.setAge(28);
        user.setUpdateTime(new Date());

        int result = userMapper.updateById(user);
        System.out.println("影响行数:"+result);
    }
}

每一次更新我们都要对相应的时间进行代码编写,这实在是太麻烦了。所幸,mp给我们提供了非常方便的代码填充插件,mp真的太懂程序员了!

需求:

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。
我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作

1、修改数据库

在User表中添加datetime类型的新的字段create_time、update_time

2、实体类修改

@Data
public class User {

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

    //mp会自动将驼峰转化成数据库对应的下划线的形式
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

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

3、实现元对象处理器接口

package com.atguigu.mybatisplus.handler;

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

三、乐观锁

1、场景

  一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。
  此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
  现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1多万。

2、悲观锁与乐观锁

  上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。

  如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。

3、模拟修改冲突

(1)数据库中增加商品表
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)
);
(2)添加数据
INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
(3)实体类
package com.atguigu.mybatis_plus.entity;
@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    private Integer version;
}
(4)Mapper
package com.atguigu.mybatis_plus.mapper;
@Repository
public interface ProductMapper extends BaseMapper<Product> {
    
}
(5)模拟测试
@Autowired
private ProductMapper productMapper;

    /**
     * 并发编程一般叫concurrent
     */
@Test
public void testConcurrentUpdate() {

    //1、小李
    Product p1 = productMapper.selectById(1L);
    System.out.println("小李取出的价格:" + p1.getPrice());

    //2、小王
    Product p2 = productMapper.selectById(1L);
    System.out.println("小王取出的价格:" + p2.getPrice());

    //3、小李将价格加了50元,存入了数据库
    p1.setPrice(p1.getPrice() + 50);
    productMapper.updateById(p1);

    //4、小王将商品减了30元,存入了数据库
    p2.setPrice(p2.getPrice() - 30);
    int result = productMapper.updateById(p2);
    if(result == 0){//更新失败,重试
        //重新获取数据
        p2 = productMapper.selectById(1L);
        //更新
        p2.setPrice(p2.getPrice() - 30);
        productMapper.updateById(p2);
    }

    //最后的结果
    Product p3 = productMapper.selectById(1L);
    System.out.println("最后的结果:" + p3.getPrice());
}

4、解决方案

  • 数据库中添加version字段

  • 取出记录时,获取当前version SELECT id,name,version FROM product WHERE id=1

  • 更新时,version + 1,如果where语句中的version版本不对,则更新失败

  • UPDATE product SET price=price+50, version=version+ 1 WHERE id=1 AND version=1

接下来介绍如何在Mybatis-Plus项目中,使用乐观锁:

5、乐观锁实现流程

(1)修改实体类

添加 @Version 注解

@Data
public class Product {
    private Long id;
    private String name;
    private int price;

    @Version
    private int version;
}
(2)创建配置文件

创建包config,创建文件MybatisPlusConfig.java,我们从此之后将对mp的配置全部集中在这个类中实现,此时可以删除主类中的 @MapperScan 扫描注解

@EnableTransactionManagement//事务处理
@Configuration
@MapperScan("com.xuhao.mp.mapper")
public class MyBatisPlusConfig {


}

(3)注册乐观锁插件

在 MybatisPlusConfig 中注册 Bean

    /**
     * 乐观锁插件
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
(4)测试
    /**
     * 并发编程一般叫concurrent
     */
    @Test
    public void testConcurrentUpdate(){
//        1、小李获取数据
        Product product1 = productMapper.selectById(1L);
        System.out.println("小李去除的价格"+product1.getPrice());

//        2、小王获取数据
        Product product2 = productMapper.selectById(1L);
        System.out.println("小王获取的数据"+product2.getPrice());

//        3、小李加了50存入数据库
        product1.setPrice(product1.getPrice()+50);
        productMapper.updateById(product1);

//        4、小王减了30存入数据库
        product2.setPrice(product2.getPrice()-30);
        int result = productMapper.updateById(product2);//若更新失败,result为0
        if (result == 0){
            System.out.println("小王更新失败");
            //发起重试
            product2 = productMapper.selectById(1L);
            product2.setPrice(product2.getPrice() - 30);
            productMapper.updateById(product2);
        }

//        5、输出结果
        Product product3 = productMapper.selectById(1L);
        System.out.println("最后的结果:"+product3.getPrice());


    }

04、查询和分页

一、查询

1、通过多个id批量查询

    @Test
    public void testSelectBatchIds(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
        users.forEach(System.out::println);
    }

2、简单的条件查询

通过map封装查询条件

    /**
     * 最基本的条件查询
     */
    @Test
    public void testSelectByMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","xubanxian");
        map.put("age",18);

        List<User> users = userMapper.selectByMap(map);
        for (User user : users) {
            System.out.println(user);
        }
    }

对应的sql语句:
SELECT id,name,age,email,create_time,update_time FROM user WHERE name = ? AND age = ?

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

二、分页

1、分页插件

MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能

(1)添加分页插件

配置类中添加@Bean配置

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

测试: 最终通过 page 对象获取相关数据

    @Test
    public void testSelectPage(){
        //第一页,每页五条记录
        Page<User> page = new Page<>(1, 5);

        //queryWrapper,条件构造器
        Page<User> pageParam = userMapper.selectPage(page, null);

        List<User> records = pageParam.getRecords();
        records.forEach(System.out::println);
        System.out.println(pageParam.getCurrent());//当前页码
        System.out.println(pageParam.getPages());//总页数
        System.out.println(pageParam.getSize());//每页记录数
        System.out.println(pageParam.getTotal());//总记录数
        System.out.println(pageParam.hasNext());//是否有下一页
        System.out.println(pageParam.hasPrevious());//是否上一页


    }

对应的sql语句:SELECT id,name,age,email,create_time,update_time FROM user LIMIT 0,5

2、返回指定的列

当指定了特定的查询列时,希望分页结果列表只返回被查询的列,而不是很多null值
测试selectMapsPage分页:结果集是Map

@Test
public void testSelectMapsPage() {

    //返回很多null列
    //Page<User> page = new Page<>(1, 5);
    //QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //queryWrapper.select("name", "age");
    //Page<User> pageParam = userMapper.selectPage(page, queryWrapper);
    //
    //pageParam.getRecords().forEach(System.out::println);


         //条件构造器
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.select("id","name");//只查id和name,
        //SELECT id,name FROM user LIMIT ?,?


        //第一页,每页五条记录
        Page<Map<String,Object>> page = new Page<>(1, 5);

        //queryWrapper,条件构造器
        Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, userQueryWrapper);

        List<Map<String, Object>> records = page.getRecords();
        records.forEach(System.out::println);
        System.out.println(pageParam.getCurrent());
        System.out.println(pageParam.getPages());
        System.out.println(pageParam.getSize());
        System.out.println(pageParam.getTotal());
        System.out.println(pageParam.hasNext());
        System.out.println(pageParam.hasPrevious());
}

05、CURD接口-删除和逻辑删除

一、删除

1、根据id删除记录

@Test
public void testDeleteById(){

    int result = userMapper.deleteById(5L);
    System.out.println(result);
}

2、批量删除

    @Test
    public void testDeleteBatchById(){
        int result = userMapper.deleteBatchIds(Arrays.asList(7, 8, 9, 10));
        System.out.println("删除了:"+result+"行");
    }

3、简单条件删除

    @Test
    public void testDeleteByMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","xubanxian");
        map.put("age",18);

        int result = userMapper.deleteByMap(map);

        System.out.println("删除了:"+result+"行");
    }

二、逻辑删除

1、物理删除和逻辑删除

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

逻辑删除的使用场景:

  • 可以进行数据恢复
  • 有关联数据,不便删除

2、逻辑删除实现流程

(1)数据库修改

添加 deleted字段:ALTER TABLE user ADD COLUMN deleted boolean DEFAULT false

(2)实体类修改

添加deleted 字段,并加上 @TableLogic 注解

@Data
public class User {

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

    //mp会自动将驼峰转化成数据库对应的下划线的形式
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

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

			
    @TableLogic	//逻辑删除标记
    private Integer deleted;
}
(3)配置(可选)

application.properties 加入以下配置,此为默认值,如果你的默认值和mp默认的一样,该配置可无

#被删除时为1
mybatis-plus.global-config.db-config.logic-delete-value=1
#未被删除时为0
mybatis-plus.global-config.db-config.logic-not-delete-value=0
(4)测试

逻辑删除的本质是对delete进行修改,语句为:
UPDATE user SET deleted=1 WHERE id=? AND deleted=0

  • 测试后发现,数据并没有被删除,deleted字段的值由0变成了1
  • 测试后分析打印的sql语句,是一条update
  • 注意:被删除前,数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作(因为sql内同时对deleted的值进行判断,若为1则直接不执行update,这样可以节省运行时间)
//logic为逻辑的意思
@Test
public void testLogicDelete() {

    int result = userMapper.deleteById(1L);
    System.out.println(result);
}

Navicat内观察结果:

在这里插入图片描述

(5)测试逻辑删除后的查询

MyBatis Plus中查询操作也会自动添加逻辑删除字段的判断:
SELECT id,name,age,email,create_time,update_time,deleted FROM user WHERE deleted=0

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

控制台运行结果如下:

在这里插入图片描述

3、有趣的发现(可能是bug)

偶然发现,mp在进行逻辑删除的时候不会触发mp的自动填充(目前还不知道是因为啥),问题如下:

在最刚开始时候在这里插入图片描述id为11的xubanxian的update_time是2023-03-07 21:37:19,此时这个xubanxian的deleted为0,处于非逻辑删除状态。
在这里插入图片描述
在上图这个时间的时候博主运行了逻辑删除测试方法,

在这里插入图片描述
测试方法运行成功后,deleted状态变为1,此时的11号xubanxian为逻辑删除状态,但是原本应该被自动填充的update_time为实现自动填充,着实怪哉!
在这里插入图片描述
目前查到的一个原因好像是低版本的mp会出现这种bug,换成新版本的好像会解决这个bug。
真正的原因目前还没查明白QAQ(不知道屏幕前的你遇没遇到过这种问题,如果有合理的解决方案请发到评论区,万分感谢!)

06、条件构造器和常用接口

一、wapper介绍

在这里插入图片描述

Wrapper : 条件构造抽象类,最顶端父类
 AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
   QueryWrapper : 查询条件封装(用的最多)
   UpdateWrapper : Update 条件封装
 AbstractLambdaWrapper : 使用Lambda 语法
  LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper(复杂的会用到)
  LambdaUpdateWrapper : Lambda 更新封装Wrapper

为了更加清晰地演示,这里博主创建了一个新的测试类

@SpringBootTest
public class QueryWrapperTests {
    
    @Autowired
    private UserMapper userMapper;
}

二、测试用例

1、ge、gt、le、lt、isNull、isNotNull

    @Test
    public void testDelete(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//        链式编程
        queryWrapper
                .eq("age",8858)
                .isNotNull("email")
                .isNotNull("name");
//        queryWrapper.ge("age",18);//大于等于18
//        queryWrapper.gt("age",18);//大于18
//        queryWrapper.le("age",18);//小于等于18
//        queryWrapper.lt("age",18);//下于18

        int delete = userMapper.delete(queryWrapper);
        System.out.println("删除了"+delete+"行");
    }

2、eq、ne

    @Test
    public void testSelectOne(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name","Tom");

        User user = userMapper.selectOne(queryWrapper);//只能返回一条记录,多余一条则抛出异常
        System.out.println(user);
    }

sql:SELECT id,name,age,email,create_time,update_time,deleted FROM user WHERE deleted=0 AND (name = ?)

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

注意:selectOne()返回的是一条实体记录,当出现多条时会报错
   selectOne()可以用于账号的是否唯一的检查中

3、between、notBetween

包含大小边界

    @Test
    public void testSelectCount(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.between("age",10,30);

        List<User> users = userMapper.selectList(queryWrapper);
        Integer count = userMapper.selectCount(queryWrapper);
        System.out.println("年纪在10-30的有"+count+"人");
        System.out.println("分别是:");
        users.forEach(System.out::println);

    }

sql:SELECT COUNT( 1 ) FROM user WHERE deleted=0 AND (age BETWEEN ? AND ?)

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

4、like、notLike、likeLeft、likeRight

selectMaps()返回Map集合列表,通常配合select()使用

    @Test
    public void testSelectMaps(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .select("name","age")
                .like("name","e")
                .likeRight("email","t");

        List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
        maps.forEach(System.out::println);
    }

sql: SELECT name,age FROM user WHERE deleted=0 AND (name LIKE ? AND email LIKE ?)
Parameters: %e%(String), t%(String)

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

5、in、notIn、inSql、notinSql、exists、notExists

待更新

6、or、and

待更新

7、lambda表达式

待更新

8、orderBy、orderByDesc、orderByAsc

待更新

9、set、setSql

待更新

三、 查询方式汇总

在这里插入图片描述

07、mp实战讲解分析

更新未完待续……
未来博主会继续将项目中遇到的一些比较好的mp实战代码进行讲解分析,并更新在这一篇博客中,诸君共勉!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值