MybatisPlus most complete tutorial

Article Directory

1 Introduction

Official website: https://baomidou.com/
Introduction: It MyBatis-Plus (opens new window)(简称 MP)is an enhancement tool of MyBatis (opens new window). On the basis of MyBatis, it only enhances and does not change. It is born to simplify development and improve efficiency.

Vision:
Our vision is to become the best partner of MyBatis, just like the 1P and 2P in Contra, the collocation of friends and friends will double the efficiency.
Insert picture description here


characteristic:

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

Architecture design:

Insert picture description here

2. Getting started based on mybatis-plus

2.1, the comparison of the implementation of mybatis and mybatis-plus

Based on Mybatis, you need to write the xxxMapper interface, and manually write the CRUD method to provide the xxxMapper.xml mapping file, and manually write the SQL statement corresponding to each method. Based on Mybatis-plus, you only need to create the xxxMapper interface and inherit the BaseMapper interface. This is to use mybatis- plus all the operations that need to be completed, even without creating a SQL mapping file

2.2 Introduction to BaseMapper interface

2.2.1. How to understand the core interface BaseMapper?

When using Mybatis-Plus, the core operation class is the BaseMapper interface, which is ultimately the implementation mechanism of the Mybatis interface programming. It provides a series of basic methods for adding, deleting, modifying and checking by default, and developers do not need to write for these basic operations SQL for processing operations (The mechanism provided by Mybatis requires developers to provide SQL statements in mapper.xml), so we can guess that Mybatis-Plus must have completed the SQL statement generation operation of the method provided by the BaseMapper interface.

2.2.2 What methods does the BaseMapper interface define for us?

Insert picture description here

2.2.3, BaseMapper interface source code

package com.baomidou.mybatisplus.core.mapper;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Param;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;


public interface BaseMapper<T> {

    /**
     * <p>
     * 插入一条记录
     * </p>
     *
     * @param entity 实体对象
     */
    int insert(T entity);

    /**
     * <p>
     * 根据 ID 删除
     * </p>
     *
     * @param id 主键ID
     */
    int deleteById(Serializable id);

    /**
     * <p>
     * 根据 columnMap 条件,删除记录
     * </p>
     *
     * @param columnMap 表字段 map 对象
     */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * <p>
     * 根据 entity 条件,删除记录
     * </p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * <p>
     * 删除(根据ID 批量删除)
     * </p>
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * <p>
     * 根据 ID 修改
     * </p>
     *
     * @param entity 实体对象
     */
    int updateById(@Param(Constants.ENTITY) T entity);

    /**
     * <p>
     * 根据 whereEntity 条件,更新记录
     * </p>
     *
     * @param entity        实体对象 (set 条件值,不能为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /**
     * <p>
     * 根据 ID 查询
     * </p>
     *
     * @param id 主键ID
     */
    T selectById(Serializable id);

