您现在的位置是:首页 >技术教程 >MybatisPlus网站首页技术教程

MybatisPlus

养一只摆烂猫. 2024-06-17 10:47:00
简介MybatisPlus

目录

MybatisPlus简介

入门案例

MybatisPlus概述

标准数据层开发

标准数据层CRUD功能

分页功能

DQL控制

条件查询方式

查询投影

查询条件设定

字段映射与表名映射

DML控制

Insert

Delete

乐观锁

快速开发

代码生成器原理分析


MybatisPlus简介

入门案例

MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率。
开发方式
基于MyBatis使用MyBatisPlus
基于Spring使用MyBatisPlus
基于SpringBoot使用MyBatisPlus

步骤1:创建数据库及表

create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user
(
    id       bigint(20) primary key auto_increment, 
    name     varchar(32) not null,
    password varchar(32) not null, 
    age      int(3)      not null,
    tel      varchar(32) not null 
);
insert into user
values (1, 'Tom', 'tom', 3, '18866668888');
insert into user
values (2, 'Jerry', 'jerry', 4, '16688886666');
insert into user
values (3, 'Jock', '123456', 41, '18812345678');

步骤2:创建SpringBoot工程

步骤3:勾选配置使用技术

说明:
由于MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动在pom.xml中配置添加

 步骤4:pom.xml补全依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>
说明:
druid数据源可以加也可以不加,SpringBoot有内置的数据源,可以配置成使用Druid数据源
从MP的依赖关系可以看出,通过依赖传递已经将MyBatis与MyBatis整合Spring的jar包导入,
不需要额外在添加MyBatis的相关jar包

 步骤5:添加MP的相关配置信息

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus_db
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource

步骤6:根据数据库表创建实体类

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

 步骤7:创建Dao接口

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

步骤8:编写引导类

@SpringBootApplication
//@MapperScan("com.green.dao")
public class Mybatisplus01QuickstartApplication {
    public static void main(String[] args) {
        SpringApplication.run(Mybatisplus01QuickstartApplication.class, args);
    }
}
说明:Dao接口要想被容器扫描到,有两种解决方案:
方案一:在Dao接口上添加 @Mapper 注解,并且确保Dao处在引导类所在包或其子包中
该方案的缺点是需要在每一Dao接口中添加注解
方案二:在引导类上添加 @MapperScan 注解,其属性为所要扫描的Dao所在包
该方案的好处是只需要写一次,则指定包下的所有Dao接口都能被扫描到, @Mapper 就可以不写。

 步骤9:编写测试类

@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll() {
        List<User> userList = userDao.selectList(null);
        System.out.println(userList);
    }
}
说明:
userDao注入的时候下面有红线提示的原因是什么?
    UserDao是一个接口,不能实例化对象
    只有在服务器启动IOC容器初始化后,由框架创建DAO接口的代理对象来注入
    现在服务器并未启动,所以代理对象也未创建,IDEA查找不到对应的对象注入,
    所以提示报红一旦服务启动,就能注入其代理对象,所以该错误提示不影响正常运行。

MybatisPlus概述

MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
MyBatisPlus的官网为: https://mp.baomidou.com/

 说明:

MP的特性:
无侵入:只做增强不做改变,不会对现有工程产生影响
强大的 CRUD  操作:内置通用 Mapper,少量配置即可实现单表CRUD  操作
支持 Lambda:编写查询条件无需担心字段写错
支持主键自动生成
内置分页插件
..

标准数据层开发

标准数据层CRUD功能

 新增

int insert (T t)
T:泛型,新增用来保存新增数据
int:返回值,新增成功后返回1,没有新增成功返回的是0

删除

int deleteById (Serializable id)
Serializable:参数类型
int:返回值类型,数据删除成功返回1,未删除数据返回0。
    思考:参数类型为什么是一个序列化类?

 

