Spring---MyBatisPlus

目标:
基于 MyBatisPlus 完成标准 Dao 的增删改查功能
掌握 MyBatisPlus 中的分页及条件查询构建
掌握主键 ID 的生成策略
了解 MyBatisPlus 的代码生成器

目录

1,MyBatisPlus入门案例与简介

1.1 入门案例

1.2 MybatisPlus简介

2,标准数据层开发

2.1 标准CRUD使用

2.2 新增

执行测试后,数据库表中就会添加一条数据。 ​编辑 2.3 删除

2.4 修改

2.5 根据ID查询

2.6 查询所有

2.7 Lombok

2.8 分页功能

3,DQL编程控制

3.1 条件查询

3.1.1 条件查询的类

3.1.2 环境构建

 3.1.3 构建条件查询

3.1.4 多条件构建

3.1.5 null判定

3.2 查询投影

3.2.1 查询指定字段

3.2.2 聚合查询

3.2.3 分组查询

3.3 查询条件

3.3.2 范围查询

 3.3.3 模糊查询

3.3.4 排序查询

3.4 映射匹配兼容性

 4,DML编程控制

4.1 id生成策略控制

4.1.1 环境构建

4.1.2 代码演示

4.1.3 ID生成策略对比

4.1.4 简化配置

4.2 多记录操作

 4.3 逻辑删除

4.4 乐观锁

4.4.1 概念

4.4.2 实现思路

4.4.3 实现步骤

5,快速开发

5.1 代码生成器原理分析

5.2 代码生成器实现


1MyBatisPlus入门案例与简介

1.1 入门案例

MybatisPlus( 简称 MP) 是基于 MyBatis 框架基础上开发的增强型工具,旨在简化开发、提供效
率。
开发方式
        基于MyBatis 使用 MyBatisPlus
        基于Spring 使用 MyBatisPlus
        基于SpringBoot 使用 MyBatisPlus
SpringBoot 刚刚我们学习完成,它能快速构建 Spring 开发环境用以整合其他技术,使用起来是非常
简单,对于 MP 的学习,我们也基于 SpringBoot 来构建学习。
学习之前,我们先来回顾下,SpringBoot整合Mybatis的开发过程:
创建 SpringBoot 工程

 勾选配置使用的技术,能够实现自动添加起步依赖包

 设置dataSource相关属性(JDBC参数)

 定义数据层接口映射配置

 SpringBoot整合MyBatisPlus具体的实现步骤为:

步骤1:创建数据库及表

 步骤2:创建SpringBoot工程

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

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

步骤4:pom.xml补全依赖

说明 :
druid 数据源可以加也可以不加, SpringBoot 有内置的数据源,可以配置成使用 Druid 数据源
MP 的依赖关系可以看出,通过依赖传递已经将 MyBatis MyBatis 整合 Spring jar 包导入,
我们不需要额外在添加 MyBatis 的相关 jar

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

resources 默认生成的是 properties 配置文件,可以将其替换成 yml 文件,并在文件中配置数据库
连接的相关信息 : application.yml

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

 步骤7:创建Dao接口

步骤8:编写引导类

说明 : Dao 接口要想被容器扫描到,有两种解决方案 :
方案一 : Dao 接口上添加 @Mapper 注解,并且确保 Dao 处在引导类所在包或其子包中
该方案的缺点是需要在每一 Dao 接口中添加注解
方案二 : 在引导类上添加 @MapperScan 注解,其属性为所要扫描的 Dao 所在包
该方案的好处是只需要写一次,则指定包下的所有 Dao 接口都能被扫描到, @Mapper 就可以不写。

步骤9:编写测试类

说明 :
userDao 注入的时候下面有红线提示的原因是什么 ?
UserDao 是一个接口,不能实例化对象
只有在服务器启动 IOC 容器初始化后,由框架创建 DAO 接口的代理对象来注入
现在服务器并未启动,所以代理对象也未创建, IDEA 查找不到对应的对象注入,所以提示报红
一旦服务启动,就能注入其代理对象,所以该错误提示不影响正常运行。

