MybatisPlus

1 【入门篇】

1.1 初识MybatisPlus

1.1.1 Mybatis框架回顾

MybatisPlus,从名称上来看,我们就发现,他和Mybatis长得很像,其实MybatisPlus就是Mybatis的孪生兄弟,在学习MybatisPlus之前,我们先来回顾一下Mybatis框架的搭建流程。
什么是Mybatis框架呢,他是一个持久层框架,目的是简化持久层的开发。在这里我们就使用springboot整合Mybatis,实现Mybatis框架的搭建。

【1】工程结构创建
我们首先创建一个空的工程
MybatisPlus_第1张图片
工程名称是mp
MybatisPlus_第2张图片
创建springboot模块
MybatisPlus_第3张图片
MybatisPlus_第4张图片
这里我们选择springboot2.7.8的版本,并不勾选依赖,随后通过pom.xml手动添加
MybatisPlus_第5张图片
创建后的结构如下

【2】创建数据库,表结构,导入表数据
创建数据库mybatisplus
MybatisPlus_第6张图片

建表语句

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

插入数据

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】 引入相关依赖


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.7.8version>
        <relativePath/> 
    parent>

    <groupId>com.liminggroupId>
    <artifactId>day01artifactId>
    <version>0.0.1-SNAPSHOTversion>

    <properties>
        <java.version>1.8java.version>
    properties>

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.30version>
        dependency>
        
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.3.0version>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>

【4】 创建domain包,编写实体类,实体类的数据类型和表字段类型一致
MybatisPlus_第7张图片

Lombok可以极大的省略类的代码量,使代码更加的简洁。
Lombok相关常用注解
@Data 注解在类上,提供类的get、set、equals、hashCode、toString等方法
@AllArgsConstructor 注解在类上,提供满参构造
@NoArgsConstructor 注解在类上,提供空参构造

package com.powernode.domain;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructorpublic class User {
    //主键
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

【5】 创建mapper包,编写Mapper接口
MybatisPlus_第8张图片
@Mapper作用在接口上,扫描到该注解后,会根据接口创建该接口的实现类对象

import com.powernode.domain.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapperpublic interface UserMapper {
    public List<User> selectList();
}

【6】 编写映射文件
MybatisPlus_第9张图片

| _


【7】 创建service包,编写Service
MybatisPlus_第10张图片

package com.powernode.service;
import com.powernode.domain.User;
import java.util.List;
public interface UserService {
    List<User>  selectAll();
}

【8】 编写ServiceImpl
MybatisPlus_第11张图片

package com.powernode.service.impl;
import com.powernode.domain.User;
import com.powernode.mapper.UserMapper;
import com.powernode.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> selectAll() {
        return userMapper.selectAll();
    }
}

【9】 编写Controller
MybatisPlus_第12张图片

package com.powernode.controller;

import com.powernode.domain.User;
import com.powernode.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/selectAll")
    public List<User> selectAll(){
        List<User> users = userService.selectAll();
        return users;
    }
}

【10】 将配置文件改为yml格式,编写配置文件

spring:
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false

【11】 测试结果
请求controller接口,发现响应了数据,至此Mybatis环境开发完毕

1.1.2 Mybatis框架的开发效率问题分析

我们来思考一下,Mybatis框架的开发效率怎么样?
开发效率也就是我们使用这款框架开发的速度快不快,是否简单好用易上手。从这个角度思考,每当我们需要编写一个SQL需求的时候,我们需要做几步
【1】Mapper接口提供一个抽象方法
【2】Mapper接口对应的映射配置文件提供对应的标签和SQL语句
【3】在Service中依赖Mapper实例对象
【4】调用Mapper实例中的方法
【5】在Controller中依赖Service实例对象
【6】调用Service实例中的方法
通过上面的发现,对于一个SQL需求,无论是单表还是多表,我们是需要完成如上几步,才能实现SQL需求的开发

但是在开发中,有一些操作是通用逻辑,这些通用逻辑是可以被简化的,例如:
【1】对于dao,是否可以由框架帮我们提供好单表的Mapper抽象方法,和对应的SQL实现,不需要程序员去实现这些
【2】对于service,使用可以有框架直接帮我们提供好一些service的抽象方法,和对应的实现,不需要程序员去实现这些
【3】一些其他的企业开发中所需要的操作
分析到这里我们发现,其实核心框架并没有发生变化,依然还是Mybatis,只不过我们希望对于Mybatis进行一些封装和进化,让它更加的好用,更加的易用。

MybatisPlus它来了,他是Mybatis的一款增强工具。

1.1.3 MybatisPlus的介绍

[MyBatis-Plus (opens new window)](https://github.com/baomidou/mybatis-plus" \t “https://baomidou.com/pages/24112f/_blank)(简称 MP)是一个 [MyBatis (opens new window)](https://www.mybatis.org/mybatis-3/” \t "https://baomidou.com/pages/24112f/_blank)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
愿景
我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
MybatisPlus_第13张图片

1.1.4 MybatisPlus的特性讲解

特性

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

支持数据库
任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。
MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库

1.1.5 MybatisPlus的架构模型

MybatisPlus_第14张图片

1.1.6 小结

本章节主要介绍了传统的Mybatis框架的开发效率问题,通过发现问题,我们期望得到一款增强工具,在不改变Mybatis核心原理的同时,解决Mybatis开发效率问题,并提供其他数据库所需要的功能。
通过了解MybatisPlus,我们得知,它是一款国产的增强工具,极大的简化了Mybatis框架操作,降低了Mybatis框架的学习成本,提高了开发效率,在国内十分流行。

1.2 入门案例

1.2.1 准备相关开发环境

IDEA 2021.3.1
PostMan 10.6.7
Navicat 15.0.9
Mysql 5.7.27
JDK8 1.8.0_311

以上环境各位同学之前一定接触过,没有安装的自行安装,在这里就不再演示安装了
另外本课程需要有Mybatis和springboot相关的基础,如果没有这部分基础的同学,需要先回顾相关课程,然后再继续本课程的学习

1.2.2 搭建springboot工程

MybatisPlus_第15张图片
MybatisPlus_第16张图片

1.2.3 创建表结构

创建数据库mybatisplus
MybatisPlus_第17张图片

建表语句

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

1.2.4 添加表数据

插入数据

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

1.2.5 引入相关依赖

<dependency>
  <groupId>com.baomidougroupId>
  <artifactId>mybatis-plus-boot-starterartifactId>
  <version>3.5.3version>
dependency>

<dependency>
  <groupId>mysqlgroupId>
  <artifactId>mysql-connector-javaartifactId>
dependency>

<dependency>
  <groupId>com.alibabagroupId>
  <artifactId>druidartifactId>
  <version>1.1.16version>
dependency>

<dependency>
  <groupId>org.projectlombokgroupId>
  <artifactId>lombokartifactId>
dependency>

<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-webartifactId>
dependency>

1.2.6 创建实体类

| package com.powernode.domain;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;_@Data
@AllArgsConstructor
@NoArgsConstructorpublic class User {
private Long id;
private String name;
private Integer age;
private String email;

}

1.2.7 集成MybatisPlus

【1】编写Mapper接口

package com.powernode.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.powernode.domain.User;
import org.apache.ibatis.annotations.Mapper;

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

【2】 编写Service接口

package com.powernode.service;
import com.powernode.domain.User;
import java.util.List;
public interface UserService {
    List<User> selectList();
}

【3】 编写ServiceImpl

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    UserMapper userMapper;

    public List<User> selectList(){
        return userMapper.selectList(null);
    }
}

【4】 编写Controller

import com.powernode.domain.User;
import com.powernode.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
    @Autowired
    UserService userService;

    @RequestMapping("/selectList")
    public String selectList() {
        List<User> all = userService.selectList();
        return all;
    }
}

【5】 编写配置文件

spring:
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false