从这张图可以看出,
String和Number是Serializable的子类,
Number又是Float,Double,Integer等类的父类,
能作为主键的数据类型都已经是Serializable的子类,
MP使用Serializable作为参数类型,就好比可以用Object接收任何数据类型一样。

 修改

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

根据ID查询

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

 查询所有

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

@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {

    @Autowired
    private UserDao userDao;


    //添加
    @Test
    void testAdd(){
        User user = new User();
        user.setName("张三");
        user.setPassword("123");
        user.setAge(23);
        user.setTel("1233456677");
        userDao.insert(user);
    }

    //删除
    @Test
    void testDelete(){
        userDao.deleteById(1658485544068743170L);
    }

    //修改
    @Test
    void testUpdate(){
        User user = new User();
        user.setId(1L);
        user.setName("Tom888");
        user.setPassword("Tom888");
        //提供什么字段修改什么字段
        userDao.updateById(user);
    }

    //查询单个数据
    @Test
    void testGetById(){
        User user = userDao.selectById(2L);
        System.out.println(user);
    }

    //查询所有
    @Test
    void testGetAll() {
        List<User> userList = userDao.selectList(null);
        System.out.println(userList);
    }
}

Lombok

Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。

使用步骤

步骤1:添加lombok依赖 

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <!--<version>1.18.12</version>-->
</dependency>
注意:版本可以不用写,因为SpringBoot中已经管理了lombok的版本。

 步骤2:安装Lombok的插件

如果在IDEA中找不到lombok插件,可以访问如下网站
https://plugins.jetbrains.com/plugin/6317-lombok/versions
根据自己IDEA的版本下载对应的lombok插件,下载成功后,在IDEA中采用离线安装的方式进行安装。

 

 步骤3:型类上添加注解

Lombok常见的注解有:
    @Setter:为模型类的属性提供setter方法
    @Getter:为模型类的属性提供getter方法
    @ToString:为模型类的属性提供toString方法
    @EqualsAndHashCode:为模型类的属性提供equals和hashcode方法
    @Data:是个组合注解,包含上面的注解的功能
    @NoArgsConstructor:提供一个无参构造函数
    @AllArgsConstructor:提供一个包含所有参数的构造函数

@Data 
@AllArgsConstructor 
@NoArgsConstructor 
public class User {
    private Long id; 
    private String name;
    private String password; 
    private Integer age; 
    private String tel;
}

分页功能

IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
IPage:用来构建分页查询条件
Wrapper:用来构建条件查询的条件,目前没有可直接传为Null 
IPage:返回值,你会发现构建分页条件和方法的返回值都是IPage
IPage是一个接口,需要找到它的实现类来构建它,具体的实现类,
可以进入到IPage类中按ctrl+h,会找到其有一个实现类为Page 。

步骤1:调用方法传入参数获取返回值

@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
    @Autowired
    private UserDao userDao;

    //分页查询
    @Test
    void testSelectPage(){
    //1  创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
    IPage<User> page=new Page<>(1,3);
    //2 执行分页查询
    userDao.selectPage(page,null);
    //3 获取分页结果
    System.out.println("当前页码值:"+page.getCurrent()); 
    System.out.println("每页显示数:"+page.getSize()); 
    System.out.println("一共多少页:"+page.getPages()); 
    System.out.println("一共多少条数据:"+page.getTotal()); 
    System.out.println("数据:"+page.getRecords());
    }
}

 步骤2:设置分页拦截器

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
    //1 创建MybatisPlusInterceptor拦截器对象
    MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
    //2 添加分页拦截器
    mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 
    return mpInterceptor;
    }
}
说明:上面的代码记不住咋办呢?
这些内容在MP的官方文档中有详细的说明,可以查看官方文档类配置

 步骤3:运行测试程序

如果想查看MP执行的SQL语句,可以修改application.yml配置文件
#开启mp的日志(输出到控制台)
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
打开日志后,就可以在控制台打印出对应的SQL语句,
开启日志功能性能就会受到影响,调试完后记得关闭。