Spring-IOC管理Dao层对象

由于dao层只有接口,需要使用sqlsession工厂对象,通过代理获取dao层对象。
使用IOC管理dao层对象需要进行如下步骤:
1、在spring容器中使用org.mybatis.spring.mapper.MapperFactoryBean创建dao接口对象;
2、注入sqlsession配置,即给sqlSessionFactory属性注入数据库信息;
3、给mapperInterface属性指定接口名字;
4、在测试类中加载spring配置文件,通过bean id获取dao层对象;并调用dao层提供方法。

查看运行结果:

1.2 MybatisPlus简介

MyBatisPlus (简称 MP )是基于 MyBatis 框架基础上开发的增强型工具,旨在 简化开发、提高效率
通过刚才的案例,相信大家能够体会简化开发和提高效率这两个方面的优点。
MyBatisPlus 的官网为 : https://mp.baomidou.com/
MP 的特性 :
无侵入:只做增强不做改变,不会对现有工程产生影响
强大的 CRUD 操作:内置通用 Mapper ,少量配置即可实现单表 CRUD 操作
支持 Lambda :编写查询条件无需担心字段写错
支持主键自动生成
内置分页插件
……

2,标准数据层开发

在这一节中我们重点学习的是数据层标准的 CRUD( 增删改查 ) 的实现与分页功能。代码比较多,我们一个个来学习。

2.1 标准CRUD使用

对于标准的 CRUD 功能都有哪些以及 MP 都提供了哪些方法可以使用呢 ?
我们先来看张图 :

2.2 新增

在进行新增之前,我们可以分析下新增的方法 :
int insert (T t)
T: 泛型,新增用来保存新增数据
int: 返回值,新增成功后返回 1 ,没有新增成功返回的是 0

在测试类中进行新增操作:

@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {

    @Autowired
    private UserDao userDao;

    @Test
    void testSave(){
        User user = new User();
        user.setName("黑马程序员");
        user.setPassword("itheima");
        user.setAge(12);
        user.setTel("4006184000");
        userDao.insert(user);
    }
}

执行测试后,数据库表中就会添加一条数据。 2.3 删除

在进行删除之前,我们可以分析下删除的方法:

 int deleteById (Serializable id)
Serializable :参数类型
思考 : 参数类型为什么是一个序列化类 ?

 

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

int:返回值类型,数据删除成功返回1,未删除数据返回0