1.2.8 测试效果

1.2.9 单元测试

到此,模拟开发的三层我们就测试完了, 因为Controller和Service都是之前的知识,所以后面我们测试MybatisPlus的代码,通过单元测试的方式来编写

@SpringBootTestclass
Mp02ApplicationTests {

    @Autowired
    private UserMapper userMapper;

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

1.2.10 精简springboot的相关日志

去除mybatisplus的logo

mybatis-plus:
  global-config:
    banner: false

去除springboot的logo

spring:
    main:
     banner-mode: off

1.2.11 MybatisPlus的执行日志

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

1.2.12 小结

本章节我们通过编写了一个MybatisPlus的入门案例,体验到了MybatisPlus的便捷性,在后面的学习中,各种各样的MybatisPlus的操作都非常容易上手,可以说MybatisPlus是一款学习成本非常低的增强工具。

2 【基础篇】

2.1 通用Mapper接口介绍

有关于通用Mapper接口,之前我们已经看到了,我们自己编写的Mapper接口继承自BaseMapper接口,由BaseMapper接口提供了很多单表的增删改查相关的操作方法,在入门案例中,我们测试了查询所有的操作。在这一章节,我们介绍一些简单的Mapper接口中的方法,主要是感受一下,Mapper接口中对于单表的增删改查的操作都有涉及。更加高级的一些操作,随后的章节会讲到。

2.1.1 Mapper接口-简单插入数据

插入一条数据

@Test
void insert(){
    User user = new User();
    user.setId(6L);
    user.setName("Mike");
    user.setAge(33);
    user.setEmail("[email protected]");
    userMapper.insert(user);
}

2.1.2 Mapper接口-简单删除数据

根据id删除数据

@Test
void deleteOne(){
    userMapper.deleteById(6L);
}

2.1.3 Mapper接口-简单修改数据

测试修改全部数据

@Test
void updateById(){
    User user = new User();
    user.setId(6L);
    user.setName("迈克");
    user.setAge(35);
    user.setEmail("[email protected]");
    userMapper.updateById(user);
}

测试修改部分数据

@Test
void updateById(){
    User user = new User();
    user.setId(6L);
    user.setName("Mike");
    user.setAge(35);
    user.setEmail("[email protected]");
    userMapper.updateById(user);
}

2.1.4 Mapper接口-简单查询数据

根据Id查询

    @Test
    void selectById(){
        User user = userMapper.selectById(6L);
        System.out.println(user);
    }

查询所有

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

2.1.5 小结

本章我们测试了通过接口提供的基本增删改查的实现,可以感受到,将来我们有这些增删改查需求的时候,直接找到对应的方法调用,由Mapper接口的代理对象就会直接给我们拼接好指定的SQL语句,完成查询。
本章节我们测试了一些基本的增删改查操作,有关于条件查询、分页查询等高级的查询操作,在随后章节会统一讲解。

2.2 通用service接口介绍

除了Mapper接口,MybatisPlus还提供了IService接口和对应的实现类ServiceImpl,该实现类已经提供好了一些对应的Service相关的方法,在某些场景下,我们可以直接使用ServiceImpl提供的方法,实现对应的功能。

IService接口

IService接口中包含了service相关的一些增删改查方法
MybatisPlus_第18张图片

ServiceImpl实现类

ServiceImpl实现类提供了service相关的增删改查方法的实现

UserService接口继承自IService接口

UserServiceImpl类继承ServiceImpl

MybatisPlus_第19张图片

注入UserService对象,测试相关方法

@Autowired
private UserService userService;

2.2.1 Service接口-简单插入数据

@Test
void insertService(){
    User user = new User();
    user.setId(7L);
    user.setName("zhangsan");
    user.setAge(35);
    user.setEmail("[email protected]");

    userService.save(user);
}

2.2.2 Service接口-简单删除数据

@Test
void deleteService(){
    userService.removeById(7L);
}

2.2.3 Service接口-简单修改数据


@Test
void updateService(){
    User user = new User();
    user.setId(6L);
    user.setAge(40);
    userService.updateById(user);
}

2.2.4 Service接口-简单查询数据

@Test
void selectService(){
    List<User> userList = userService.selectList();
    System.out.println(userList);
}

2.2.5 小结

通过继承MybatisPlus提供的Service接口,我们既可以拓展自己的service方法,也可以使用现有的一些service方法

2.3 自定义接口方法

MybatisPlus除了给我们提供了这些丰富的接口方法以外,对于我们自己的需求,也可以编写自定义的接口方法,我们通过自己编写SQL语句的形式,实现想要的SQL需求

2.3.1 自定义Mapper接口方法

Mapper接口中提供抽象方法

@Mapper
public interface UserMapper extends BaseMapper<User> {
       User selectByName(String name);
}

提供映射配置文件,提供对应的SQL语句
MybatisPlus_第20张图片

| _

 

测试自定义的Mapper接口方法

@Test
void myMethod(){
    User user = userMapper.selectByName("Jone");
    System.out.println(user);
}

2.3.2 小结

通过本章节的学习,我们学会了自定义接口的方法,自定义接口的语法规范和之前编写Mybatis的语法规范一样,所以可以看出,MybatisPlus是无侵入式的,也就是引入了MybatisPlus并不会对于原先的语法造成任何改变。

3 【进阶篇】

3.1 映射

学习过Mybatis的同学应该知道,Mybatis框架之所以能够简化数据库操作,是因为他内部的映射机制,通过自动映射,进行数据的封装,我们只要符合映射规则,就可以快速高效的完成SQL操作的实现。
既然MybatisPlus是基于Mybatis的增强工具,所以也具有这样的映射规则。
我们先来了解一下自动映射规则。

3.1.1 自动映射规则

【1】表名和实体类名映射 -> 表名user 实体类名User
【2】字段名和实体类属性名映射 -> 字段名name 实体类属性名name
【3】字段名下划线命名方式和实体类属性小驼峰命名方式映射 ->
字段名 user_email 实体类属性名 userEmail
MybatisPlus支持这种映射规则,可以通过配置来设置
map-underscore-to-camel-case: true 表示支持下划线到驼峰的映射
map-underscore-to-camel-case: false 表示不支持下划线到驼峰的映射

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true

3.1.2 表映射

通过@TableName()注解指定映射的数据库表名,就会按照指定的表名进行映射
如:此时将数据库的表名改为powershop_user,要完成表名和实体类名的映射,需要将实体类名也要指定为powershop_user

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("powershop_user")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

如果有很多实体类,对应到数据库中的很多表,我们不需要每个依次配置,只需要配置一个全局的设置,他都会给每个实体类名前面添加指定的前缀,这里我们演示一下全局配置的效果

mybatis-plus:
  global-config:
    db-config:
      table-prefix: powershop_

3.1.3 字段映射

什么场景下会改变字段映射呢?
【1】当数据库字段和表实体类的属性不一致时,我们可以使用@TableField()注解改变字段和属性的映射,让注解中的名称和表字段保持一致
如:此时将数据库字段的名称我们改为username,在根据实体类的属性拼接SQL的使用,就会使用@TableField()中指定的名称username进行拼接,完成查询

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableField("username")
    private String name;
}

此时的SQL语句是这样的
SELECT id,username AS name,email FROM powershop_user

【2】数据库字段和表实体类的属性一致,框架在拼接SQL语句的时候,会使用属性名称直接拼接sql语句,例如:
SELECT id,username AS name,age,email,desc FROM powershop_user

这条语句直接进行查询的时候,会出现错误
Error querying database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘desc FROM powershop_user’ at line 1

原因是,desc属于关键字,不能直接用于sql查询,要解决这个问题,就需要将desc字段加上``符号,将他变为不是关键字,才能完成查询,那这个问题的根本也是改变生成的SQL语句的字段名称,也就是我们需要通过@TableField()改变实体类的属性名称,将desc变为desc,就可以解决这个问题

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableField("`desc`")
    private String desc;
}