DQL控制

条件查询方式

MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。
一个 Wrapper 类,这个类就是用来构建查询条件的,如下图所示:

 环境构建

创建一个SpringBoot项目
pom.xml中添加对应的依赖
编写UserDao接口
@Mapper
public interface UserDao extends BaseMapper<User> { }
编写模型类
@Data
public class User { 
    private Long id; 
    private String name;
    private String password; 
    private Integer age; 
    private String tel;
}
编写引导类
@SpringBootApplication
public class Mybatisplus02DqlApplication {
    public static void main(String[] args) {
        SpringApplication.run(Mybatisplus02DqlApplication.class, args);
    }
}
编写配置文件
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus_db
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
  main:
    banner-mode: off

#开启mp的日志(输出到控制台)
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    banner: false

编写测试类
@SpringBootTest
class Mybatisplus02DqlApplicationTests {

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

 

测试的时候,控制台打印的日志比较多,速度有点慢而且不利于查看运行结果,所以把这个日志处理下:
取消初始化spring日志打印,resources目录下添加logback.xml,名称固定,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
取消MybatisPlus启动banner图标
application.yml添加如下内容:
# mybatis-plus日志控制台输出
mybatis-plus:
    configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    global-config:
        banner: off # 关闭mybatisplus启动图标

取消SpringBoot的log打印
spring:
    main:
        banner-mode: off # 关闭SpringBoot启动图标(banner)

 构建条件查询


@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    @Autowired
    private UserDao userDao;

    //查询所有
    @Test
    void testGetAll() {
//        //方式一:按条件查询
//        QueryWrapper qw = new QueryWrapper();
//        qw.lt("age",18);
//        List<User> userList = userDao.selectList(qw);
//        System.out.println(userList);

//        //方式二:Lambda格式按条件查询
//        QueryWrapper<User> qw = new QueryWrapper<User>();
//        qw.lambda().lt(User::getAge,18);
//        List<User> userList = userDao.selectList(qw);
//        System.out.println(userList);

        //方式三:Lambda格式按条件查询
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
//        lqw.lt(User::getAge, 30);
//        lqw.gt(User::getAge, 10);
        //链式编程
        //lqw.lt(User::getAge, 30).gt(User::getAge, 10);//10-30之间
        //小于10或者大于30
        lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}

 null判定

在做条件查询的时候,一般会有很多条件可以供用户进行选择查询。
这些条件用户可以选择使用也可以选择不使用,比如要查询价格在8000以上的手机
在输入条件的时候,价格有一个区间范围,按照需求只需要在第一个价格输入框中输入8000
后台在做价格查询的时候,一般会让 price>值1  and  price  <值2
因为前端没有输入值2,所以如果不处理的话,就会出现 price>8000  and  price  <  null问题
这个时候查询的结果就会出问题,具体该如何解决?

 

需求:
查询数据库表中,根据输入年龄范围来查询符合条件的记录
用户在输入值的时候,
如果只输入第一个框,说明要查询大于该年龄的用户
如果只输入第二个框,说明要查询小于该年龄的用户
如果两个框都输入了,说明要查询年龄在两个范围之间的用户
思考第一个问题:后台如果想接收前端的两个数据,该如何接收?
可以使用两个简单数据类型,也可以使用一个模型类,但是User类中目前只有一个age属性,如:
@Data
public class User { 
    private Long id; 
    private String name;
    private String password; 
    private Integer age; 
    private String tel;
}
使用一个age属性,如何去接收页面上的两个值呢?
方案一:添加属性age2,这种做法可以但是会影响到原模型类的属性内容
@Data
public class User { 
    private Long id; 
    private String name;
    private String password; 
    private Integer age; 
    private String tel;
    private Integer age2;
}
方案二:新建一个模型类,让其继承User类,并在其中添加age2属性,
UserQuery在拥有User属性后同时添加了age2属性。
@Data
public class UserQuery extends User { 
    private Integer age2;
}
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    @Autowired
    private UserDao userDao;