    /**
     * <p>
     * 查询(根据ID 批量查询)
     * </p>
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * <p>
     * 查询(根据 columnMap 条件)
     * </p>
     *
     * @param columnMap 表字段 map 对象
     */
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * <p>
     * 根据 entity 条件,查询一条记录
     * </p>
     *
     * @param queryWrapper 实体对象
     */
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * <p>
     * 根据 Wrapper 条件,查询总记录数
     * </p>
     *
     * @param queryWrapper 实体对象
     */
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * <p>
     * 根据 entity 条件,查询全部记录
     * </p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * <p>
     * 根据 Wrapper 条件,查询全部记录
     * </p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * <p>
     * 根据 Wrapper 条件,查询全部记录
     * 注意: 只返回第一个字段的值
     * </p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * <p>
     * 根据 entity 条件,查询全部记录(并翻页)
     * </p>
     *
     * @param page         分页查询条件(可以为 RowBounds.DEFAULT)
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * <p>
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     * </p>
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     */
    IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

2.2.4, common annotations in mybatis-plus

Insert picture description here
@TableName:对数据表名注解
@TableId:表主键标识
@TableId(value = "id", type = IdType.AUTO):自增
@TableId(value = "id", type = IdType.ID_WORKER_STR):分布式全局唯一ID字符串类型
@TableId(value = "id", type = IdType.INPUT):自行输入
@TableId(value = "id", type = IdType.ID_WORKER):分布式全局唯一ID 长整型类型
@TableId(value = "id", type = IdType.UUID):32位UUID字符串
@TableId(value = "id", type = IdType.NONE):无状态
@TableField:表字段标识
@TableField(exist = false):表示该属性不为数据库表字段,但又是必须使用的。
@TableField(exist = true):表示该属性为数据库表字段。
@TableField(condition = SqlCondition.LIKE):表示该属性可以模糊搜索。
@TableField(fill = FieldFill.INSERT):注解填充字段 ,生成器策略部分也可以配置!
@FieldStrategy:
@FieldFill
@Version:乐观锁注解、标记
@EnumValue:通枚举类注解
@TableLogic:表字段逻辑处理注解(逻辑删除)
@SqlParser:租户注解
@KeySequence:序列主键策略

For more, please see https://baomidou.com/guide/annotation.html#tablefield

3. Quick use

3.1 Introduce dependencies

<!--引入mybatis-plus-->
<!‐‐ https://mvnrepository.com/artifact/com.baomidou/mybatis‐plus ‐‐>
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis‐plus</artifactId>
	<version>3.3.1</version>
</dependency>
<!--引入mybatis-plus在spring boot-->
<!‐‐ https://mvnrepository.com/artifact/com.baomidou/mybatis‐plus‐boot‐starter ‐‐>
<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis‐plus‐boot‐starter</artifactId>
	<version>3.3.1</version>
</dependency>
<!--减少get/set方法,,构造器等-->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.18.12</version>
 </dependency>

Note: no need to reference the maven dependency of mybatis and mybatis-spring

3.2, create a database

CREATE TABLE `t_student` (
  `sid` int(10) NOT NULL AUTO_INCREMENT,
  `s_name` varchar(100) NOT NULL,
  `sage` int(3) DEFAULT NULL,
  `ssex` char(1) DEFAULT NULL,
  `sphone` char(11) DEFAULT NULL,
  PRIMARY KEY (`sid`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

INSERT INTO `t_student` (`sid`, `s_name`, `sage`, `ssex`, `sphone`) VALUES ('4', '张三', '18', '1', '12345678912');
INSERT INTO `t_student` (`sid`, `s_name`, `sage`, `ssex`, `sphone`) VALUES ('5', '李四', '20', '1', '12467897452');
INSERT INTO `t_student` (`sid`, `s_name`, `sage`, `ssex`, `sphone`) VALUES ('8', '小丽', '15', '2', '4678');
INSERT INTO `t_student` (`sid`, `s_name`, `sage`, `ssex`, `sphone`) VALUES ('9', '赵六六', '15', '1', '7897564');
INSERT INTO `t_student` (`sid`, `s_name`, `sage`, `ssex`, `sphone`) VALUES ('10', '小特', '50', '1', '4564654');
Insert picture description here

3.3, configure application.yml

# 设置开发环境
spring:
  profiles:
    active: dev
#数据库连接
  datasource:
    url: jdbc:mysql://localhost:3307/mybatisplus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    username: root
    password: 489773
# 配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 配置逻辑删除
  global-config:
    db-config:
      logic-delete-value: 1
      logic-not-delete-value: 0

3.4, create a pojo entity class

package com.zhz.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.extension.activerecord.Model;

import java.io.Serializable;
import java.util.Objects;

/**
 * @author zhz
 * @date 2020/03/24
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@TableName(value = "t_student")
public class Student{
  /*
  * @TableId:
  * value: 指定表中的主键列的列名, 如果实体属性名与列名一致,可以省略不指定.
  * type: 指定主键策略.
  */
    @TableId(type = IdType.AUTO)
    private Integer sid;
    @TableField("s_name")
    private String sname;
    private Integer sage;
    private String ssex;
    private String sphone;
}

3.5, add, delete, check and modify operations

Write the StudentMapper interface to inherit the BaseMapper interface

package com.zhz.mapper;

import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.pojo.Student;
/**
 * @author zhz
 *基于Mybatis‐plus实现: 让XxxMapper接口继承 BaseMapper接口即可.
 *BaseMapper<T> : 泛型指定的就是当前Mapper接口所操作的实体类类型
 */
@Mapper
public interface StudentMapperextends BaseMapper<Student> {
}

Prepare the test class (it is also possible to apply it directly to the usual development)

package com.zhz.test;
import	java.util.Map;
import java.util.ArrayList;
import	java.util.HashMap;

import com.zhz.mapper.StudentMapper;
import com.bjsxt.pojo.Student;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

/**
 * @author zhz
 * @date 2020/03/26
 * mybatisPlus基本增删查改
 **/
public class TestMybatisPlusBase {

    @Autowired
    private StudentMapper studentMapper;
    /**
     * 测试使用mp查询所有学生信息
     */
    @Test
    public void testSelAllStu() {
        //查询所有学生信息
        List<Student> students = studentMapper.selectList(null);
        //输出结果
        for (Student student:students) {
            System.out.println(student);
        }
    }
    /**
     * 测试使用Mp完成新增
     */
    @Test
    public void testIns() {
        //创建学生对象存储要新增的学生信息
        Student student = new Student();
        student.setSname("赵六六");
        student.setSage(15);
        student.setSsex("1");
        student.setSphone("7897564");
        //新增学生信息
        int insert = studentMapper.insert(student);
        //输出结果
        System.out.println("添加得数量:"+insert);
        System.out.println("主键:"+student.getSid());
    }
    /**
     * 测试使用Mp完成修改
     */
    @Test
    public void testUp(){
        //创建学生对象存储要修改的学生信息
        Student student = new Student();
        student.setSid(6);
        student.setSage(20);
        //修改学生信息
        int i = studentMapper.updateById(student);
        //输出结果
        System.out.println("修改的条数:"+i);
    }
    /**
     * 删除:通过ID删除
     */
    @Test
    public void testDelById(){
        //根据ID删除学生信息
        int i = studentMapper.deleteById(7);
        //输出结果
        System.out.println("删除的条数:"+i);
    }
    /**
     * 删除:指定条件删除数据(deleteByMap)
     */
    @Test
    public void testDelByMap(){
        Map<String,Object> map = new HashMap<>();
        map.put("s_name","小红");
        //指定条件删除学生信息
        int i = studentMapper.deleteByMap(map);
        System.out.println("删除的条数:"+i);
    }
    /**
     * 删除:多选删除
     */
    @Test
    public void testDelByIds(){
        List<Integer> list = new ArrayList<>();
        list.add(6);
        list.add(7);
        //删除符合Id要求的数据
        int i = studentMapper.deleteBatchIds(list);
        System.out.println("删除的条数:"+i);
    }

    /**
     * 查询:通过ID查询
     */
    @Test
    public void testSelById(){
        //根据ID查询学生信息
        Student student = studentMapper.selectById(4);
        //输出结果
        System.out.println(student);
    }

    /**
     * 查询:通过指定的条件完成查询
     */
    @Test
    public void testSelByMap(){
        Map<String, Object> map = new HashMap<> ();
        map.put("s_name","张三");
        //指定查询条件查询学生信息
        List<Student> list = studentMapper.selectByMap(map);
        //输出结果
        System.out.println(list);
    }
    /**
     * 查询:根据ID集合获取数据
     */
    @Test
    public void testSelectBatchIds(){
        List<Integer> list = new ArrayList<>();
        list.add(4);
        list.add(5);
        //指定查询条件查询学生信息
        List<Student> students = studentMapper.selectBatchIds(list);
        //输出结果
        System.out.println(students);
    }
}

4. Condition Constructor: Wrapper

structure:

Insert picture description here

4.1. Introduction of 7 Constructors

4.1.1, Wrapper

Conditionally construct an abstract class, the top parent class, 3 methods and other methods are provided in the abstract class
Insert picture description here

4.1.2, AbstractWrapper

Used to encapsulate query conditions, generate where conditions for sql, parent classes of QueryWrapper (LambdaQueryWrapper) and UpdateWrapper (LambdaUpdateWrapper) are used to generate where conditions for sql, and entity attributes are also used to generate where conditions for sql
Insert picture description here


Important methods:

Insert picture description here

4.1.3, AbstractLambdaWrapper

Lambda grammar uses Wrapper to uniformly process and parse lambda to obtain column.

4.1.4, LambdaQueryWrapper

Query wrapper used in Lambda syntax

4.1.5, LambdaUpdateWrapper

Lambda Update Package Wrapper

4.1.6, QueryWrapper

The Entity object encapsulates the operation class, instead of using lambda syntax, its own internal attribute entity is also used to generate where conditions
Insert picture description here
select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
/*
例: select("id", "name", "age")
例: select(i ‐> i.getProperty().startsWith("zhz"))
*/

4.1.7, UpdateWrapper

Update condition encapsulation, used for Entity object update operation
Insert picture description here


set method

set(String column, Object val)
set(boolean condition, String column, Object val)
/*
SQL SET 字段
例: set("name", "zhz")
例: set("name", "")‐‐‐>数据库字段值变为空字符串
例: set("name", null)‐‐‐>数据库字段值变为null
说明:boolean condition为控制该字段是否拼接到最终的sql语句中
*/

setSql method

setSql(String sql)
/*
设置 SET 部分 SQL
例: setSql("name = '老李头'")
*/

4.2, CURD with conditions

4.2.1, query with conditions

// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

4.2.2, conditional update

@Test
void update() {
	UpdateWrapper<Student> updateWrapper=new UpdateWrapper<Student>();
	updateWrapper.eq("s_name", "张三").eq("sage", 18).set("id", 100);
	empolyeeMapper.update(student, updateWrapper);
}

4.2.3, conditional deletion

// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

4.3, wrapper query example

package com.zhz;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zhz.mapper.StudentMapper;
import com.zhz.pojo.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;
import java.util.Map;

@SpringBootTest
public class WrapperTest {

    @Autowired
    private StudentMapper studentMapper;

    @Test
    void contextLoads() {
        // 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12
        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        wrapper
                .isNotNull("s_name")
                .ge("sage",12);
        userMapper.selectList(wrapper).forEach(System.out::println);
    }

    @Test
    void test2(){
        // 查询名字zhz
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("s_name","zhz");
        User user = userMapper.selectOne(wrapper); // 查询一个数据,出现多个结果使用List 或者 Map
        System.out.println(user);
    }

    @Test
    void test3(){
        // 查询年龄在 20 ~ 30 岁之间的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.between("sage",20,30); // 区间
        Integer count = userMapper.selectCount(wrapper);// 查询结果数
        System.out.println(count);
    }

    // 模糊查询
    @Test
    void test4(){
        // 查询年龄在 20 ~ 30 岁之间的用户
        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        // 左和右  t%
        wrapper
                .notLike("s_name","e")
                .likeRight("sphone","131");
        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
        maps.forEach(System.out::println);
    }

    // 模糊查询
    @Test
    void test5(){

        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // id 在子查询中查出来
        wrapper.inSql("sid","select sid from student where sid<3");

        List<Object> objects = userMapper.selectObjs(wrapper);
        objects.forEach(System.out::println);
    }

    //测试六
    @Test
    void test6(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 通过id进行排序
        wrapper.orderByAsc("sid");
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
}

5. Expansion

5.1, global ID generation strategy

After configuration, there is no need to configure on the primary key of the entity class

mybatis-plus:
  global‐config:
    db‐config:
      id‐type: auto

5.2, logical deletion

Physical deletion: directly remove the data from the database when deleting. DELTE
logical deletion: control the deletion from the logical level. Usually a logical deletion field such as enabled and is_delete is added to the table. The data is valid by default (the value is 1), When the user deletes the data, modify the data to UPDATE 0, and only check where enabled=1 when querying.

  1. Need to add tombstone fields
  2. For partial single table logical deletion, the corresponding logical deletion identification field needs to be added to the corresponding pojo class
@TableLogic // 代表逻辑删除(单个字段的)
private Integer flag;

Turn onGlobal tombstoneConfiguration, if the global logic deletion configuration is performed and specified, it is not necessary to configure @TableLogic in each entity class

mybatis-plus:
  global-config:
    db-config:
      logic-not-delete-value: # 逻辑未删除值(默认为 0)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic‐delete‐field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置@TableLogic)

Database configuration:

Insert picture description here


when the deletion is executed, the tombstone field will be modified

@Test
void logicDel(){
	studentService.removeById(1);
}

The executed sql statement is

update t_student set flag=0 where sid=? and flag=1 

When the query is executed. Will automatically query valid data where flag=1

@Test
void logicList(){
	studentService.list();
}

sql is:

select sid,s_name,sage,ssex,sphone,flag from s_student where flag=1

5.3. Perform SQL analysis and print (good stuff)

5.3.1, dependency

<!-- https://mvnrepository.com/artifact/p6spy/p6spy -->
<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version>
</dependency>

5.3.2, yml configuration

spring:
  datasource:
    url: jdbc:p6spy:mysql://localhost:3307/mybatisplus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
	driver-class-name: com.p6spy.engine.spy.P6SpyDriver

5.3.3, add p6spy: spy.properties

#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,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

5.3.4, SQL log beautification plug-in:

Insert picture description here


Insert picture description here


address:

5.3.5, data security protection

Prevent deleting libraries and running away

5.3.5.1 Get a 16-bit random key

@Test
void test(){// 生成 16 位随机 AES 密钥
	String randomKey = AES.generateRandomKey();
	System.out.println(randomKey);
}

da12166c7db8a58f

5.3.5.2, encrypt database connection information according to the secret key

@Test
void test(){
	String url = AES.encrypt("jdbc:mysql://localhost:3307/mybatisplus?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&" , "da12166c7db8a58f");
	String uname = AES.encrypt("root" , "da12166c7db8a58f");
	String pwd = AES.encrypt("489773" , "da12166c7db8a58f");
	System.out.println(url);
	System.out.println(uname);
	System.out.println(pwd);
}

5.3.5.3, modify the configuration file, pay attention to the beginning of mpw: (contact above)

spring:
  datasource:
    url: mpw:上面得到的url的加密密码
    username: mpw:上面得到的username的加密密码
    password: mpw:上面得到的password的加密密码
	driver-class-name: com.p6spy.engine.spy.P6SpyDriver

5.3.5.4. Decryption is required during deployment

java ‐jar xxxx.jar ‐‐mpw.key=你的16位随机秘钥, 越少人知道越好(5.3.5.1得到的随机码)

5.4 Use of optimistic lock plugin

5.4.1. Introduction

悲观锁:
  1. Pessimistic lock, as its name suggests, has strong exclusive and exclusive characteristics. It refers to a conservative attitude towards data being modified by the outside world (including other current affairs of the system, and transaction processing from external systems). Therefore, in the entire data processing process, the data is in a locked state.
  2. Assuming that the amount of functional concurrency is very large, you need to use synchronized to deal with thread insecurity under high concurrency, which will cause other threads to wait and affect system throughput
乐观锁:
  1. Optimistic locking is relative to pessimistic locking. Optimistic locking assumes that data will not cause conflicts under normal circumstances. Therefore, when the data is submitted and updated, it will formally detect whether the data conflicts or not. If a conflict is found, return Give the user wrong information and let the user decide what to do. 读多写少The scenario where optimistic locking is applicable , which can improve the throughput of the program.
  2. Assuming that the function has very little chance of concurrency, the optimistic lock version mechanism is used for comparison, and if there is a conflict, the user will be returned with wrong information

5.4.2 Why do we need locks (concurrency control)

In a multi-user environment, multiple users may update the same record at the same time, which can cause conflicts. This is the famous concurrency problem

  • Lost update: The update of one transaction overwrites the update result of other transactions, which is the so-called update loss. For example: User 1 changes the value from 500 to 8000, and user B changes the value from 500 to 200, then multiple people submit the same record at the same time, and the later submission will overwrite the previous submission data.
  • Dirty reads: Dirty reads occur when a transaction reads the records of other half-completed transactions. For example: the values ​​seen by users A and B are both 500, user B changes the value to 200, and the value read by user A is still 500.

A solution to a problem is born to solve a problem. What problem does it solve? It is mainly to solve the problem of missing updates as shown in the figure below.

Insert picture description here


In order to solve these problems caused by concurrency. We need to introduce a concurrency control mechanism.

5.4.3, MybatisPlus uses optimistic lock

Database level: Need to add a "version" field to control the script

Insert picture description here


Code level:
1. Add the version field to the corresponding entity class and set it as follows

@Version //这就是控制版本的
@TableField(fill = FieldFill.INSERT) //这个方便在添加的时候设置版本初始为1
private Integer version; //版本的字段

Create a new class, realize the automatic filling of MetaObjectHandler, like creation time, update time can also be operated here.

@Component
public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
	@Override
	public void insertFill(MetaObject metaObject) {
	//这里的“version”就是指定的字段,设置初始值为1,之后每修改一次+1 
		this.setFieldValByName("version",1,metaObject);
	}
	@Override
	public void updateFill(MetaObject metaObject) {
	}
}

When creating a configuration class, open an optimistic lock plugin

@Configuration
@MapperScan("com.zhz.mapper")//这里就是你的mapper文件的包
public class MyBatisConfig {
	//乐观锁插件
	@Bean
	public OptimisticLockerInterceptor optimisticLockerInterceptor(){
		return new OptimisticLockerInterceptor();
	}
}

Next, when adding data, just call the insert method.
When making changes, we need to check people before making changes, because in order to prevent problems, we need to check the version number before proceeding with follow-up operations! !

5.5, code generator

package com.zhz.testcode;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.LikeTable;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/***
 * @Author zhz
 * @Slogan 致敬大师,致敬未来的你
 */
public class GeneratorApp {

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        // 判断用户是否输入
        if (scanner.hasNext()) {
            // 拿到输入内容
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {

        String moduleName = scanner("模块名");
        String tableName = scanner("表名(多个用,号分隔,或者按前缀(pms*))");
        String prefixName = scanner("需要替换的表前缀");


        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        // 获得当前项目的路径
        String projectPath = System.getProperty("user.dir")+"/05_generator";
        // 设置生成路径
        gc.setOutputDir(projectPath + "/src/main/java");
        // 作者
        gc.setAuthor("zhz");
        // 代码生成是不是要打开所在文件夹
        gc.setOpen(false);
        // 生成Swagger2注解
        gc.setSwagger2(true);
        // 会在mapper.xml 生成一个基础的<ResultMap> 映射所有的字段
        gc.setBaseResultMap(true);
        // 同文件生成覆盖
        gc.setFileOverride(true);
        //gc.setDateType(DateType.ONLY_DATE)
        // 实体名:直接用表名  %s=表名
        gc.setEntityName("%s");
        // mapper接口名
        gc.setMapperName("%sMapper");
        // mapper.xml 文件名
        gc.setXmlName("%sMapper");
        // 业务逻辑类接口名
        gc.setServiceName("%sService");
        // 业务逻辑类实现类名
        gc.setServiceName("%sImplService");
        // 将全局配置设置到AutoGenerator
        mpg.setGlobalConfig(gc);



        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3307/mybatisplus?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("489773");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        //  模块名
        pc.setModuleName(moduleName);
        // 包名
        pc.setParent("com.zhz");
        // 完整的报名: com.zhz.pms
        mpg.setPackageInfo(pc);



        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 velocity
        String templatePath = "/templates/mapper.xml.vm";
        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });

        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        // 把已有的xml生成置空
        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        // 表名的生成策略:下划线转驼峰 pms_product -- PmsProduct
        strategy.setNaming(NamingStrategy.underline_to_camel);
        // 列名的生成策略:下划线转驼峰 last_name -- lastName
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);
        // 在controller类上是否生成@RestController
        strategy.setRestControllerStyle(true);
        // 公共父类
        //strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");

        if(tableName.indexOf('*')>0){
            // 按前缀生成表
            strategy.setLikeTable(new LikeTable(tableName.replace('*','_')));
        }
        else{
            // 要生成的表名 多个用逗号分隔
             strategy.setInclude(tableName);
        }
        // 设置表替换前缀
        strategy.setTablePrefix(prefixName);
        // 驼峰转连字符 比如 pms_product --> controller @RequestMapping("/pms/pmsProduct")
        //strategy.setControllerMappingHyphenStyle(true);
        mpg.setStrategy(strategy);

        // 进行生成
        mpg.execute();

    }
}

6. Introduce a very useful idea plugin

MybatisCodeHelperNew

Download and use it on Baidu by yourself, you can quickly omit the code generation operation of mybatisplus, he can directly manipulate the database and understand it by himself

The following is my official account: (If you are interested, you can scan it, and the article will be synchronized)

Insert picture description here
I am the younger brother of Xiaobai, a Xiaobai in the Internet industry, who aspires to become an architect
https://blog.csdn.net/zhouhengzhe?t=1