MybatisPlus_第21张图片
此时可以观察到,框架拼接生成的SQL语句的字段名称变为了desc,这样是可以正常完成查询的

3.1.4 字段失效

当数据库中有字段不希望被查询,我们可以通过@TableField(select = false)来隐藏这个字段,那在拼接SQL语句的时候,就不会拼接这个字段
如:如果不想显示年龄信息,那么可以在age属性上添加这个注解,来隐藏这个字段

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableField(select = false)
    private Integer age;
}

生成的SQL语句如下,通过查询生成的SQL语句,发现并没有拼接age字段
MybatisPlus_第22张图片

3.1.5 视图属性

在实际开发中,有些字段不需要数据库存储,但是却需要展示,需要展示也就是意味着实体类中需要存在这个字段,我们称这些实体类中存在但是数据库中不存在的字段,叫做视图字段。
根据之前的经验,框架会默认将实体类中的属性作为查询字段进行拼接,那我们来思考,像这种视图字段,能够作为查询条件么,显示是不能的。因为数据库中没有这个字段,所以查询字段如果包含这个字段,SQL语句会出现问题。我们通过@TableField(exist = false)来去掉这个字段,不让他作为查询字段。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableField(exist = false)
    private Integer online;
}

可以看到查询结果中不包含该字段
MybatisPlus_第23张图片

3.1.6 小结

这一章节,我们讲解了MybatisPlus的映射规则,以及如果通过注解配置来改变这种映射规则。

3.2 条件构造器

3.2.1 条件构造器介绍

之前我们进行的MybatisPlus的操作,没有涉及到条件查询,实际上在开发需求中条件查询是非常普遍的。接下来我们就来讲解如何使用MybatisPlus完成条件查询。
首先,想要使用MybatisPlus完成条件查询,基于面向对象的思想,万物皆对象,那么查询条件也需要使用对象来完成封装。我们先看一下,在MybatisPlus中,和条件有关的类有哪些,他们之间有什么关系,理清楚了这个,我们在传递条件对象的时候,就很清晰了。

3.2.2 Wrapper

抽象类,条件类的顶层,提供了一些获取和判断相关的方法

3.2.3 AbstractWrapper

抽象类,Wrapper的子类,提供了所有的条件相关方法

3.2.4 AbstractLambdaWrapper

抽象类,AbstractWrapper的子类,确定字段参数为方法引用类型

3.2.5 QueryWrapper

类,AbstractWrapper的子类,如果我们需要传递String类型的字段信息,创建该对象

3.2.6 LambdaQueryWrapper

类,AbstractLambdaWrapper的子类,如果我们需要传递方法引用方式的字段信息,创建该对象

该图为以上各类的关系,我们在编写代码的时候,只需要关注QueryWrapper和LambdaQueryWrapper
MybatisPlus_第24张图片

3.2.7 小结

通过学习类的继承体系,我们知道,我们需要重点掌握QueryWrapper和LambdaQueryWrapper这两个类,在一般情况下,我们大多选择LambdaQueryWrapper,因为选择这种方式传递参数,不用担心拼写错误问题。

3.3 等值查询

3.3.1 eq

使用QueryWrapper对象,构建查询条件

@Test
void eq(){
    //1.创建QueryWrapper对象
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //2.设置条件,指定String字段名称和值
    queryWrapper.eq("name","Jack");
    //3.使用条件完成查询
    User user = userMapper.selectOne(queryWrapper);
    System.out.println(user);
}

测试效果
MybatisPlus_第25张图片
我们思考如果每次都是自己进行字段名称的编写,有可能会出现名称写错的情况,怎么避免这种情况呢,我们可以使用LambdaQueryWrapper对象,在构建字段时,使用方法引用的方式来选择字段,这样做可以避免字段拼写错误出现问题。
代码如下:

@Test
void eq2(){
    //1.创建lambdaQueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定String字段名称和值
    lambdaQueryWrapper.eq(User::getName,"Jack");
    //3.使用条件完成查询
    User user = userMapper.selectOne(lambdaQueryWrapper);
    System.out.println(user);
}

还要考虑一种情况,我们构建的条件是从哪里来的?应该是从客户端通过请求发送过来的,由服务端接收的。在网站中一般都会有多个条件入口,用户可以选择一个或多个条件进行查询,那这个时候在请求时,我们不能确定所有的条件都是有值的,部分条件可能用户没有传值,那该条件就为null。
比如在电商网站中,可以选择多个查询条件。

那为null的条件,我们是不需要进行查询条件拼接的,否则就会出现如下情况,将为null的条件进行拼接,筛选后无法查询出结果

@Test
void isNull(){
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(User::getName,null);
    User user = userMapper.selectOne(lambdaQueryWrapper);
    System.out.println(user);
}

当然我们要解决这个问题,可以先判断是否为空,根据判断结果选择是否拼接该字段,这个功能其实不需要我们写,由MybatisPlus的方法已经提供好了。