    //查询所有
    @Test
    void testGetAll() {

        //模拟页面传递过来的查询数据
        UserQuery uq = new UserQuery();
        uq.setAge(10);
//      uq.setAge2(30);

        //null判定
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.lt(null != uq.getAge2(), User::getAge, uq.getAge2());
        lqw.gt(null != uq.getAge(), User::getAge, uq.getAge());
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}

lt()方法
condition为boolean类型,返回true,则添加条件,返回false则不添加条件

查询投影

查询指定字段

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.select(User::getId,User::getName,User::getAge);
        List<User> userList = userDao.selectList(lqw); 
        System.out.println(userList);
    }
}
select(...)方法用来设置查询的字段列,可以设置多个,最终的sql语句为:
SELECT  id,name,age  FROM  user
如果使用的不是lambda,就需要手动指定字段
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        lqw.select("id","name","age","tel");
        List<User> userList = userDao.selectList(lqw); 
        System.out.println(userList);
    }
}
最终的sql语句为:SELECT  id,name,age,tel  FROM  user

聚合查询

需求:聚合函数查询,完成count、max、min、avg、sum的使用
count:总记录数
max:最大值
min:最小值
avg:平均值
sum:求和
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    @Autowired
    private UserDao userDao;

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

 分组查询

需求:分组查询,完成 group  by的查询使用
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll(){
        QueryWrapper<User> lqw = new QueryWrapper<User>(); 
        lqw.select("count(*) as count,tel"); lqw.groupBy("tel");
        List<Map<String, Object>> list = userDao.selectMaps(lqw); 
        System.out.println(list);
    }
}
groupBy为分组,最终的sql语句为
SELECT count(*) as count,tel FROM user GROUP BY tel
注意:
聚合与分组查询,无法使用lambda表达式来完成
MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实现

查询条件设定

  查询条件

MP的查询条件有很多:
范围匹配(>  、 =  、between)
模糊匹配(like)
空判定(null)
包含性匹配(in)
分组(group)
排序(order)
..

等值查询

需求:根据用户名和密码查询用户信息
@SpringBootTest
class Mybatisplus02DqlApplicationTests {

    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); 
        lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry"); 
        User loginUser = userDao.selectOne(lqw); 
        System.out.println(loginUser);
    }
}
eq(): 相当于 = ,对应的sql语句为
SELECT  id,name,password,age,tel  FROM  user  WHERE  (name  =  ?  AND  password  =  ?)
selectList:查询结果为多个或者单个
selectOne:查询结果为单个

范围查询

需求:对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    @Autowired
    private UserDao userDao;

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

 模糊查询

需求:查询表中name属性的值以J 开头的用户信息,使用like进行模糊查询
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); 
        lqw.likeLeft(User::getName, "J");
        //SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?) 
        List<User> userList = userDao.selectList(lqw); 
        System.out.println(userList);
    }
}
like():前后加百分号,如 %J% 
likeLeft():前面加百分号,如 %J 
likeRight():后面加百分号,如 J%

 排序查询

需求:查询所有数据,然后按照id降序
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll(){
        LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
        /**
        * condition :条件,返回boolean,
        * 当condition为true,进行排序,如果为false,则不排序
        * isAsc:是否为升序,true为升序,false为降序
        *  columns:需要操作的列
        */
        lwq.orderBy(true,false, User::getId);
        userDao.selectList(lwq)
    }
}
orderBy排序
    condition:条件,true则添加排序,false则不添加排序
    isAsc:是否为升序,true升序,false降序
    columns:排序字段,可以有多个
orderByAsc/Desc(单个column):按照指定字段进行升序/降序
orderByAsc/Desc(多个column):按照多个字段进行升序/降序
orderByAsc/Desc
    condition:条件,true添加排序,false不添加排序
    多个columns:按照多个字段进行排序