在测试类中进行新增操作 :
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {

    @Autowired
    private UserDao userDao;

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

2.4 修改

在进行修改之前,我们可以分析下修改的方法 :
int updateById(T t);
T: 泛型,需要修改的数据内容,注意因为是根据 ID 进行修改,所以传入的对象中需要有 ID 属性值
int: 返回值,修改成功后返回 1 ,未修改数据返回 0
在测试类中进行新增操作 :
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {

    @Autowired
    private UserDao userDao;

    @Test
    void testUpdate(){
        User user = new User();
        user.setId(1L);
        user.setName("Tom888");
        user.setPassword("tom888");
        userDao.updateById(user);
    }
}
说明 : 修改的时候,只修改实体对象中有值的字段,不会出现null的情况。

2.5 根据ID查询

在进行根据 ID 查询之前,我们可以分析下根据 ID 查询的方法 :
T selectById (Serializable id)
Serializable :参数类型 , 主键 ID 的值
T: 根据 ID 查询只会返回一条数据
在测试类中进行新增操作 :
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {

    @Autowired
    private UserDao userDao;

    @Test
    void testGetById(){
        User user = userDao.selectById(2L);
        System.out.println(user);
    }
}

2.6 查询所有

在进行查询所有之前,我们可以分析下查询所有的方法 :
List<T> selectList(Wrapper<T> queryWrapper)
Wrapper :用来构建条件查询的条件,目前我们没有可直接传为 Null
List: 因为查询的是所有,所以返回的数据是一个集合
在测试类中进行新增操作 :
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {

    @Autowired
    private UserDao userDao;

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

2.7 Lombok

代码写到这,我们会发现 DAO 接口类的编写现在变成最简单的了,里面什么都不用写。反过来看看模型类的编写都需要哪些内容 :
私有属性
setter...getter... 方法
toString 方法
构造函数
虽然这些内容不难,同时也都是通过 IDEA 工具生成的,但是过程还是必须得走一遍,那么对于模型类的编写有没有什么优化方法 ? 就是我们接下来要学习的 Lombok
概念
Lombok ,一个 Java 类库,提供了一组注解,简化 POJO 实体类开发。
使用步骤
步骤 1: 添加 lombok 依赖
注意: 版本可以不用写,因为 SpringBoot 中已经管理了 lombok 的版本。
步骤 2: 安装 Lombok 的插件
新版本 IDEA 已经内置了该插件,如果删除 setter getter 方法程序有报红,则需要安装插件

如果在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
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
}
说明 :
Lombok 只是简化模型类的编写,我们之前的方法也能用,比如有人会问 : 我如果只想要有 name
password 的构造函数,该如何编写 ?

2.8 分页功能

基础的增删改查就已经学习完了,刚才我们在分析基础开发的时候,有一个分页功能还没有实现,在MP 中如何实现分页功能,就是咱们接下来要学习的内容。
分页查询使用的方法是 :
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 testGetByPage(){
        //IPage对象封装了分页操作相关的数据
        IPage page  = new Page(2,3);
        //执行分页查询
        userDao.selectPage(page,null);
        //获取分页结果
        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: 设置分页拦截器
这个拦截器 MP 已经为我们提供好了,我们只需要将其配置成 Spring 管理的 bean 对象即可。

Mybatis-plus分页查询底层原理

        ​ PageHelper内部原理是将传⼊的页码和每页条数赋值给了Page对象,保存到了⼀个本地线程ThreadLoacl中,然后会进⼊Mybatis的拦截器中。然后在拦截器中获取本地线程中保存的分页的参数。最后再将这些分页参数和原本的sql以及内部定义好的sql进⾏拼接完成sql的分页处理。中间会进⾏判断该sql 的类型是查询还是修改操作。如果是查询才会进⼊分页的逻辑并判断封装好的Page对象是否是null,null则不分页,否则分页。

        ​ IPage内部原理也是基于拦截器,但是这个拦截的是⽅法以及⽅法中的参数,这个也会判断是否是查询操作。如果是查询操作,才会进⼊分页的处理逻辑。进⼊分页逻辑处理后,拦截器会通过反射获取该⽅法的参数进⾏判断是否存在IPage对象的实现类。如果不存在则不进⾏分页,存在则将该参数赋值给IPage对象。然后进⾏拼接sql的处理完成分页操作。

@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor(){
        //1.定义Mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        //2.添加具体的拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}
说明 : 上面的代码记不住咋办呢 ?
这些内容在MP的官方文档中有详细的说明,我们可以查看官方文档类配置

 

步骤3:运行测试程序

 如果想查看MP执行的SQL语句,可以修改application.yml配置文件,

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
打开日志后,就可以在控制台打印出对应的SQL语句,开启日志功能性能就会受到影响,调试完后记得关闭。

 

3DQL编程控制

增删改查四个操作中,查询是非常重要的也是非常复杂的操作,这块需要我们重点学习下,这节我们主要学习的内容有 :
条件查询方式
查询投影
查询条件设定
字段映射与表名映射

3.1 条件查询

3.1.1 条件查询的类

MyBatisPlus 将书写复杂的 SQL 查询条件进行了封装,使用编程的形式完成查询条件的组合。
这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到过一个 Wrapper 类,这个类就 是用来构建查询条件的,如下图所示 :

3.1.2 环境构建

在构建条件查询之前,我们先来准备下环境
创建一个 SpringBoot 项目
pom.xml 中添加对应的依赖
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>mybatisplus_02_dql</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

    </dependencies>

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

</project>
编写 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 Mybatisplus02DqlApplication {

    public static void main(String[] args) {
        SpringApplication.run(Mybatisplus02DqlApplication.class, args);
    }

}
编写配置文件
# dataSource
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
    username: root
    password: root
  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添加如下内容:
取消 SpringBoot log 打印
application.yml添加如下内容:

 3.1.3 构建条件查询

在进行查询的时候,我们的入口是在 Wrapper 这个类上,因为它是一个接口,所以我们需要去找它对应的实现类,关于实现类也有很多,说明我们有多种构建查询条件对象的方式,
1. 先来看第一种 : QueryWrapper

 lt: 小于(<) ,最终的sql语句为

SELECT id,name,password,age,tel FROM user WHERE (age < ?)
第一种方式介绍完后,有个小问题就是在写条件的时候,容易出错,比如 age 写错,就会导致查询不成功
2. 接着来看第二种 : QueryWrapper的基础上使用lambda

 User::getAget,lambda表达式中的,类名::方法名,最终的sql语句为:

 SELECT id,name,password,age,tel FROM user WHERE (age < ?)
注意 : 构建 LambdaQueryWrapper 的时候泛型不能省。
此时我们再次编写条件的时候,就不会存在写错名称的情况,但是 qw 后面多了一层 lambda() 调用
3. 接着来看第三种 : LambdaQueryWrapper

 

3.1.4 多条件构建

学完了三种构建查询对象的方式,每一种都有自己的特点,所以用哪一种都行,刚才都是一个条件,那如果有多个条件该如何构建呢 ?

 需求:查询数据库表中,年龄在10岁到30岁之间的用户信息

 gt:大于(>),最终的SQL语句为

SELECT id,name,password,age,tel FROM user WHERE (age < ? AND age > ?)
构建多条件的时候,可以支持链式编程

 需求:查询数据库表中,年龄小于10或年龄大于30的数据

 or()就相当于我们sql语句中的or关键字,不加默认是and,最终的sql语句为:

SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)

3.1.5 null判定

需求 : 查询数据库表中,根据输入年龄范围来查询符合条件的记录
用户在输入值的时候,
如果只输入第一个框,说明要查询大于该年龄的用户
如果只输入第二个框,说明要查询小于该年龄的用户
如果两个框都输入了,说明要查询年龄在两个范围之间的用户
思考第一个问题:后台如果想接收前端的两个数据,该如何接收 ?
我们可以使用两个简单数据类型,也可以使用一个模型类,但是 User 类中目前只有一个 age 属性 ,如:
使用一个 age 属性,如何去接收页面上的两个值呢 ? 这个时候我们有两个解决方案
方案一 : 添加属性 age2, 这种做法可以但是会影响到原模型类的属性内容

 

方案二 : 新建一个模型类 , 让其继承 User 类,并在其中添加 age2 属性, UserQuery 在拥有 User 属性后
同时添加了age2属性。

 环境准备好后,我们来实现下刚才的需求:

上面的写法可以完成条件为非空的判断,但是问题很明显,如果条件多的话,每个条件都需要判断,
代码量就比较大,来看MP给我们提供的简化方式:

3.2 查询投影

3.2.1 查询指定字段

目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容,我们所说的查询投影即不查询所有字段,只查询出指定内容的数据
具体如何来实现 ?

 select(...)方法用来设置查询的字段列,可以设置多个,最终的sql语句为:

SELECT id,name,age FROM user
如果使用的不是lambda,就需要手动指定字段

 最终的sql语句为:SELECT id,name,age,tel FROM user

3.2.2 聚合查询

需求 : 聚合函数查询,完成 count max min avg sum 的使用
count: 总记录数
max: 最大值
min: 最小值
avg: 平均值
sum: 求和

3.2.3 分组查询

需求 : 分组查询,完成 group by 的查询使用

 groupBy为分组,最终的sql语句为

SELECT count(*) as count,tel FROM user GROUP BY tel
注意 :
聚合与分组查询,无法使用 lambda 表达式来完成
MP 只是对 MyBatis 的增强,如果 MP 实现不了,我们可以直接在 DAO 接口中使用 MyBatis 的方式实

3.3 查询条件

前面我们只使用了 lt() gt(), 除了这两个方法外, MP 还封装了很多条件对应的方法,这一节我们重
点把 MP 提供的查询条件方法进行学习下。
MP 的查询条件有很多 :
范围匹配( > = between
模糊匹配( like
空判定( null
包含性匹配( in
分组( group
排序( order
……
3.3.1 等值查询
需求:根据用户名和密码查询用户信息

 eq(): 相当于 = ,对应的sql语句为

SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
selectList :查询结果为多个或者单个
selectOne: 查询结果为单个

3.3.2 范围查询

需求 : 对年龄进行范围查询,使用 lt() le() gt() ge() between() 进行范围查询

 

gt(): 大于 (>)
ge(): 大于等于 (>=)
lt(): 小于 (<)
lte(): 小于等于 (<=)
between():between ? and ?

 3.3.3 模糊查询

需求:查询表中name属性的值以J开头的用户信息,使用like进行模糊查询

like(): 前后加百分号 , %J%
likeLeft(): 前面加百分号 , %J
likeRight(): 后面加百分号 , J%

3.3.4 排序查询

需求 : 查询所有数据,然后按照id降序

 除了上面演示的这种实现方式,还有很多其他的排序方法可以被调用,如图:

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

3.4 映射匹配兼容性

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

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

当模型类中多了一个数据库表不存在的字段,就会导致生成的 sql 语句中在 select 的时候查询了数据
库不存在的字段,程序运行就会报错,错误信息为 :
Unknown column ' 多出来的字段名称 ' in 'field list'
具体的解决方案用到的还是 @TableField 注解,它有一个属性叫 exist ,设置该字段是否在数据库表
中存在,如果设置为 false 则不存在,生成 sql 语句查询的时候,就不会再查询该字段了。

 

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

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

 

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

该问题主要是表的名称和模型类的名称不一致,导致查询失败,这个时候通常会报如下错误信息 :
Table 'databaseName.tableNaem' doesn't exist , 翻译过来就是数据库中的表不存在。
解决方案是使用 MP 提供的另外一个注解 @TableName 来设置表与模型类之间的对应关系。

 

 4DML编程控制

4.1 id生成策略控制

前面我们在新增的时候留了一个问题,就是新增成功后,主键 ID 是一个很长串的内容,我们更想要的是按照数据库表字段进行自增长,在解决这个问题之前,我们先来分析下 ID 该如何选择 :
不同的表应用不同的 id 生成策略
日志:自增( 1,2,3,4 ……
购物订单:特殊规则( FQ23948AK3843
外卖单:关联地区日期等信息( 10 04 20200314 34 91
关系表:可省略 id
……
不同的业务采用的 ID 生成方式应该是不一样的,那么在 MP 中都提供了哪些主键生成策略,以及我们该如何进行选择 ?
在这里我们又需要用到 MP 的一个注解叫 @TableId

4.1.1 环境构建

在构建条件查询之前,我们先来准备下环境
创建一个 SpringBoot 项目
pom.xml 中添加对应的依赖
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>mybatisplus_03_dml</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

    </dependencies>

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

</project>

 编写配置文件

 

 最终创建的项目结构为:

 

4.1.2 代码演示

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

步骤 2: 删除测试数据并修改自增值
删除测试数据

因为之前生成主键 ID 的值比较长,会把 MySQL 的自动增长的值变的很大,所以需要将其调整为目前 最新的id值。

 

步骤 3: 运行新增方法
会发现,新增成功,并且主键 id 也是从5开始

 

经过这三步的演示,会发现 AUTO 的作用是 使用数据库 ID 自增 ,在使用该策略的时候一定要确保对应的数据库表设置了 ID 主键自增,否则无效。
接下来,我们可以进入源码查看下 ID 的生成策略有哪些 ?
打开源码后,你会发现并没有看到中文注释,这就需要我们点击右上角的 Download Sources , 会自动帮你把这个类的 java 文件下载下来,我们就能看到具体的注释内容。因为这个技术是国人制作的,所 以他代码中的注释还是比较容易看懂的。
从源码中可以看到,除了 AUTO 这个策略以外,还有如下几种生成策略 :
NONE: 不设置 id 生成策略
INPUT: 用户手工输入 id
ASSIGN_ID: 雪花算法生成 id( 可兼容数值型与字符串型 )
ASSIGN_UUID: UUID 生成算法作为 id 生成策略
其他的几个策略均已过时,都将被 ASSIGN_ID ASSIGN_UUID 代替掉。
拓展 :
分布式 ID 是什么 ?
当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储
比如订单表就有可能被存储在不同的服务器上
如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突
这个时候就需要一个全局唯一 ID, 这个 ID 就是分布式 ID
INPUT 策略
步骤 1: 设置生成策略为 INPUT

 注意:这种ID生成策略,需要将表的自增策略删除掉

 步骤2:添加数据手动设置ID

步骤 3: 运行新增方法
如果没有设置主键 ID 的值,则会报错,错误提示就是主键 ID 没有给值 :

 

如果设置了主键 ID,则数据添加成功,如下:

 

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

 步骤2:添加数据不设置ID

 注意:这种生成策略,不需要手动设置ID,如果手动设置ID,则会使用自己设置的值。

步骤3:运行新增方法

 生成的ID就是一个Long类型的数据。

ASSIGN_UUID 策略
步骤 1: 设置生成策略为 ASSIGN_UUID
使用 uuid 需要注意的是,主键的类型不能是 Long ,而应该改成 String 类型

 步骤2:修改表的主键类型

 

主键类型设置为 varchar ,长度要大于 32 ,因为 UUID 生成的主键为 32 位,如果长度小的话就会导致
插入失败。
步骤 3: 添加数据不设置 ID

 步骤4:运行新增方法

接下来我们来聊一聊雪花算法 :
雪花算法 (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

4.1.3 ID生成策略对比

介绍了这些主键 ID 的生成策略,我们以后该用哪个呢 ?
NONE: 不设置 id 生成策略, MP 不自动生成,约等于 INPUT, 所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的 ID 造成主键冲突,为了保证主键不冲突就需要做很 多判定,实现起来比较复杂
AUTO: 数据库 ID 自增 , 这种策略适合在数据库服务器只有 1 台的情况下使用 , 不可作为分布式 ID 使用
ASSIGN_UUID: 可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是 32 位的字符 串,长度过长占用空间而且还不能排序,查询性能也慢
ASSIGN_ID: 可以在分布式的情况下使用,生成的是 Long 类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键
综上所述,每一种主键策略都有自己的优缺点,根据自己项目业务的实际情况来选择使用才是最明智的选择。

4.1.4 简化配置

前面我们已经完成了表关系映射、数据库主键策略的设置,接下来对于这两个内容的使用,我们再讲下他们的简化配置 :
模型类主键策略设置
对于主键 ID 的策略已经介绍完,但是如果要在项目中的每一个模型类上都需要使用相同的生成策略,如 :

确实是稍微有点繁琐,我们能不能在某一处进行配置,就能让所有的模型类都可以使用该主键ID策略呢? 答案是肯定有,我们只需要在配置文件中添加如下内容:

 配置完成后,每个模型类的主键ID策略都将成为assign_id.

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

 配置起来还是比较繁琐,简化方式为在配置文件中配置如下内容:设置表的前缀内容,这样MP就会拿 tbl_加上模型类的首字母小写,就刚好组装成数据库的表名。

4.2 多记录操作

具体该如何实现多条删除,我们找找对应的API方法

int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
翻译方法的字面意思为 : 删除(根据 ID 批量删除) , 参数是一个集合,可以存放多个 id 值。
需求 : 根据传入的 id 集合将数据库表中的数据删除掉

执行成功后,数据库表中的数据就会按照指定的 id 进行删除。
除了按照 id 集合进行批量删除,也可以按照 id 集合进行批量查询,还是先来看下 API
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
需求:根据传入的ID集合查询用户信息

 查询结果就会按照指定传入的id值进行查询

 4.3 逻辑删除

在多表中,要将某信息删除,但该信息中的某以字段要保留,此时我们可以使用逻辑删除,标记要删除的信息,那么此时该信息已经假定删除了,但其字段信息仍然保留。

MP 中逻辑删除具体该如何实现 ?
步骤 1: 修改数据库表添加 deleted
字段名可以任意,内容也可以自定义,比如 0 代表正常, 1 代表删除,可以在添加列的同时设置其默认值为 0 正常。
步骤 2: 实体类添加属性
(1) 添加与数据库表的列对应的一个属性名,名称可以任意,如果和数据表列名对不上,可以使用
@TableField 进行关系映射,如果一致,则会自动对应。
(2) 标识新增的字段为逻辑删除字段,使用 @TableLogic

 

 步骤3:运行删除方法

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

 

 运行测试,会发现打印出来的sql语句中会多一个查询条件,如:

可想而知, MP 的逻辑删除会将所有的查询都添加一个未被删除的条件,也就是已经被删除的数据是不应该被查询出来的。
如果还是想把已经删除的数据都查询出来该如何实现呢 ?

 

如果每个表都要有逻辑删除,那么就需要在每个模型类的属性上添加 @TableLogic 注解,如何优
?
在配置文件中添加全局配置,如下:
介绍完逻辑删除,逻辑删除的本质为 :
逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
执行的 SQL 语句为 :
UPDATE tbl_user SET deleted =1 where id = ? AND deleted =0
执行数据结果为:

4.4 乐观锁

4.4.1 概念

乐观锁主要解决的问题是当要更新一条记录的时候,希望这条记录没有被别人更新。

4.4.2 实现思路

乐观锁的实现方式 :

数据库表中添加 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 提供的乐观锁的实现原理分析。

4.4.3 实现步骤

分析完步骤后,具体的实现步骤如下 :
步骤 1: 数据库表添加列
列名可以任意,比如使用 version ,给列设置默认值为1

 

步骤 2: 在模型类中添加对应的属性
根据添加的字段列名,在模型类中添加对应的属性值

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

 步骤4:执行更新操作

 

你会发现,这次修改并没有更新 version 字段,原因是没有携带 version 数据。
添加version数据

 

你会发现,我们传递的是 1 MP 会将 1 进行加 1 ,然后,更新回到数据库表中。
所以要想实现乐观锁,首先第一步应该是拿到表中的 version ,然后拿 version 当条件在将 version
加1更新回到数据库表中,所以我们在查询的时候,需要对其进行查询

 

大概分析完乐观锁的实现步骤以后,我们来模拟一种加锁的情况,看看能不能实现多个人修改同一个数据的时候,只能有一个人修改成功。

 

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

5,快速开发

5.1 代码生成器原理分析

 

 

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

 

5.2 代码生成器实现

步骤 1: 创建一个 Maven 项目
代码 2: 导入对应的 jar

 

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
    </parent>
    <groupId>com.itheima</groupId>
    <artifactId>mybatisplus_04_generator</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--spring webmvc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mybatisplus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

        <!--代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>

        <!--velocity模板引擎-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>

    </dependencies>

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

</project>

 

 步骤4:创建代码生成类

package com.itheima;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;

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("root");
        autoGenerator.setDataSource(dataSource);

        //设置全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java");    //设置代码生成位置
        globalConfig.setOpen(false);    //设置生成完毕后是否打开生成代码所在的目录
        globalConfig.setAuthor("黑马程序员");    //设置作者
        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();
    }
}
对于代码生成器中的代码内容,我们可以直接从官方文档中获取代码进行修改,
https://mp.baomidou.com/guide/generator.html
步骤 5: 运行程序

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值