@Test
void isNull2(){
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    String name = null;
    lambdaQueryWrapper.eq(name != null,User::getName,name);
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

3.3.2 allEq

先演示一下如何通过多个eq,构建多条件查询

@Test
void allEq1(){
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(User::getName,"Jone");
    lambdaQueryWrapper.eq(User::getAge,18);

    User user = userMapper.selectOne(lambdaQueryWrapper);
    System.out.println(user);
}

如果此时有多个条件需要同时判断,我们可以将这多个条件放入到Map集合中,更加的方便

 @Test
    void allEq2(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //构建条件Map
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("name",null);
        hashMap.put("age",18);

        /**
         * 默认第二个参数为true
         *true: SELECT id,name,email FROM user WHERE (name IS NULL AND age = ?)
         *false: SELECT id,name,email FROM user WHERE (age = ?)
         */
        queryWrapper.allEq(hashMap,false);
        User user = userMapper.selectOne(queryWrapper);
        System.out.println(user);
    }

allEq(Map params, boolean null2IsNull)
参数params:表示传递的Map集合
参数null2IsNull:表示对于为null的条件是否判断isNull

3.3.3 ne(不等)

@Test
void ne(){
    //1.创建LambdaQueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定String字段名称和值
    String name = "Jone";
    lambdaQueryWrapper.ne(User::getName,name);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.4 范围查询

3.4.1 gt

@Test
void gt(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    Integer age = 18;
    lambdaQueryWrapper.gt(User::getAge,age);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.4.2 ge

@Test
void gt(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    Integer age = 18;
    lambdaQueryWrapper.ge(User::getAge,age);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.4.3 lt

@Test
void lt(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    Integer age = 18;
    lambdaQueryWrapper.lt(User::getAge,age);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.4.4 le

@Test
void le(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    Integer age = 18;
    lambdaQueryWrapper.le(User::getAge,age);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.4.5 between

@Test
void between(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.between(User::getAge,18,30);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.4.6 notBetween

@Test
void notBetween(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.notBetween(User::getAge,18,30);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}


拼接的SQL如下

3.5 模糊查询

3.5.1 like

@Test
void like(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.like(User::getName,"J");
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.5.2 notLike

@Test
void notLike(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.notLike(User::getName,"J");
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.5.3 likeLeft

@Test
void likeLeft(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.likeLeft(User::getName,"e");
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.5.4 likeRight

@Test
void likeLeft(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.likeLeft(User::getName,"J");
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.6 判空查询

3.6.1 isNull

@Test
void isNull(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称
    lambdaQueryWrapper.isNull(User::getName);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}
 

拼接的SQL如下

3.6.2 isNotNull

@Test
void isNotNull(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称
    lambdaQueryWrapper.isNotNull(User::getName);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.7 包含查询

3.7.1 in

@Test
void in(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    ArrayList<Integer> arrayList = new ArrayList<>();
    Collections.addAll(arrayList,18,20,21);
    lambdaQueryWrapper.in(User::getAge,arrayList);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}
@Test
void in2(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.in(User::getAge,18,20,21);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.7.2 notIn

@Test
void notIn(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    ArrayList<Integer> arrayList = new ArrayList<>();
    Collections.addAll(arrayList,18,20,21);
    lambdaQueryWrapper.notIn(User::getAge,arrayList);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

@Test
void notIn2(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.notIn(User::getAge,18,20,21);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.7.3 inSql

@Test
void inSql(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.inSql(User::getAge,"18,20,22");
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

@Test
void inSql2(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.inSql(User::getAge,"select age from powershop_user where age > 20");
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.7.4 notInSql

@Test
void notInSql(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.notInSql(User::getAge,"18,20,21");
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

@Test
void notInSql2(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定字段名称和值
    lambdaQueryWrapper.notInSql(User::getAge,"select age from powershop_user where age > 20");
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.8 分组查询

3.8.1 groupBy

@Test
void groupBy(){
    //1.创建QueryWrapper对象
   QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //2.设置条件,指定字段名称和值
    queryWrapper.groupBy("age");
    queryWrapper.select("age,count(*) as field_count");
    //3.使用条件完成查询
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    System.out.println(maps);
}

拼接的SQL如下
MybatisPlus_第26张图片
实际查询结果
MybatisPlus_第27张图片
封装结果

3.9 聚合查询

3.9.1 having

@Test
void having(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //分组字段
    queryWrapper.groupBy("age");
    //查询字段
    queryWrapper.select("age,count(*) as field_count");
    //聚合条件筛选
    queryWrapper.having("field_count = 1");
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    System.out.println(maps);
}

拼接的SQL如下
MybatisPlus_第28张图片

3.10 排序查询

3.10.1 orderByAsc

@Test
void orderByAsc(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定升序排序字段
    lambdaQueryWrapper.orderByAsc(User::getAge,User::getId);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.10.2 orderByDesc

@Test
void orderByDesc(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定降序排序字段
    lambdaQueryWrapper.orderByDesc(User::getAge,User::getId);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.10.3 orderBy

@Test
void orderBy(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.设置条件,指定降序排序字段
    lambdaQueryWrapper.orderBy(true,true,User::getId);
    lambdaQueryWrapper.orderBy(true,false,User::getAge);
    //3.使用条件完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.11 func查询

3.11.1 func

@Test
void func(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建逻辑判断语句
    lambdaQueryWrapper.func(i -> {
        if(true) {
            i.eq(User::getId, 1);
        }else {
            i.ne(User::getId, 1);
        }
    });
    //3.完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.12 逻辑查询

3.12.1 and

正常拼接默认就是and,例如

@Test
void and(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建条件查询语句
    lambdaQueryWrapper.gt(User::getAge,22).lt(User::getAge,30);
    //3.完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

and也可以进行嵌套

@Test
void and2(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建条件查询语句
    lambdaQueryWrapper.eq(User::getName,"wang").and(i -> i.gt(User::getAge,26).or().lt(User::getAge,22));
    //3.完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.12.2 or

@Test
void or(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建条件查询语句
    lambdaQueryWrapper.lt(User::getAge,20).or().gt(User::getAge,23);
    //3.完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

OR嵌套

@Test
void or2(){
     LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
     lambdaQueryWrapper.eq(User::getName,"wang").or(i -> i.gt(User::getAge,22).lt(User::getAge,26));
     List<User> users = userMapper.selectList(lambdaQueryWrapper);
     System.out.println(users);
}

拼接的SQL如下

3.12.3 nested

@Test
void nested(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建条件查询语句
    lambdaQueryWrapper.nested(i -> i.eq(User::getName, "Billie").ne(User::getAge, 22));
    //3.完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}
 

拼接的SQL如下

3.13 自定义条件查询

3.13.1 apply

@Test
void apply(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建条件查询语句
    lambdaQueryWrapper.apply("id = 1");
    //3.完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.14 last查询

3.14.1 last

@Test
void last(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建条件查询语句
    lambdaQueryWrapper.last("limit 0,2");
    //3.完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.15 exists查询

3.15.1 exists

@Test
void exists(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建查询条件
    lambdaQueryWrapper.exists("select id from powershop_user where age = 18");
    //3.查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.15.2 notExists

@Test
void notExists(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建查询条件
    lambdaQueryWrapper.notExists("select id from powershop_user where age = 33");
    //3.查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下

3.16 字段查询

3.16.1 select

@Test
void select(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建查询条件
    lambdaQueryWrapper.select(User::getId,User::getName);
    //3.查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

拼接的SQL如下
MybatisPlus_第29张图片

3.17 小结

本章节讲解了条件查询中的各种查询操作,我们可以通过面向对象的形式,直接调用方法来完成mysql中的条件查询

4 【高级篇】

4.1 主键策略

4.1.1 主键生成策略介绍

首先大家先要知道什么是主键,主键的作用就是唯一标识,我们可以通过这个唯一标识来定位到这条数据。
当然对于表数据中的主键,我们可以自己设计生成规则,生成主键。但是在更多的场景中,没有特殊要求的话,我们每次自己手动生成的比较麻烦,我们可以借助框架提供好的主键生成策略,来生成主键。这样比较方便快捷
在MybatisPlus中提供了一个注解,是@TableId,该注解提供了各种的主键生成策略,我们可以通过使用该注解来对于新增的数据指定主键生成策略。那么在以后新增数据的时候,数据就会按照我们指定的主键生成策略来生成对应的主键。

4.1.2 AUTO策略

该策略为跟随数据库表的主键递增策略,前提是数据库表的主键要设置为自增
MybatisPlus_第30张图片
此处要设置好下次递增的数字
MybatisPlus_第31张图片
实体类添加注解,指定主键生成策略

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {    
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

@Test
void primaryKey(){
    User user = new User();
    user.setName("Mary");
    user.setAge(35);
    user.setEmail("[email protected]");
    userMapper.insert(user);
}

拼接的SQL语句如下

4.1.3 INPUT策略

该策略表示,必须由我们手动的插入id,否则无法添加数据

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {    
    @TableId(type = IdType.INPUT)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

由于我们不使用AUTO了,所以把自动递增去掉
MybatisPlus_第32张图片
这里如果我们省略不写id,会发现,无法插入数据

@Test
void primaryKey(){
    User user = new User();
    user.setName("Jerry");
    user.setAge(38);
    user.setEmail("[email protected]");
    userMapper.insert(user);
}

MybatisPlus_第33张图片
但是我们自己指定了id,发现可以添加成功

@Test
void primaryKey(){
    User user = new User();
    user.setId(8L);
    user.setName("Jerry");
    user.setAge(38);
    user.setEmail("[email protected]");
    userMapper.insert(user);
}

4.1.4 ASSIGN_ID策略

我们来思考一下,像之前这种自动递增的方式,有什么问题?
如果我们将来一张表的数据量很大,我们需要进行分表。
常见的分表策略有两种:
【1】水平拆分
MybatisPlus_第34张图片
水平拆分就是将一个大的表按照数据量进行拆分

【2】垂直拆分
MybatisPlus_第35张图片
垂直拆分就是将一个大的表按照字段进行拆分

其实我们对于拆分后的数据,有三点需求,就拿水平拆分来说:
【1】之前的表的主键是有序的,拆分后还是有序的
【2】虽然做了表的拆分,但是每条数据还需要保证主键的唯一性
【3】主键最好不要直接暴露数据的数量,这样容易被外界知道关键信息
那就需要有一种算法,能够实现这三个需求,这个算法就是雪花算法

雪花算法是由一个64位的二进制组成的,最终就是一个Long类型的数值。
主要分为四部分存储
【1】1位的符号位,固定值为0
【2】41位的时间戳
【3】10位的机器码,包含5位机器id和5位服务id
【4】12位的序列号
MybatisPlus_第36张图片
使用雪花算法可以实现有序、唯一、且不直接暴露排序的数字。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

@Test
void primaryKey(){
    User user = new User();
    user.setName("Jerry");
    user.setAge(38);
    user.setEmail("[email protected]");
    userMapper.insert(user);
}

我们可以在插入后发现一个19位长度的id,该id就是雪花算法生成的id,这是二级制的十进制表示形式
MybatisPlus_第37张图片

4.1.5 NONE策略

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableId(type = IdType.NONE)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

NONE策略表示不指定主键生成策略,当我们没有指定主键生成策略或者主键策略为NONE的时候,他跟随的是全局策略,那我们来看一下他的全局策略默认是什么
全局配置中 id-type是用于配置主键生成策略的,我们可以看一下id-type的默认值
MybatisPlus_第38张图片
MybatisPlus_第39张图片
通过查看源码发现,id-type的默认值就是雪花算法

4.1.6 ASSIGN_UUID策略

UUID(Universally Unique Identifier)全局唯一标识符,定义为一个字符串主键,采用32位数字组成,编码采用16进制,定义了在时间和空间都完全唯一的系统信息。
UUID的编码规则:
【1】1~8位采用系统时间,在系统时间上精确到毫秒级保证时间上的唯一性;
【2】9~16位采用底层的IP地址,在服务器集群中的唯一性;
【3】17~24位采用当前对象的HashCode值,在一个内部对象上的唯一性;
【4】25~32位采用调用方法的一个随机数,在一个对象内的毫秒级的唯一性。
通过以上4种策略可以保证唯一性。在系统中需要用到随机数的地方都可以考虑采用UUID算法。
我们想要演示UUID的效果,需要改变一下表的字段类型和实体类的属性类型
将数据库表的字段类型改为varchar(50)
MybatisPlus_第40张图片
将实体类的属性类型改为String,并指定主键生成策略为IdType.ASSIGN_UUID

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    private String name;
    private Integer age;
    private String email;
}

完成数据的添加

@Test
void primaryKey(){
    User user = new User();
    user.setName("Jerry");
    user.setAge(38);
    user.setEmail("[email protected]");
    userMapper.insert(user);
}

我们会发现,成功添加了一条数据,id为uuid类型
MybatisPlus_第41张图片

4.1.7 小结

本章节讲解了主键生成策略,我们可以通过指定主键生成策略来生成不同的主键id,从而达到对于数据进行唯一标识的作用。

4.2 分页

分页操作在实际开发中非常的常见,我们在各种平台和网站中都可以看到分页的效果。
例如:京东商城的分页效果
MybatisPlus_第42张图片
例如:百度的分页效果
MybatisPlus_第43张图片
在MybatisPlus中我们如何配置分页呢?这里我们思考一下
在MybatisPlus中的查询语句是怎么实现的,我们可以通过两种方式实现查询语句
【1】通过MybatisPlus提供的方法来实现条件查询
【2】通过自定义SQL语句的方式来实现查询
接下来我们就来演示这两种分页方式如何实现

4.2.1 分页插件

在大部分场景下,如果我们的SQL没有这么复杂,是可以直接通过MybatisPlus提供的方法来实现查询的,在这种情况下,我们可以通过配置分页插件来实现分页效果
分页的本质就是需要设置一个拦截器,通过拦截器拦截了SQL,通过在SQL语句的结尾添加limit关键字,来实现分页的效果
MybatisPlus_第44张图片

接下来看一下配置的步骤
【1】通过配置类来指定一个具体数据库的分页插件,因为不同的数据库的方言不同,具体生成的分页语句也会不同,这里我们指定数据库为Mysql数据库

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

【2】实现分页查询效果

@Test
void selectPage(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.创建分页查询对象,指定当前页和每页显示条数
    IPage<User> page = new Page<>(1,3);
    //3.执行分页查询
    userMapper.selectPage(page, lambdaQueryWrapper);
    //4.查看分页查询的结果
    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());
}

MybatisPlus_第45张图片

4.2.2 自定义分页插件

在某些场景下,我们需要自定义SQL语句来进行查询。接下来我们来演示一下自定义SQL的分页操作
【1】在UserMapper.xml映射配置文件中提供查询语句

DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.powernode.mapper.UserMapper">

  <select id="selectByName" resultType="com.powernode.domain.User">
    select * from powershop_user where name = #{name}
  select>

mapper>

【2】在Mapper接口中提供对应的方法,方法中将IPage对象作为参数传入

@Mapper
public interface UserMapper extends BaseMapper<User> {
    IPage<User> selectByName(IPage<User> page, String name);
}

【3】表数据为
MybatisPlus_第46张图片

【4】实现分页查询效果

@Test
void selectPage2(){
    //1.创建分页查询对象,指定当前页和每页显示条数
    IPage<User> page = new Page<>(1,2);
    //2.执行分页查询
    userMapper.selectByName(page,"Mary");
    //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());
}

4.2.3 小结

这里我们学习了两种分页的配置方法,将来以后我们在进行条件查询的时候,可以使用分页的配置进行配置。

4.3 ActiveRecord模式

4.3.1 ActiveRecord介绍

ActiveRecord(活动记录,简称AR),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。ActiveRecord 一直广受解释型动态语言( PHP 、 Ruby 等)的喜爱,通过围绕一个数据对象进行CRUD操作。而 Java 作为准静态(编译型)语言,对于 ActiveRecord 往往只能感叹其优雅,所以 MP 也在 AR 道路上进行了一定的探索,仅仅需要让实体类继承 Model 类且实现主键指定方法,即可开启 AR 之旅。

4.3.2 ActiveRecord实现

接下来我们来看一下ActiveRecord的实现步骤
【1】让实体类继承Model类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

我们可以看到,Model类中提供了一些增删改查方法,这样的话我们就可以直接使用实体类对象调用这些增删改查方法了,简化了操作的语法,但是他的底层依然是需要UserMapper的,所以持久层接口并不能省略
【2】测试ActiveRecord模式的增删改查
添加数据

@Test
void activeRecordAdd(){
    User user = new User();
    user.setName("wang");
    user.setAge(35);
    user.setEmail("[email protected]");
    user.insert();
}

删除数据

@Test
void activeRecordDelete(){
    User user = new User();
    user.setId(8L);
    user.deleteById();
}

修改数据

@Test
void activeRecordUpdate(){
    User user = new User();
    user.setId(6L);
    user.setAge(50);
    user.updateById();
}

查询数据

@Test
void activeRecordSelect(){
    User user = new User();
    user.setId(6L);
    User result = user.selectById();
    System.out.println(result);
}

4.4 SimpleQuery工具类

4.4.1 SimpleQuery介绍

SimpleQuery可以对selectList查询后的结果用Stream流进行了一些封装,使其可以返回一些指定结果,简洁了api的调用

4.4.2 list

演示基于字段封装集合

@Test
void testList(){
    List<Long> ids = SimpleQuery.list(new LambdaQueryWrapper<User>().eq(User::getName, "Mary"), User::getId);
    System.out.println(ids);
}

演示对于封装后的字段进行lambda操作

@Test
void testList2(){
    List<String> names = SimpleQuery.list(new LambdaQueryWrapper<User>().eq(User::getName, "Mary"),User::getName,e ->  Optional.of(e.getName()).map(String::toLowerCase).ifPresent(e::setName));
    System.out.println(names);
}

4.4.3 map

演示将所有的对象以id,实体的方式封装为Map集合

@Test
void testMap(){
    //将所有元素封装为Map形式
    Map<Long, User> idEntityMap = SimpleQuery.keyMap(
            new LambdaQueryWrapper<>(), User::getId);
    System.out.println(idEntityMap);
}

演示将单个对象以id,实体的方式封装为Map集合

@Test
void testMap2(){
    //将单个元素封装为Map形式
    Map<Long, User> idEntityMap = SimpleQuery.keyMap(
            new LambdaQueryWrapper<User>().eq(User::getId,1L), User::getId);
    System.out.println(idEntityMap);
}

演示只想要id和name组成的map

@Test
void testMap3(){
    //只想要只想要id和name组成的map
    Map<Long, String> idNameMap = SimpleQuery.map(new LambdaQueryWrapper<>(), User::getId, User::getName);
    System.out.println(idNameMap);
}

4.4.4 Group

演示分组效果

@Test
void testGroup(){
    Map<String, List<User>> nameUsersMap = SimpleQuery.group(new LambdaQueryWrapper<>(), User::getName);
    System.out.println(nameUsersMap);
}

4.4.5 小结

在这一小节,我们演示了SimpleQuery提供的Stream流式操作的方法,通过这些操作继续为我们提供了查询方面简化的功能

5 【拓展篇】

5.1 逻辑删除

前面我们完成了基本的增删改查操作,但是对于删除操作来说,我们思考一个问题,在实际开发中我们真的会将数据完成从数据库中删除掉么?
当然是不会的,这里我们举个例子:
在电商网站中,我们会上架很多商品,这些商品下架以后,我们如果将这些商品从数据库中删除,那么在年底统计商品数据信息的时候,这个商品要统计的,所以这个商品信息我们是不能删除的。
MybatisPlus_第47张图片
如果商城中的商品下架了,这时候我们将商品从数据库删掉
MybatisPlus_第48张图片
那到了年终总结的时候,我们要总结一下这一年的销售额,发现少了20000,这肯定不合理。所以我们是不能将数据真实删除的。
这里我们就采取逻辑删除的方案,逻辑删除的操作就是增加一个字段表示这个数据的状态,如果一条数据需要删除,我们通过改变这条数据的状态来实现,这样既可以表示这条数据是删除的状态,又保留了数据以便以后统计,我们来实现一下这个效果。

【1】先在表中增加一列字段,表示是否删除的状态,这里我们使用的字段类型为int类型,通过1表示该条数据可用,0表示该条数据不可用
MybatisPlus_第49张图片

【2】实体类添加一个字段为Integer,用于对应表中的字段

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    @TableLogic(value = "1",delval = "0")
    private Integer status;
}

【3】测试逻辑删除效果

@Test
void logicDelete(){
    userMapper.deleteById(7L);
}

查看拼接的SQL语句,我们发现在执行删除操作的时候,语句变成了修改,是将这条数据的状态由1变为的0,表示这条数据为删除状态

MybatisPlus_第50张图片
我们还可以通过全局配置来实现逻辑删除的效果
MybatisPlus_第51张图片

5.2 通用枚举

首先我们先来回顾一下枚举,什么是枚举呢?
当我们想要表示一组信息,这组信息只能从一些固定的值中进行选择,不能随意写,在这种场景下,枚举就非常的合适。
例如我们想要表示性别,性别只有两个值,要么是男性,要么是女性,那我们就可以使用枚举来描述性别。
【1】我们先在表中添加一个字段,表示性别,这里我们一般使用int来描述,因为int类型可以通过0和1这两个值来表示两个不同的性别
MybatisPlus_第52张图片
【2】编写枚举类

public enum GenderEnum {

    MAN(0,"男"),
    WOMAN(1,"女");

    private Integer gender;
    private String genderName;

    GenderEnum(Integer gender, String genderName) {
        this.gender = gender;
        this.genderName = genderName;
    }
}

【3】实体类添加相关字段

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private GenderEnum gender;
    private Integer status;
}

【4】添加数据

@Test
void enumTest(){
    User user = new User();
    user.setName("liu");
    user.setAge(29);
    user.setEmail("[email protected]");
    user.setGenderEnum(GenderEnum.MAN);
    user.setStatus(1);
    userMapper.insert(user);
}

此时我们查看控制台,会发现添加失败了
MybatisPlus_第53张图片
原因是我们无法将一个枚举类型作为int数字插入到数据库中。不过我们对于枚举类型都给了对应的int的值,所以这里我们只需要进行一个配置,就可以将枚举类型作为数字插入到数据库中,为属性gender,添加上@EnumValue注解

public enum GenderEnum {

    MAN(0,"男"),
    WOMAN(1,"女");

    @EnumValue
    private Integer gender;
    private String genderName;

    GenderEnum(Integer gender, String genderName) {
        this.gender = gender;
        this.genderName = genderName;
    }
}

此时我们再次执行添加操作,发现可以成功添加数据,而枚举类型的值也作为数据被插入到数据库中

MybatisPlus_第54张图片

5.3 字段类型处理器

在某些场景下,我们在实体类中是使用Map集合作为属性接收前端传递过来的数据的,但是这些数据存储在数据库时,我们使用的是json格式的数据进行存储,json本质是一个字符串,就是varchar类型。那怎么做到实体类的Map类型和数据库的varchar类型的互相转换,这里就需要使用到字段类型处理器来完成。
【1】我们先在实体类中添加一个字段,Map类型

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User extends Model<User> {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private GenderEnum gender;
    private Integer status;
    private Map<String,String> contact;//联系方式}

【2】在数据库中我们添加一个字段,为varchar类型
MybatisPlus_第55张图片

【3】为实体类添加上对应的注解,实现使用字段类型处理器进行不同类型数据转换

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(autoResultMap = true)//查询时将json字符串封装为Map集合
public class User extends Model<User> {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private GenderEnum gender;
    private Integer status;
    @TableField(typeHandler = FastjsonTypeHandler.class)//指定字段类型处理器
    private Map<String,String> contact;//联系方式
}

【4】字段类型处理器依赖Fastjson这个Json处理器,所以我们需要引入对应的依赖

<dependency>
  <groupId>com.alibabagroupId>
  <artifactId>fastjsonartifactId>
  <version>1.2.76version>
dependency>

【5】测试添加操作

@Test
void typeHandler(){
    User user = new User();
    user.setName("zhang");
    user.setAge(28);
    user.setEmail("[email protected]");
    user.setGender(GenderEnum.MAN);
    user.setStatus(1);
    HashMap<String, String> contact = new HashMap<>();
    contact.put("phone","010-1234567");
    contact.put("tel","13388889999");
    user.setContact(contact);

    userMapper.insert(user);
}

执行的SQL语句如下

==>  Preparing: INSERT INTO powershop_user ( id, name, age, email, gender, status, contact ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
==> Parameters: 1617915941843202049(Long), zhang(String), 28(Integer), [email protected](String), 0(Integer), 1(Integer), {"phone":"010-1234567","tel":"13388889999"}(String)
<==    Updates: 1

通过观察SQL语句,我们发现当插入一个Map类型的字段的时候,该字段会转换为String类型

查看数据库中的信息,发现添加成功

【6】测试查询操作,通过结果发现,从数据库中查询出来的数据,已经被转到Map集合

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

MybatisPlus_第56张图片

5.4 自动填充功能

在项目中有一些属性,如果我们不希望每次都填充的话,我们可以设置为自动填充,比如常见的时间,创建时间和更新时间可以设置为自动填充。
【1】在数据库的表中添加两个字段
MybatisPlus_第57张图片
注意只有设置了下划线和小驼峰映射,这种mysql的写法才能和实体类完成映射
MybatisPlus_第58张图片
【2】在实体类中,添加对应字段,并为需要自动填充的属性指定填充时机

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(autoResultMap = true)
public class User extends Model<User> {
    @TableId
    private Long id;
    private String name;
    private Integer age;
    private String email;
    private Integer status;
    private GenderEnum gender;
    @TableField(typeHandler = FastjsonTypeHandler.class)
    private Map<String,String> contact;
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}

【3】编写自动填充处理器,指定填充策略

@Component
public class MyMetaHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        setFieldValByName("createTime",new Date(),metaObject);
        setFieldValByName("updateTime",new Date(),metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        setFieldValByName("updateTime",new Date(),metaObject);
    }
}

【4】这里在插入前先设置一下mysql时区

通过查看发现,目前时区时间正常

【5】再将配置文件的时区修改为serverTimezone=Asia/Shanghai

【6】测试插入操作

@Test
void testFill(){
    User user = new User();
    user.setName("wang");
    user.setAge(35);
    user.setEmail("[email protected]");
    user.setGender(GenderEnum.MAN);
    user.setStatus(1);
    HashMap<String, String> contact = new HashMap<>();
    contact.put("phone","010-1234567");
    contact.put("tel","13388889999");
    userMapper.insert(user);
}

MybatisPlus_第59张图片
【7】测试更新操作

@Test
void testFill2(){
    User user = new User();
    user.setId(6L);
    user.setName("wang");
    user.setAge(35);
    user.setEmail("[email protected]");
    user.setGender(GenderEnum.MAN);
    user.setStatus(1);
    HashMap<String, String> contact = new HashMap<>();
    contact.put("phone","010-1234567");
    contact.put("tel","13388889999");

    userMapper.updateById(user);
}

MybatisPlus_第60张图片

5.5 防全表更新与删除插件

在实际开发中,全表更新和删除是非常危险的操作,在MybatisPlus中,提供了插件和防止这种危险操作的发生
先演示一下全表更新的场景

@Test
public void testUpdateAll(){
    User user = new User();
    user.setGender(GenderEnum.MAN);
    userService.saveOrUpdate(user,null);
}

MybatisPlus_第61张图片
这是很危险的

如何解决呢?
注入MybatisPlusInterceptor类,并配置BlockAttackInnerInterceptor拦截器

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}

测试全表更新,会出现抛出异常,防止了全表更新

@SpringBootTest
public class QueryTest {

    @Autowired
    private UserService userService;

	@Test
    void allUpdate(){
    User user = new User();
    user.setId(999L);
    user.setName("wang");
    user.setEmail("[email protected]");
    userService.saveOrUpdate(user,null);
	}
}

5.6 MybatisX快速开发插件

5.6.1 安装

MybatisX是一款IDEA提供的插件,目的是为了我们简化Mybatis以及MybatisPlus框架而生。
我们来看一下,如何在IDEA中安装插件
【1】首先选择File -> Settings
MybatisPlus_第62张图片
【2】选择Plugins

MybatisPlus_第63张图片

【3】搜索MybatisX,点击安装
MybatisPlus_第64张图片
【4】这种效果就是安装完毕了
MybatisPlus_第65张图片
【5】在已安装的目录下,我们可以看到有了这个IDEA插件MybatisPlus_第66张图片
【6】此时我们勾选他,表示启用

【7】重启IDEA,让该插件生效,至此MybatisX插件就安装完毕了

5.6.2 功能

插件安装好以后,我们来看一下插件的功能
1、Mapper接口和映射文件的跳转功能
MybatisPlus_第67张图片
MybatisPlus_第68张图片

2、逆向工程
逆向工程就是通过数据库表结构,逆向产生Java工程的结构
包括以下几点:
(1)实体类
(2)Mapper接口
(3)Mapper映射文件
(4)Service接口
(5)Service实现类
在这里我们创建一个模块,用于测试逆向工程功能
MybatisPlus_第69张图片
MybatisPlus_第70张图片

引入依赖,和编写对应的配置文件信息

<dependencies>
  <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starterartifactId>
  dependency>

  <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-testartifactId>
    <scope>testscope>
  dependency>

  <dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.31version>
  dependency>

  <dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.2.8version>
  dependency>

  <dependency>
    <groupId>com.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
    <version>3.5.3version>
  dependency>

  <dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
  dependency>

  <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
  dependency>
dependencies>


spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver

【1】首先使用IDEA连接mysql
MybatisPlus_第71张图片
【2】填写连接信息,测试连接通过
MybatisPlus_第72张图片

【3】找到表右键,选择插件的逆向工程选项
MybatisPlus_第73张图片

【4】编写逆向工程配置信息
MybatisPlus_第74张图片
【5】编写生成信息
MybatisPlus_第75张图片
【6】观察生成结构,发现下面这些内容都已产生
(1)实体类
(2)Mapper接口
(3)Mapper映射文件
(4)Service接口
(5)Service映射文件

【7】接下来我们在Mapper接口上添加@Mapper注解

@Mapper
public interface UserMapper extends BaseMapper<User> {

}

【8】测试代码环境

  1. 常见需求代码生成

虽然Mapper接口中提供了一些常见方法,我们可以直接使用这些常见的方法来完成sql操作,但是对于实际场景中各种复杂的操作需求来说,依然是不够用的,所以MybatisX提供了更多的方法,以及可以根据这些方法直接生成对应的sql语句,这样使得开发变得更加的简单。
可以根据名称联想常见的操作

@Mapper
public interface UserMapper extends BaseMapper<User> {
    //添加操作
    int insertSelective(User user);

    //删除操作
    int deleteByNameAndAge(@Param("name") String name, @Param("age") Integer age);

    //修改操作
    int updateNameByAge(@Param("name") String name, @Param("age") Integer age);

    //查询操作
    List<User> selectAllByAgeBetween(@Param("beginAge") Integer beginAge, @Param("endAge") Integer endAge);
}

在映射配置文件中,会生成对应的sql,并不需要我们编写

<insert id="insertSelective">
  insert into powershop_user
  <trim prefix="(" suffix=")" suffixOverrides=",">
    <if test="id != null">id,if>
    <if test="name != null">name,if>
    <if test="age != null">age,if>
    <if test="email != null">email,if>
  trim>
  values
  <trim prefix="(" suffix=")" suffixOverrides=",">
    <if test="id != null">#{id,jdbcType=BIGINT},if>
    <if test="name != null">#{name,jdbcType=VARCHAR},if>
    <if test="age != null">#{age,jdbcType=INTEGER},if>
    <if test="email != null">#{email,jdbcType=VARCHAR},if>
  trim>
insert>
<delete id="deleteByNameAndAge">
  delete
  from powershop_user
  where name = #{name,jdbcType=VARCHAR}
  AND age = #{age,jdbcType=NUMERIC}
delete>
<update id="updateNameByAge">
  update powershop_user
  set name = #{name,jdbcType=VARCHAR}
  where age = #{age,jdbcType=NUMERIC}
update>
<select id="selectAllByAgeBetween" resultMap="BaseResultMap">
  select
  <include refid="Base_Column_List"/>
  from powershop_user
  where
  age between #{beginAge,jdbcType=INTEGER} and #{endAge,jdbcType=INTEGER}
select>

5.7 乐观锁

首先我们先要了解开发中的一个常见场景,叫做并发请求。并发请求就是在同一时刻有多个请求同时请求服务器资源,如果是获取信息,没什么问题,但是如果是对于信息做修改操作呢,那就会出现问题。
这里举一个具体的例子,比如目前商品的库存只剩余1件了,这个时候有多个用户都想要购买这件商品,都发起了购买商品的请求,那么能让这多个用户都购买到么,肯定是不行的,因为多个用户都买到了这件商品,那么就会出现超卖问题,库存不够是没法发货的。所以在开发中就要解决这种超卖的问题
MybatisPlus_第76张图片

抛开超卖这一种场景,诸如此类并发访问的场景非常多,这类场景的核心问题就是,一个
请求在执行的过程中,其他请求不能改变数据,如果是一次完整的请求,在该请求的过程中其他请求没有对于这个数据产生修改操作,那么这个请求是能够正常修改数据的。如果该请求在改变数据的过程中,已经有其他请求改变了数据,那该请求就不去改变这条数据了。
MybatisPlus_第77张图片

想要解决这类问题,最常见的就是加锁的思想,锁可以用验证在请求的执行过程中,是否有数据发生改变。
常见的数据库锁类型有两种,悲观锁和乐观锁。
一次完成的修改操作是,先查询数据,然后修改数据。
悲观锁:悲观锁是在查询的时候就锁定数据,在这次请求未完成之前,不会释放锁。等到这次请求完毕以后,再释放锁,释放了锁以后,其他请求才可以对于这条数据完成读写

这样做的操作能够保证读取到的信息就是当前的信息,保证了信息的正确性,但是并发效率很低,在实际开发中使用悲观锁的场景很少,因为在并发时我们是要保证效率的。

乐观锁:乐观锁是通过表字段完成设计的,他的核心思想是,在读取的时候不加锁,其他请求依然可以读取到这个数据,在修改的时候判断一个数据是否有被修改过,如果有被修改过,那本次请求的修改操作失效。

具体的通过sql是这样实现的
Update 表 set 字段 = 新值,version = version + 1 where version = 1

这样做的操作是不会对于数据读取产生影响,并发的效率较高。但是可能目前看到的数据并不是真实信息数据,是被修改之前的,但是在很多场景下是可以容忍的,并不是产生很大影响,例如很多时候我们看到的是有库存,或者都加入到购物车了,但是点进去以后库存没有了。

接下来我们来看一下乐观锁的使用
【1】在数据库表中添加一个字段version,表示版本,默认值是1


生成后的效果
MybatisPlus_第78张图片
【2】找到实体类,添加对应的属性,并使用@Version标注为这是一个乐观锁字段信息

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

【3】因为要对每条修改语句完成语句的增强,这里我们通过拦截器的配置,让每条修改的sql语句在执行的时候,都加上版本控制的功能

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

【4】测试效果,这里我们模拟先查询,再修改

@Test
void updateTest(){
    User user = userMapper.selectById(6L);
    user.setName("li");
    userMapper.updateById(user);
}

我们通过查看拼接好的SQL语句发现,查询时将User的数据查询出来,是包含version版本信息的
MybatisPlus_第79张图片

当我们完成修改时,他会将版本号 + 1

此时查看数据发现,更改姓名后,version已经为2了

MybatisPlus_第80张图片

接下来我们模拟一下,当出现多个修改请求的时候,是否能够做到乐观锁的效果。
乐观锁的效果是,一个请求在修改的过程中,是允许另一个请求查询的,但是修改时会通过版本号是否改变来决定是否修改,如果版本号变了,证明已经有请求修改过数据了,那这次修改不生效,如果版本号没有发生变化,那就完成修改。

@Test
void updateTest2(){
    //模拟操作1的查询操作
    User user1 = userMapper.selectById(6L);

    //模拟操作2的查询操作
    User user2 = userMapper.selectById(6L);

    //模拟操作2的修改操作
    user2.setName("lisi");
    userMapper.updateById(user2);

    //模拟操作1的修改操作
    user1.setName("zhangsan");
    userMapper.updateById(user1);
}

我们来看下这段代码的执行过程,这段代码其实是两次操作,只不过操作1在执行的过程中,有操作2完成了对于数据的修改,这时操作1就无法再次进行修改了
操作1的查询:此时版本为2
MybatisPlus_第81张图片

操作2的查询:此时版本为2

操作2的修改:此时检查版本,版本没有变化,所以完成修改,并将版本改为3

MybatisPlus_第82张图片

操作1的修改:此时检查版本,版本已经有最初获取的版本信息发生了变化,所以杜绝修改

5.8 代码生成器

代码生成器和逆向工程的区别在于,代码生成器可以生成更多的结构,更多的内容,允许我们能够配置生成的选项更多。在这里我们演示一下代码生成器的用法。
【1】参考官网,使用代码生成器需要引入两个依赖


<dependency>
  <groupId>com.baomidougroupId>
  <artifactId>mybatis-plus-generatorartifactId>
  <version>3.5.3version>
dependency>

<dependency>
  <groupId>org.freemarkergroupId>
  <artifactId>freemarkerartifactId>
  <version>2.3.31version>
dependency>

【2】编写代码生成器代码

@SpringBootTest
class GeneratorApplicationTests {
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false", "root", "root")
        .globalConfig(builder -> {
            builder.author("powernode") // 设置作者
            //.enableSwagger() // 开启 swagger 模式
            .fileOverride() // 覆盖已生成文件
            .outputDir("D://"); // 指定输出目录
        })
        .packageConfig(builder -> {
            builder.parent("com.powernode") // 设置父包名
            .moduleName("mybatisplus") // 设置父包模块名
            .pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 设置mapperXml生成路径
        })
        .strategyConfig(builder -> {
            builder.addInclude("powershop_user") // 设置需要生成的表名
            .addTablePrefix("powershop"); // 设置过滤表前缀
        })
        .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
        .execute();
    }
}

【3】执行,查看生成效果

5.9 执行SQL分析打印

在我们日常开发工作当中,避免不了查看当前程序所执行的SQL语句,以及了解它的执行时间,方便分析是否出现了慢SQL问题。我们可以使用MybatisPlus提供的SQL分析打印的功能,来获取SQL语句执行的时间。
【1】由于该功能依赖于p6spy组件,所以需要在pom.xml中先引入该组件

<dependency>
  <groupId>p6spygroupId>
  <artifactId>p6spyartifactId>
  <version>3.9.1version>
dependency>

【2】在application.yml中进行配置
将驱动和url修改

spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql

【3】在resources下,创建 spy.properties配置文件

#3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

【4】测试
执行查询所有的操作,可以看到sql语句的执行时间
MybatisPlus_第83张图片

5.10 多数据源

在学习多数据源之前,我们先来了解一下分库分表
当一个项目的数据库的数据十分庞大时,在完成SQL操作的时候,需要检索的数据就会更多,我们会遇到性能问题,会出现SQL执行效率低的问题。
针对这个问题,我们的解决方案是,将一个数据库中的数据,拆分到多个数据库中,从而减少单个数据库的数据量,从分摊访问请求的压力和减少单个数据库数据量这两个方面,都提升了效率。
我们来演示一下,在MybatisPlus中,如何演示数据源切换的效果
【1】先创建一个新的模块,将之前模块中的内容复制过来
结构如下
MybatisPlus_第84张图片

【2】引入依赖

<dependency>
  <groupId>com.baomidougroupId>
  <artifactId>dynamic-datasource-spring-boot-starterartifactId>
  <version>3.1.0version>
dependency>

【3】创建新的数据库,提供多数据源环境

数据库

表数据
MybatisPlus_第85张图片

【4】编写配置文件,指定多数据源信息

spring:
  datasource:
    dynamic:
      primary: master
      strict: false
      datasource:
        master:
          username: root
          password: root
          url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_1:
          username: root
          password: root
          url: jdbc:mysql://localhost:3306/mybatisplus2?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver

【5】创建多个Service,分别使用@DS注解描述不同的数据源信息

@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
@Service
@DS("slave_1")
public class UserServiceImpl2 extends ServiceImpl<UserMapper, User> implements UserService{
}

【6】测试service多数据源环境执行结果

@SpringBootTest
class Mp03ApplicationTests {

    @Autowired
    private UserServiceImpl userServiceImpl;

    @Autowired
    private UserServiceImpl2 userServiceImpl2;

    @Test
    public void select(){
        User user = userServiceImpl.getById(1L);
        System.out.println(user);
    }

    @Test
    public void select2(){
        User user = userServiceImpl2.getById(1L);
        System.out.println(user);
    }
}

【7】观察测试结果,发现结果可以从两个数据源中获取
MybatisPlus_第86张图片

MybatisPlus_第87张图片

你可能感兴趣的:(Mybatis,spring,boot,java,mybatis-plus)