除了上面的这几种查询条件构建方法以外还会有很多其他的方法,
比如isNull,isNotNull,in,notIn等等方法可供选择,具体参考官方文档的条件构造器来学习使用
https://mp.baomidou.com/guide/wrapper.html#abstractwrapper

字段映射与表名映射

问题1:表字段与编码属性设计不同步

当表的列名和模型类的属性名发生不一致,就会导致数据封装不到模型对象,
这个时候就需要其中一方做出修改,那如果前提是两边都不能改又该如何解决?
MP提供了一个注解@TableField ,使用该注解可以实现模型类属性名和表的列名之间的映射关系

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

当模型类中多了一个数据库表不存在的字段,
就会导致生成的sql语句中在select的时候查询了数据库不存在的字段

 问题3:采用默认查询开放了更多的字段查看权限

查询表中所有的列的数据,就可能把一些敏感数据查询到返回给前端,
这个时候就需要限制哪些字段默认不要进行查询
@TableField 注解的一个属性叫select ,该属性设置默认是否需要查询该字段的值,
true(默认值)表示默认查询该字段,false表示默认不查询该字段

 

名称

@TableField

类型

属性注解

位置

模型类属性定义上方

作用

设置当前属性对应的数据库表中的字段关系

相关属性

value(默认):设置数据库表字段名称

exist:设置属性在数据库表字段中是否存在,默认为true,此属性不能与value合并
使用

select:设置属性是否参与查询,此属性与select()映射配置不冲突

 问题4:表名与编码开发设计不同步

该问题主要是表的名称和模型类的名称不一致,导致查询失败
解决方案是使用MP提供的另外一个注解 @TableName 来设置表与模型类之间的对应关系。

 

 

名称

@TableName

类型

类注解

位置

模型类定义上方

作用

设置当前类对应于数据库表关系

相关属性

value(默认):设置数据库表名称

DML控制

Insert

id生成策略控制

不同的表应用不同的id生成策略
    日志:自增(1,2,3,4,..)
    购物订单:特殊规则(FQ23948AK3843)
    外卖单:关联地区日期等信息(10  04  20200314  34  91)
    关系表:可省略id
    ...

名称

@TableId

类型

属性注解

位置

模型类中用于表示主键的属性定义上方

作用

置当前类中主键属性的生成策略

相关属性

value(默认):设置数据库表主键名称

type:设置主键属性的生成策略,值查照IdType的枚举值

环境构建

创建一个SpringBoot项目
pom.xml中添加对应的依赖
编写UserDao接口
@Mapper
public interface UserDao extends BaseMapper<User> { }

编写模型类
@Data 
@TableName("tbl_user") 
public class User {
    private Long id; 
    private String name;
    @TableField(value="pwd",select=false) 
    private String password;
    private Integer age; 
    private String tel; 
    @TableField(exist=false) 
    private Integer online;
}

编写引导类
@SpringBootApplication
public class Mybatisplus03DmlApplication {
    public static void main(String[] args) {
        SpringApplication.run(Mybatisplus03DmlApplication.class, args);
    }
}

编写配置文件
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus_db
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
  main:
    banner-mode: off

#开启mp的日志(输出到控制台)
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    banner: false

编写测试类
@SpringBootTest
class Mybatisplus03DmlApplicationTests {

    @Autowired
    private UserDao userDao;

    //添加
    @Test
    void testAdd() {
        User user = new User();
        user.setName("张三");
        user.setPassword("123");
        user.setAge(23);
        user.setTel("1233456677");
        userDao.insert(user);
    }

    //删除
    @Test
    void testDelete() {
        userDao.deleteById(1658485544068743170L);
    }

    //修改
    @Test
    void testUpdate() {
        User user = new User();
        user.setId(1L);
        user.setName("Tom888");
        user.setPassword("Tom888");
        //提供什么字段修改什么字段
        userDao.updateById(user);
    }
}

 

 代码演示

步骤1:设置生成策略为AUTO

@Data
//@TableName("tbl_user")
public class User {
    //修改id生成策略
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField(value="pwd",select=false)//查询时隐藏字段
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)//数据库不存在字段
    private Integer online;

}
AUTO 的作用是使用数据库ID自增,在使用该策略的时候一定要确保对应的数据库表设置了ID主键自增,
否则无效。

 

从源码中可以看到,除了AUTO这个策略以外,还有如下几种生成策略:
NONE:  不设置id生成策略
INPUT:用户手工输入id
ASSIGN_ID:雪花算法生成id(可兼容数值型与字符串型) 
ASSIGN_UUID:以UUID生成算法作为id生成策略
其他的几个策略均已过时,都将被ASSIGN_ID和ASSIGN_UUID代替掉。
拓展
分布式ID是什么?
当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储
比如订单表就有可能被存储在不同的服务器上
如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突
这个时候就需要一个全局唯一ID,这个ID就是分布式ID。

 雪花算法

雪花算法(SnowFlake),是Twitter官方给出的算法实现 是用Scala写的。
其生成的结果是一个
64bit大小整数,它的结构如下图:

1.  1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。
生成的id一般都是用整数,所以最高位固定为0。
2.  41bit-时间戳,用来记录时间戳,毫秒级
3.  10bit-工作机器id,用来记录工作机器id,其中高位5bit是数据中心ID其取值范围0-31,
低位5bit是工作节点ID其取值范围0-31,两个组合起来最多可以容纳1024个节点
4.  序列号占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID

 简化配置

模型类主键策略设置

项目中的每一个模型类上都需要使用相同的生成策略,稍微有点繁琐,在配置文件中进行配置
就能让所有的模型类都可以使用该主键ID策略
mybatis-plus: 
    global-config:
        db-config:
        id-type: auto #全局id生成策略
配置完成后,每个模型类的主键ID策略都将成为auto 

数据库表与模型类的映射关系

MP会默认将模型类的类名名首字母小写作为表名使用,假如数据库表的名称都以 tbl_ 开头
就需要将所有的模型类上添加 @TableName 

 

配置起来还是比较繁琐,简化方式为在配置文件中配置如下内容:
mybatis-plus: 
    global-config:
        db-config:
            table-prefix: tbl_

Delete

多记录操作

之前添加了很多商品到购物车,过了几天发现这些东西又不想要了,该怎么办呢?
很简单删除掉,但是一个个删除的话还是比较慢和费事的,所以一般会给用户一个批量操作,
也就是前面有一个复选框,用户一次可以勾选多个也可以进行全选,
然后删一次就可以将购物车清空,这个就需要用到 批量删除 的操作。
int deleteBatchIds(@Param(Constants.COLLECTION) 
Collection<? extends Serializable> idList);
翻译方法的字面意思为:删除(根据ID  批量删除),参数是一个集合,可以存放多个id值。
需求:根据传入的id集合将数据库表中的数据删除掉。
@SpringBootTest
class Mybatisplus03DmlApplicationTests {

    @Autowired
    private UserDao userDao;

    //删除
    @Test
    void testDelete() {
        List<Long> list = new ArrayList<>();
        list.add(5L);
        list.add(6L);
        list.add(7L);
        //删除多条数据
        userDao.deleteBatchIds(list);
    }
}
List<T> selectBatchIds(@Param(Constants.COLLECTION) 
Collection<? extends Serializable> idList);
方法名称翻译为:查询(根据ID  批量查询),参数是一个集合,可以存放多个id值。
需求:根据传入的ID集合查询用户信息
//查询指定多条数据
List<Long> list = new ArrayList<>(); 
list.add(1L);
list.add(3L); 
list.add(4L);
userDao.selectBatchIds(list); 
查询结果就会按照指定传入的id值进行查询

 逻辑删除

这是一个员工和其所签的合同表,关系是一个员工可以签多个合同,是一个一(员工)对多(合同)的表
员工ID为1的张业绩,总共签了三个合同,如果此时他离职了,
需要将员工表中的数据进行删除,会执行delete操作
如果表在设计的时候有主外键关系,那么同时也得将合同表中的前三条数据也删除掉

 

后期要统计所签合同的总金额,就会发现对不上,原因是已经将员工1签的合同信息删除掉了
如果只删除员工不删除合同表数据,那么合同的员工编号对应的员工信息不存在,
那么就会出现垃圾数据,就会出现无主合同,根本不知道有张业绩这个人的存在
所以经过分析,不应该将表中的数据删除掉,而是需要进行保留,
但是又得把离职的人和在职的人进行区分,这样就解决了上述问题,如:
区分的方式,就是在员工表中添加一列数据deleted ,
如果为0说明在职员工,如果离职则将其改完1,(0和1所代表的含义是可以自定义的)

 

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

 步骤1:修改数据库表添加deleted

步骤2:实体类添加属性

@Data
//@TableName("tbl_user")    可以不写是因为配置了全局配置
public class User {
    //修改id生成策略  已写全局配置
//    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField(value="pwd",select=false)//查询时隐藏字段
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)//数据库不存在字段
    private Integer online;
    //逻辑删除字段
    @TableLogic(value = "0",delval = "1")
    private Integer deleted;
}

步骤3:运行删除方法

@SpringBootTest
class Mybatisplus03DqlApplicationTests {

    @Autowired
    private UserDao userDao;

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

 

从测试结果来看,逻辑删除最后走的是update操作,会将指定的字段修改成删除状态对应的值。
思考
逻辑删除,对查询有没有影响呢?
执行查询操作
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testFind(){
        System.out.println(userDao.selectList(null)); 
    }
}

 

如果每个表都要有逻辑删除,那么就需要在每个模型类的属性上添加 @TableLogic 注解,如何优化?
在配置文件中添加全局配置,如下:
mybatis-plus: global-config:
    db-config:
        #  逻辑删除字段名
        logic-delete-field: deleted 
        #  逻辑删除字面值:未删除为0 
        logic-not-delete-value: 0
        #  逻辑删除字面值:删除为1 
        logic-delete-value: 1

 逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。

执行的SQL语句为:

UPDATE  tbl_user  SET  deleted=1  where  id  =  ?  AND  deleted=

名称

@TableLogic

类型

属性注解

位置

模型类中用于表示删除字段的属性定义上方

作用

识该字段为进行逻辑删除的字段

相关属性

value:逻辑未删除值
delval:逻辑删除值

乐观锁

业务并发现象带来的问题:秒杀
假如有100个商品或者票在出售,为了能保证每个商品或者票只能被一个人购买,
如何保证不会出现超买或者重复卖

实现思路

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

实现步骤

步骤1:据库表添加列

列名可以任意,比如使用version ,给列设置默认值为1

 步骤2:在模型类中添加对应的属性

@Data
//@TableName("tbl_user")    可以不写是因为配置了全局配置
public class User {
    //修改id生成策略  已写全局配置
//    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField(value="pwd",select=false)//查询时隐藏字段
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)//数据库不存在字段
    private Integer online;
    //逻辑删除字段
//    @TableLogic(value = "0",delval = "1")
    private Integer deleted;
    //乐观锁
    @Version
    private Integer version;
}

步骤3:添加乐观锁的拦截器

@Configuration
public class MpConfig {

    @Bean
    public MybatisPlusInterceptor mpInterceptor(){
        //1、定义mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        //2、添加具体拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        //3、添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}

 步骤4:行更新操作

@SpringBootTest
class Mybatisplus03DqlApplicationTests {
    @Test
    void testUpdate() {
//        User user = new User();
//        user.setId(3L);
//        user.setName("Jock666");
//        user.setVersion(1);
//        userDao.updateById(user);
//
//        //1.先通过要修改的id 将当前数据查询出来
//        User user = userDao.selectById(3L);
//        //2.将要修改的数据逐一设置
//        user.setName("Jock888");
//        userDao.updateById(user);

        User user = userDao.selectById(3L); //version=3

        User user2 = userDao.selectById(3L); //version=3

        user2.setName("Jock bbb");
        userDao.updateById(user2);          //version=4

        user.setName("Jock aaa");
        userDao.updateById(user);           //version=3?还成立吗

    }
}

 

乐观锁就已经实现完成了,如果对于上面的这些步骤记不住咋办呢?
参考官方文档来实现:
https://mp.baomidou.com/guide/interceptor-optimistic- locker.html#optimisticlockerinnerinterceptor

 

快速开发

代码生成器原理分析

观察之前写的代码,会发现其中也会有很多重复内容,比如:

 

想做一个Book模块的开发,是不是只需要将红色部分的内容全部更换成 Book 即可,如:

 

做任何模块的开发,对于这段代码,基本上都是对红色部分的调整,
所以把去掉红色内容的东西称之为模板,红色部分称之为参数,以后只需要传入不同的参数,
就可以根据模板创建出不同模块的dao代码。
除了Dao可以抽取模块,其实常见的类都可以进行抽取,只要有公共部分即可。再来看下模型类的模板:

 

模板:  MyBatisPlus提供,可以自己提供,但是麻烦,不建议
数据库相关配置:读取数据库获取表和字段信息
开发者自定义配置:手工配置,比如ID生成策略

 代码生成器实现


public class CodeGenerator {
    public static void main(String[] args) {
        //1.获取代码生成器的对象
        AutoGenerator autoGenerator = new AutoGenerator();

        //设置数据库相关配置
        DataSourceConfig dataSource = new DataSourceConfig();
        dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        autoGenerator.setDataSource(dataSource);

        //设置全局配置
        GlobalConfig globalConfig = new GlobalConfig();
//        globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java");    //设置代码生成位置
        globalConfig.setOutputDir("D:/code/mybatisplus/mybatisplus-04_generator/src/main/java");    //设置代码生成位置
        globalConfig.setOpen(false);    //设置生成完毕后是否打开生成代码所在的目录
        globalConfig.setAuthor("Jen");    //设置作者
        globalConfig.setFileOverride(true);     //设置是否覆盖原始生成的文件
        globalConfig.setMapperName("%sDao");    //设置数据层接口名,%s为占位符,指代模块名称
        globalConfig.setIdType(IdType.ASSIGN_ID);   //设置Id生成策略
        autoGenerator.setGlobalConfig(globalConfig);

        //设置包名相关配置
        PackageConfig packageInfo = new PackageConfig();
        packageInfo.setParent("com.aaa");   //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
        packageInfo.setEntity("domain");    //设置实体类包名
        packageInfo.setMapper("dao");   //设置数据层包名
        autoGenerator.setPackageInfo(packageInfo);

        //策略设置
        StrategyConfig strategyConfig = new StrategyConfig();
        strategyConfig.setInclude("tbl_user");  //设置当前参与生成的表名,参数为可变参数
        strategyConfig.setTablePrefix("tbl_");  //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名  例如: User = tbl_user - tbl_
        strategyConfig.setRestControllerStyle(true);    //设置是否启用Rest风格
        strategyConfig.setVersionFieldName("version");  //设置乐观锁字段名
        strategyConfig.setLogicDeleteFieldName("deleted");  //设置逻辑删除字段名
        strategyConfig.setEntityLombokModel(true);  //设置是否启用lombok
        autoGenerator.setStrategy(strategyConfig);
        //2.执行生成操作
        autoGenerator.execute();
    }
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。