03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)

文章目录

    • 学习目标
    • 一、MyBatisPlus简介
      • 1. 入门案例
        • 问题导入
        • 1.1 SpringBoot整合MyBatisPlus入门程序
          • ①:创建新模块,选择Spring初始化,并配置模块相关基础信息
          • ③:手动添加MyBatisPlus起步依赖
          • ④:制作实体类与表结构
          • ⑤:设置Jdbc参数(**application.yml**)
          • ⑥:定义数据接口,继承**BaseMapper**
          • ⑦:测试类中注入dao接口,测试功能
      • 2. MyBatisPlus概述
        • 问题导入
        • 2.1 MyBatis介绍
        • 2.2 MyBatisPlus特性
    • 二、标准数据层开发
      • 1. MyBatisPlus的CRUD操作
      • 2. Lombok插件介绍
        • 问题导入
      • 3. MyBatisPlus分页功能
        • 问题导入
        • 3.1 分页功能接口
        • 3.2 MyBatisPlus分页使用
        • 3.3 开启MyBatisPlus日志
        • 3.4 解决日志打印过多问题
          • 3.4.1 取消初始化spring日志打印
          • 3.4.2 取消SpringBoot启动banner图标
          • 3.4.3 取消MybatisPlus启动banner图标
    • 三、DQL编程控制
      • 1. 条件查询方式
        • 1.0 环境准备
        • 1.1 条件查询
          • 1.1.1 方式一:按条件查询
          • 1.1.2 方式二:lambda格式按条件查询
          • 1.1.3 方式三:lambda格式按条件查询(推荐)
        • 1.2 组合条件
          • 1.2.1 并且关系(and)
          • 1.2.2 或者关系(or)
        • 1.3 NULL值处理
          • 问题导入
          • 1.3.1 if语句控制条件追加
          • 1.3.2 条件参数控制
          • 1.3.3 条件参数控制(链式编程)
      • 2. 查询投影-设置【查询字段、分组、分页】
      • 2.1 查询结果包含模型类中部分属性
        • 2.2 查询结果包含模型类中未定义的属性
      • 3. 查询条件设定
        • 问题导入
        • 3.1 查询条件
        • 3.2 查询API
        • 3.3 练习:MyBatisPlus练习
      • 4. 字段映射与表名映射
        • 问题导入
        • 4.1 问题一:表字段与编码属性设计不同步
        • 4.2 问题二:编码中添加了数据库中未定义的属性 (防止查询时java代码真的构造sql去查它)
        • 4.3 问题三:采用默认查询开放了更多的字段查看权限
        • 4.4 问题四:表名与编码开发设计不同步
    • 四、DML编程控制
      • 0. 环境搭建
      • 1. id生成策略控制(Insert)
        • 问题导入
        • 1.1 id生成策略控制(@TableId注解)
        • 1.2 全局策略配置
          • id生成策略全局配置
          • 表名前缀全局配置
      • 2. 多记录操作(批量Delete/Select)
        • 问题导入
        • 2.1 按照主键删除多条记录
        • 2.2 根据主键查询多条记录
      • 3. 逻辑删除(Delete/Update)
        • 问题导入
        • 3.1 逻辑删除案例
          • ①:数据库表中添加逻辑删除标记字段
          • ②:实体类中添加对应字段,并设定当前字段为逻辑删除标记字段(指定逻辑删除值)
          • trick:全局配置逻辑删除字面值 (推荐)
      • 4. 乐观锁(Update)
        • 问题导入
        • 4.1 乐观锁案例
          • ①:数据库表中添加锁标记字段
          • ②:实体类中添加对应字段,并设定当前字段为 锁标记字段
          • ③:配置乐观锁==拦截器==实现锁机制对应的动态==SQL语句拼装==
          • ④:使用乐观锁机制在修改前必须先获取到对应数据的verion方可正常进行
    • 五、快速开发-代码生成器
      • 问题导入
      • 1. MyBatisPlus提供模板
      • 2. 工程搭建和基本代码编写
      • 3. 开发者自定义配置

学习目标

  • 能够基于MyBatisPlus完成标准Dao开发
  • 能够掌握MyBatisPlus的条件查询
  • 能够掌握MyBatisPlus的字段映射与表名映射
  • 能够掌握id生成策略控制
  • 能够理解代码生成器的相关配置

一、MyBatisPlus简介

springBoot是帮助我们快速开发spring程序的
mybatisPlus是帮助我们快速开发mybatis程序的

MyBatisPlus简称MP

1. 入门案例

问题导入

MyBatisPlus环境搭建的步骤?

1.1 SpringBoot整合MyBatisPlus入门程序

①:创建新模块,选择Spring初始化,并配置模块相关基础信息

先创建空白Project作为容器,mybatisplus所有工程都创建成其下面的子module
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第1张图片
直接创建boot工程
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第2张图片
②:选择当前模块需要使用的技术集(仅保留JDBC)
勾选功能:
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第3张图片
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第4张图片

③:手动添加MyBatisPlus起步依赖

pom.xml里

<dependency>
    <groupId>com.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
    <version>3.4.1version>
dependency>
<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druidartifactId>
    <version>1.1.16version>
dependency>

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第5张图片

注意事项1:由于MP(MybatisPlus)并未被收录到idea的系统内置配置,无法直接选择加入

注意事项2:如果使用Druid数据源,需要导入对应坐标

④:制作实体类与表结构

(类名与表名对应,属性名与字段名对应)

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(null,'tom','123456',12,'12345678910');
insert into user values(null,'jack','123456',8,'12345678910');
insert into user values(null,'jerry','123456',15,'12345678910');
insert into user values(null,'tom','123456',9,'12345678910');
insert into user values(null,'snake','123456',28,'12345678910');
insert into user values(null,'张益达','123456',22,'12345678910');
insert into user values(null,'张大炮','123456',16,'12345678910');

pom.xml里导入lombok

<dependency>
	<groupId>org.projectlombokgroupId>
	<artifactId>lombokartifactId>
dependency>
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
}
⑤:设置Jdbc参数(application.yml

resources下application.properties文件重命名为application.yml

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
⑥:定义数据接口,继承BaseMapper

MP提供了BaseMapper类, 继承它,那么基本的crud方法直接就有了~
(就是之前Hibernate里面抽取的BaseDao)

package com.itheima.dao;

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

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

dao就开发完了,所有的基础crud功能都有了,抽取BaseDao的好处啊~

⑦:测试类中注入dao接口,测试功能

src/test/java/cn/whu/Mybatisplus01QuickstartApplicationTests.java 里写测试

@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
	@Autowired
	UserDao userDao;
	@Test
	void testUserDao() {
		List<User> users = userDao.selectList(null);
		System.out.println(users);
	}
}

运行结果:

2023-03-29 22:43:53.643  INFO 8492 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
[User(id=1, name=tom, password=123456, age=12, tel=12345678910), User(id=2, name=jack, password=123456, age=8, tel=12345678910), User(id=3, name=jerry, password=123456, age=15, tel=12345678910), User(id=4, name=tom, password=123456, age=9, tel=12345678910), User(id=5, name=snake, password=123456, age=28, tel=12345678910), User(id=6, name=张益达, password=123456, age=22, tel=12345678910), User(id=7, name=张大炮, password=123456, age=16, tel=12345678910)]

2. MyBatisPlus概述

问题导入

通过入门案例制作,MyBatisPlus的优点有哪些?

2.1 MyBatis介绍

  • MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率

  • 官网:https/mybatis.plus/ 或者 https://mp.baomidou.com/

两个域名,第一个之前就被网友注册了,后来热心网友捐给他了,笑死~
网站竟然是中文的?没错,就是中国人开发的,振奋人心啊~ 也理解了baomidou,拼音域名了,哈哈哈

2.2 MyBatisPlus特性

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

二、标准数据层开发

1. MyBatisPlus的CRUD操作

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第6张图片

@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
	@Autowired
	UserDao userDao;

	@Test
	void testGetById(){
		Long id = 1l;
		User user = userDao.selectById(id);
		System.out.println(user);
		// User(id=1, name=tomcat, password=123456, age=12, tel=12345678910)
		// 果然能查到
	}

	@Test
	void testUpdate(){
		User user = new User();
		user.setId(1l);
		user.setName("tomcat");
		userDao.updateById(user);
		//查数据库 name真的变成 tomcat了
		//有值修改 null值不变 //想想如何实现? 1、动态sql  2、先根据id查一下,将user的null属性补上原来的值
	}

	@Test
	void testDel(){
		Long id = 1641093882043678722l;
		userDao.deleteById(id);
		//查看数据库,确实删除了
	}

	@Test
	void testSave(){
		User user = new User(null,"张三","123",13,"13912345678");
		userDao.insert(user);
		//查数据库确实增加了 //id还是uuid,真不错~
	}

	@Test
	void testUserDao() {//GetAll
		List<User> users = userDao.selectList(null);
		System.out.println(users);
	}
}

2. Lombok插件介绍

问题导入

有什么简单的办法可以自动生成实体类的GET、SET方法?

  • Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。
<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
    
    <version>1.18.12version>
    
    <scope>providedscope>
dependency>
  • 常用注解:@Data,为当前实体类在编译期设置对应的get/set方法,无参/无参构造方法,toString方法,hashCode方法,equals方法等
import lombok.*;
/*
    1 生成getter和setter方法:@Getter、@Setter
      生成toString方法:@ToString
      生成equals和hashcode方法:@EqualsAndHashCode

    2 统一成以上所有:@Data

    3 生成空参构造: @NoArgsConstructor
      生成全参构造: @AllArgsConstructor

    4 lombok还给我们提供了builder的方式创建对象,好处就是可以链式编程。 @Builder【扩展】
 */
@Data
public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
}

3. MyBatisPlus分页功能

问题导入

思考一下Mybatis分页插件是如何用的?

3.1 分页功能接口

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第7张图片

3.2 MyBatisPlus分页使用

①:设置分页拦截器作为Spring管理的bean

分页查询其实就是查询全部的基础上增强一点功能(最后加上limit),所以要么AOP要么拦截器,MP是通过拦截器实现的,所以得回归原始配置类,写MP的拦截器了,并将其放到IOC容器中给Spring管理
如果不写,分页查询方法就退化为查询所有方法了

新建类: cn.whu.config.MyBatisPlusConfig

package com.itheima.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//最简单的放到容器里的方法,直接注解@Configuration ,只要此文件能被扫描到,就能在IOC容器中创建它 
//否则就得启动类XXXApplications上写@Import(MybatisPlusConfig.class)也行
@Configuration 
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1.定义MP拦截器
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        // 2.添加具体的拦截器 (这里加的就是MP内置的分页拦截器)
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}

忍不住创建为模板类

②:执行分页查询

cn.whu.Mybatisplus01QuickstartApplicationTests 测试类里面接着写

//分页查询
@Test
void testGetByPage(){
	//1. 创建IPage分页对象,设置分页参数
	//注意页码从1开始 (所有小于1的非法页码值都会被处理成1)
	// 泛型不写也行写了方便点
	IPage<User> page = new Page(2,2);//直接可以设置第几页 这页(每页)多少条
	//2. 执行分页查询
	userDao.selectPage(page, null);//查询参数先设置为null //其实返回值也是page
	//3. 获取分页结果
	System.out.println(page.getRecords());//直接就存储到page里面了
	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());
}

再下面两步(3.3、3.4)非必要,调试时才可能需要

3.3 开启MyBatisPlus日志

配置打印MybatisPlus的sql语句 (就是看它的日志)

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
# 开启mp的日志(输出到控制台)
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

重新查询:查看日志

@Test
void testGetByPage2(){
	IPage<User> page = new Page(2,3);//查询具有一般性的一页
	userDao.selectPage(page, null);
	System.out.println("实际数据: "+page.getRecords());
}

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第8张图片

3.4 解决日志打印过多问题

3.4.1 取消初始化spring日志打印

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第9张图片

做法:在resources下新建一个logback.xml文件,名称固定,内容如下:


<configuration>

configuration>

关于logback参考播客:https://www.jianshu.com/p/75f9d11ae011

再执行只有MP日志了,没有Spring日志了
不过其实点具体的方法,效果差不多
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第10张图片

3.4.2 取消SpringBoot启动banner图标

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第11张图片

resources下application.yml里

spring:
  main:
    banner-mode: off # 关闭SpringBoot启动图标(banner)
3.4.3 取消MybatisPlus启动banner图标

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第12张图片
现在控制台切不切换到最终方法,打印的日志都差不多了

# mybatis-plus日志控制台输出
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    banner: off # 关闭mybatisplus启动图标

trick: 这两个配置都叫banner, yml里写banner,就会出现代码提示,选择即可

此时完整的yml配置:

server:
  port: 80

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: 1234
  main:
    banner-mode: off # 关闭SpringBoot启动图标(banner)

# 开启MP日志(控制台输出,其中有sql语句)
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    banner: off # 关闭mybatisplus启动图标

三、DQL编程控制

1. 条件查询方式

  • MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第13张图片

1.0 环境准备

和前面一样,新建一个一样的boot程序,初始操作也完全一样
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第14张图片
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第15张图片
pom.xml添加依赖:

<dependency>
	<groupId>com.baomidougroupId>
	<artifactId>mybatis-plus-boot-starterartifactId>
	<version>3.4.1version>
dependency>
<dependency>
	<groupId>com.alibabagroupId>
	<artifactId>druidartifactId>
	<version>1.1.16version>
dependency>
<dependency>
	<groupId>org.projectlombokgroupId>
	<artifactId>lombokartifactId>
dependency>

删除多余的,然后新建目录结构如下:
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第16张图片
UserDao

@Mapper // 肯定要IOC创建代理对象啊
public interface UserDao extends BaseMapper<User> {

}

User

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

application.yml

server:
  port: 80

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: 1234
  main:
    banner-mode: off # 关闭SpringBoot启动图标(banner)

# 开启MP日志(控制台输出,其中有sql语句)
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    banner: off # 关闭mybatisplus启动图标

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

</configuration>

Mybatisplus02DqlApplicationTests
就剩下一个测试方法了:

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
	@Autowired
	UserDao userDao;
	@Test
	void testGetAll() {
		List<User> users = userDao.selectList(null);
		System.out.println(users);
	}
}

分页查询用不着,就不配那个拦截器了

此案例的初始环境,搭建完毕!


1.1 条件查询

Wrapper是个接口,我们用其实现类完成按条件查询,DQL对应的实现类是QueryWrapper、LambdaQueryWrapper

1.1.1 方式一:按条件查询
//方式一:按条件查询
@Test
void testGetAll() {
	//按条件查询
	QueryWrapper qw = new QueryWrapper();
	qw.lt("age",18);//where age<18
	List<User> users = userDao.selectList(qw);
	for (User user : users) {
		System.out.println(user);
	}
}
1.1.2 方式二:lambda格式按条件查询

"age"容易写错,所以就出现了lambda格式
注意:只是还是QueryWrapper类,所以写法比较复杂

//方式二:lambda格式按条件查询
@Test
void testGetAll2() {
	//方式一:按条件查询
	//这种写法必须指定泛型了
	QueryWrapper<User> qw = new QueryWrapper<User>();
	qw.lambda().lt(User::getAge,10);//where age<10
	List<User> users = userDao.selectList(qw);
	for (User user : users) {
		System.out.println(user);
	}
}
1.1.3 方式三:lambda格式按条件查询(推荐)

注意:此时直接换用LambdaQueryWrapper类了,写法简单多了

//方式三:lambda格式按条件查询
@Test
void testGetAll2() {
	//方式三:按条件查询 LambdaQueryWrapper实现类
	//这种写法必须指定泛型了
	LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
	lqw.lt(User::getAge,10);//where age<10
	List<User> users = userDao.selectList(lqw);
	for (User user : users) {
		System.out.println(user);
	}
}

感想:还是Hibernate那一套,不写sql语句,只写java方法,毕竟java程序嘛

1.2 组合条件

1.2.1 并且关系(and)
//并且关系
@Test
void testGetAll3() {
	// 多条件查询方一:直接设置多个条件
	LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
	lqw.lt(User::getAge,30);//where age<30
	lqw.gt(User::getAge,20);//where age>20
	List<User> users = userDao.selectList(lqw);
	for (User user : users) {
		System.out.println(user);
	}
}

条件多了会很难看,这个时候就该链式编程出马了

//并且关系
@Test
void testGetAll4() {
	// 多条件查询方式2:链式编程
	LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
	lqw.lt(User::getAge,30).gt(User::getAge,20);//where age<30 and age>20
	List<User> users = userDao.selectList(lqw);
	for (User user : users) {
		System.out.println(user);
	}
}
1.2.2 或者关系(or)
//或者关系
@Test
void testGetAll5() {
	// 多条件查询:or
	LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
	// 中间链接一个or就行了
	lqw.gt(User::getAge,30).or().lt(User::getAge,10);//where age>30 or age<10
	List<User> users = userDao.selectList(lqw);
	for (User user : users) {
		System.out.println(user);
	}
}

非链式这么写,肯定不推荐:

@Test
void testGetAll5_1() {
	// 多条件查询:or
	LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
	lqw.gt(User::getAge,30);
	lqw.or().lt(User::getAge,10);//where age>30 or age<10
	List<User> users = userDao.selectList(lqw);
	for (User user : users) {
		System.out.println(user);
	}
}

1.3 NULL值处理

问题导入

如下搜索场景,在多条件查询中,有条件的值为空应该怎么解决?
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第17张图片

先定义个辅助查询类,专门封装用户前端指定的查询条件参数
cn.whu.domain.query.UserQuery

/**
 * 专门用于封装User对象查询条件的辅助类
 */
@Data
public class UserQuery extends User {//想用其属性 直接继承呗
    private Integer age2;//作为查询条件的上限而存在
}
1.3.1 if语句控制条件追加
@Test
void testGetAll6() {
	//模拟页面传递过来的查询数据
	UserQuery uq = new UserQuery();
	//uq.setAge(10);//模拟用户没有设置下限
	uq.setAge2(20);//模拟用户设置上限的数据

	// null判定
	LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
	if(null!=uq.getAge()){
		lqw.gt(User::getAge,uq.getAge());//age>xx
	}
	if(null!=uq.getAge2()){
		lqw.lt(User::getAge,uq.getAge2());//age
	}
	List<User> users = userDao.selectList(lqw);
	users.forEach(System.out::println);//就是每个对象输出一行
}

万一条件参数很多,代码中会出现大量的if判断,咋办?

1.3.2 条件参数控制
@Test
void testGetAll7() {
    //模拟页面传递过来的查询数据
    UserQuery uq = new UserQuery();
    //uq.setAge(10);//模拟用户没有设置下限
    uq.setAge2(20);//模拟用户设置上限的数据

    // null判定
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());//第一个参数为true, 条件才会加上 age>xx
    lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());//第一个参数为true, 条件才会加上 age
    List<User> users = userDao.selectList(lqw);
    users.forEach(System.out::println);//就是每个对象输出一行
}
1.3.3 条件参数控制(链式编程)

链太长,其实就不建议这么写了

@Test
void testGetAll8() {
    //模拟页面传递过来的查询数据
    UserQuery uq = new UserQuery();
    //uq.setAge(10);//模拟用户没有设置下限
    uq.setAge2(20);//模拟用户设置上限的数据

    // null判定
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    //这条链有点长 不建议这么写
    lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge())
       .lt(null!=uq.getAge2(),User::getAge, uq.getAge2());//第一个参数为true, 条件才会加上
    List<User> users = userDao.selectList(lqw);
    users.forEach(System.out::println);//就是每个对象输出一行
}

2. 查询投影-设置【查询字段、分组、分页】

2.1 查询结果包含模型类中部分属性

只查询部分字段,查出来的每行数据都有少了一些相同的列,就像投影一样
其实查询出来看到的结果都叫投影

@Test
void testGetAll9() {
    //Lambda格式
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    lqw.select(User::getId,User::getName,User::getAge);//只看这三个字段
    List<User> users = userDao.selectList(lqw);
    users.forEach(System.out::println);
}
@Test
void testGetAll10() {
    //普通格式 : 只能写字段名了
    QueryWrapper<User> lqw = new QueryWrapper<User>();
    qw.select("id","name","age");//只看这三个字段
    List<User> users = userDao.selectList(qw);
    users.forEach(System.out::println);
}

在这里插入图片描述
没查的字段全部为null了

2.2 查询结果包含模型类中未定义的属性

这里只能用QueryWrapper不能用LambdaQueryWrapper了
因为LambdaQueryWrapper只能列出POJO中 有属性对应的字段

selectMaps

@Test
void testGetAll11() {
    QueryWrapper<User> qw = new QueryWrapper<User>();
    //查询结果包含模型类中未定义的属性
    qw.select("count(*) as count, tel"); // 和sql语句一样写 也可以取别名  as也可以省略
    List<Map<String, Object>> maps = userDao.selectMaps(qw);//换一个API 直接返回map,key是查询表达式,value是对应的值
    System.out.println(maps);
}

运行结果

[{count=8, tel=12345678910}]

再加个分组玩玩

@Test
void testGetAll12() {
    QueryWrapper<User> qw = new QueryWrapper<User>();
    //查询结果包含模型类中未定义的属性
    // 1)写逻辑字段
    qw.select("count(*) as count, tel");
    // 加分组条件
    qw.groupBy("tel");
    // 2)用selectMaps函数
    List<Map<String, Object>> maps = userDao.selectMaps(qw);
    System.out.println(maps);
}

运行结果

{count=7, tel=12345678910}
{count=1, tel=13912345678}

3. 查询条件设定

问题导入

多条件查询有哪些组合?

  • 范围匹配(> 、 = 、between)
  • 模糊匹配(like)
  • 空判定(null)
  • 包含性匹配(in)
  • 分组(group)
  • 排序(order)
  • ……

3.1 查询条件

  • 用户登录(eq匹配)

查一条数据用selectOne
eg: where name = “张三” and password = “123”;

@Test
void testGetByIdAndPwd() {
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
    // where name = "张三" and password = "123";
    lqw.eq(User::getName,"张三").eq(User::getPassword,"123");
    // 查一条数据用selectOne
    User user = userDao.selectOne(lqw);
    System.out.println(user);
    //User(id=1641093548684476417, name=张三, password=123, age=13, tel=13912345678)
}
  • 购物设定价格区间、户籍设定年龄区间(le ge匹配 或 between匹配)
@Test
void testGetByBetween() {
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
    // 范围查询:lt gt le ge eq between
    lqw.between(User::getAge,10,20);//有between直接做简单范围查询
    List<User> users = userDao.selectList(lqw);
    users.forEach(System.out::println);
}
  • 查信息,搜索新闻(非全文检索版:like匹配)

1、直接like, ‘%’左右都加

 @Test
 void testGetByLike() {
     LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
     lqw.like(User::getName,"张");
     List<User> users = userDao.selectList(lqw);
     users.forEach(System.out::println);
 }
==>  Preparing: SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
==> Parameters: %%(String)

2、likeLeft, ‘%’加左边

@Test
void testGetByLikeLeft() {
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
    lqw.likeLeft(User::getName,"张");//%张
    List<User> users = userDao.selectList(lqw);
    users.forEach(System.out::println);
}
==>  Preparing: SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
==> Parameters: %(String)

3、likeRight, ‘%’加右边

@Test
void testGetByLikeRight() {
    LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
    lqw.likeRight(User::getName,"张");//张%
    List<User> users = userDao.selectList(lqw);
    users.forEach(System.out::println);
}
==>  Preparing: SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
==> Parameters:%(String)
  • 统计报表(分组查询聚合函数)
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.select("gender","count(*) as nums");
qw.groupBy("gender");
List<Map<String, Object>> maps = userDao.selectMaps(qw);
System.out.println(maps);

3.2 查询API

  • 查询API地址:更多查询条件设置参看 https://baomidou.com/pages/10c804/#notinsql
    03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第18张图片

3.3 练习:MyBatisPlus练习

题目:基于MyBatisPlus_Ex1模块,完成Top5功能的开发。

  • 说明:

    ①:Top5指根据销售量排序(提示:对销售量进行降序排序)

    ②:Top5是仅获取前5条数据(提示:使用分页功能控制数据显示数量)

4. 字段映射与表名映射

问题导入

思考表的字段和实体类的属性不对应,查询会怎么样?

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

  • 在模型类属性上方,使用 @TableField 属性注解,通过 value 属性,设置当前属性对应的数据库表中的字段关系。
    03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第19张图片

完全不写mybatis的mapper.xml配置文件了,直接POJO里写,个人感觉这么写好很多,那就这么写

4.2 问题二:编码中添加了数据库中未定义的属性 (防止查询时java代码真的构造sql去查它)

  • 在模型类属性上方,使用 @TableField 注解,通过 exist 属性,设置属性在数据库表字段中是否存在,默认为true。此属性无法与value合并使用。

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第20张图片

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

  • 问题:pwd密码字段,永远都不能被查询出来给前端json,只能作为条件进行比较
  • 解决:在模型类属性上方,使用 @TableField 注解,通过 select 属性:设置该属性是否参与查询。此属性与select()映射配置不冲突。

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第21张图片

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

  • 模型类上方,使用 @TableName 注解,通过 value 属性,设置当前类对应的数据库表名称。

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第22张图片

之前之所以没有这些问题,是因为我们的表名、字段名和Model类的类名、属性名,完全一样的
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第23张图片
接下来修改表结构:

alter table user rename to tbl_user;
alter table tbl_user change password pwd varchar(32) not null; -- 注意上面执行完表名就变了

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第24张图片

再执行之前的查询代码就报错了

修改POJO后再执行代码,又OK了

@Data
@TableName("tbl_user")
public class User {
    /*
        id为Long类型,因为数据库中id为bigint类型,
        并且mybatis有自己的一套id生成方案,生成出来的id必须是Long类型
     */
    private Long id;
    private String name;
    @TableField(value = "pwd",select = false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist = false) //表示online字段不参与CRUD操作
    private Boolean online;
}

少一个注解都会报错,要么user表不存在,要么password字段不存在,要么online字段不存在

四、DML编程控制

0. 环境搭建

其实就相当于将上面的工程mybatisplus_02_dql复制一份为mybatisplus_03_dml,然后接着做
直接复制吧,除了项目名,pom.xml里也要修改名称,mybatisplus_02_dql.iml删了,然后刷新maven,执行任意代码就会重新生成的
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第25张图片
domain下的query包删了,ApplicationTests里面的方法都删了,然后写3个分别增删改的方法

@SpringBootTest
class Mybatisplus02DqlApplicationTests {
    @Autowired
    UserDao userDao;

    @Test
    void testSave() {
        User user = new User();//有了online字段 全参数方法也不好用了,非得给他加上吗?
        user.setName("whu");
        user.setPassword("456");
        user.setAge(130);
        user.setTel("110");
        userDao.insert(user);
    }

    @Test
    void testDelById(){
        Long id =1641451444677042177l;
        userDao.deleteById(id);
    }

    @Test
    void testUpdateById(){
        User user = new User();
        user.setId(1l);
        user.setName("tom666");
        userDao.updateById(user);//updateById不是update 否则全变成tom666了
    }
}

1. id生成策略控制(Insert)

问题导入

主键生成的策略有哪几种方式?

不同的表应用不同的id生成策略

  • 日志:自增(1,2,3,4,……)
  • 购物订单:特殊规则(FQ23948AK3843)
  • 外卖单:关联地区日期等信息(10 04 20200314 34 91)
  • 关系表:可省略id
  • ……

1.1 id生成策略控制(@TableId注解)

  • 名称:@TableId

  • 类型:属性注解

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

  • 作用:设置当前类中主键属性的生成策略

  • 相关属性

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

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第26张图片

以后都用默认的,不指定就是默认,默认就是ASSIGN_ID,雪花算法,同时兼容整数和字符串类型

雪花算法,它至少有如下4个优点:
1.系统环境ID不重复
能满足高并发分布式系统环境ID不重复,比如大家熟知的分布式场景下的数据库表的ID生成。
2.生成效率极高
在高并发,以及分布式环境下,除了生成不重复 id,每秒可生成百万个不重复 id,生成效率极高。
3.保证基本有序递增
基于时间戳,可以保证基本有序递增,很多业务场景都有这个需求。
4.不依赖第三方库
不依赖第三方的库,或者中间件,算法简单,在内存中进行。
雪花算法,有一个比较大的缺点:
依赖服务器时间,服务器时钟回拨时可能会生成重复 id。
在这里插入图片描述


代码演示:
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第27张图片
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第28张图片
然后修改当前主键自增的值:
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第29张图片
再执行ApplicationTest测试类里的save方法
在这里插入图片描述
果然就自增为8了


手动配置成雪花算法,就恢复默认了:
在这里插入图片描述
再执行2次save方法,奇怪的id又回来了:
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第30张图片

1.2 全局策略配置

问题:万一想换一个主键生成策略,每个实体类主键都要写一个重复的注解,好麻烦。
所有表明都是POJO类名加上一个相同的前缀 tbl_ 这样每个实体类都要配置一个@TableName注解,也好麻烦
解决: 全局配置

mybatis-plus:
  global-config:
    db-config:
      id-type: assign_id
      table-prefix: tbl_
id生成策略全局配置

上面yml中的配置id-type: assign_id, 相当于给每个pojo实体类的主键字段上都加上了@TableId(type = IdType.ASSIGN_ID)这个注解,效果如下图:

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第31张图片

表名前缀全局配置

上面yml中的配置table-prefix: tbl_, 相当于给每个pojo实体类上都加上了@TableName("tbl_user")这个注解(user是自适应表名),效果如下图:

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第32张图片
注释掉POJO类里面的注解:
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第33张图片
改用全局配置:
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第34张图片
再去执行ApplicationTest方法,仍然可以执行。

2. 多记录操作(批量Delete/Select)

问题导入

MyBatisPlus是否支持批量操作?

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第35张图片

2.1 按照主键删除多条记录

int deleteBatchIds(@Param(“coll”) Collection idList);
int deleteBatchIds(Collection idList)

//删除指定多条数据
@Test
void testDelByIds(){
    ArrayList<Long> list_id = new ArrayList<>();
    list_id.add(1641456685967360001l);
    list_id.add(1641456810840256513l);
    list_id.add(1642013510106222593l);
    list_id.add(1642015240697143298l);
    userDao.deleteBatchIds(list_id);
}

2.2 根据主键查询多条记录

当然也能按照多Id查询啦,了解一下
List selectBatchIds(@Param(“coll”) Collection idList);
List selectBatchIds(Collection idList);

//查询指定多条数据
@Test
void testGetByIds(){
    ArrayList<Long> list_id = new ArrayList<>();
    list_id.add(1l);
    list_id.add(2l);
    list_id.add(3l);
    list_id.add(4l);
    List<User> users = userDao.selectBatchIds(list_id);
    users.forEach(System.out::println);
}

3. 逻辑删除(Delete/Update)

问题导入

在实际环境中,如果想删除一条数据,是否会真的从数据库中删除该条数据?

  • 删除操作业务问题:业务数据从数据库中丢弃

  • 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第36张图片

3.1 逻辑删除案例

①:数据库表中添加逻辑删除标记字段
-- 添加逻辑删除字段: 默认值0 表示未被删除
alter table tbl_user add deleted int(1) default 0;

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第37张图片

②:实体类中添加对应字段,并设定当前字段为逻辑删除标记字段(指定逻辑删除值)

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第38张图片

//@TableName("tbl_user") // 改为全局配置了
public class User {
    //@TableId(type = IdType.ASSIGN_ID)//其实默认就是雪花算法 不用配 // 改为全部配置了 (其实可以不配)
    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;
    // 逻辑删除字段:1标记当前记录被删除  0标记未被删除
    @TableLogic(value = "0",delval = "1")
    private Integer deleted;
}

点进注解,就可以看到很清楚的中文注释
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第39张图片
没有注释,点下上方提示的download source即可

执行删除操作

@Test
void testDelById(){
    Long id =1l;
    userDao.deleteById(id);
}

在这里插入图片描述

然后查数据库
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第40张图片
有逻辑字段的MP查询:

@Test
void testGetById(){
    Long id =1l;
    User user = userDao.selectById(id);
    System.out.println(user);
}

也查不到了,查询操作只会查没有被逻辑删除的字段了
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第41张图片

现在还想查逻辑删除的记录咋办? 自己写sql就ok啦

@Mapper // 肯定要IOC创建代理对象啊
public interface UserDao extends BaseMapper<User> {
    @Select("select * from tbl_user where id = #{id}") //这条sql语句肯定不会带and deleted = 1条件了
    User getByIdUser(Integer id);
}
@Test
void testGetById3(){
    User user = userDao.getByIdUser(1l);
    System.out.println(user);
}

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第42张图片

MP的条件拼接试了,不行,只能原始mybatis自己写sql了
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第43张图片

trick:全局配置逻辑删除字面值 (推荐)

大量表需要逻辑删除,一个个写麻烦,万一某一个写错了,写反了,很难查错,不如yml里做个全局配置

mybatis-plus:
  global-config:
    db-config:
      table-prefix: tbl_
      # 逻辑删除字段名
      logic-delete-field: deleted
      # 逻辑删除字面值:未删除为0
      logic-not-delete-value: 0
      # 逻辑删除字面值:删除为1
      logic-delete-value: 1

写logic就会自动有代码提示了
现在POJO实体类中只需提供字段就行了
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第44张图片
再执行删除:

@Test
void testDelById(){
    Long id =2l;
    userDao.deleteById(id);
}

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第45张图片

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

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第46张图片

4. 乐观锁(Update)

问题导入

乐观锁主张的思想是什么?

  • 业务并发现象带来的问题:秒杀

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第47张图片
只剩下1个的时候,不能秒杀卖出超过1个,也就不能卖出-1,-2,-10号商品,或者说,同一个商品不能被并发同时读到卖出多次,简单的资源OS同步互斥问题,生产者-消费者问题

4.1 乐观锁案例

①:数据库表中添加锁标记字段
-- 添加锁标记字段: 默认值设为1
alter table tbl_user add version int(11) default 1;

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第48张图片

②:实体类中添加对应字段,并设定当前字段为 锁标记字段
import com.baomidou.mybatisplus.annotation.Version;
public class User {   
    private Long id;	
	//... 其他字段省略
    @Version
    private Integer version;
}

实现原理: update set xxx = x, version = version+1 where version = 1
执行sql语句时,加上 version = version+1 where version = version+1 操作,然后自动锁表能保证数据库的修改操作是互斥的,多个请求秒杀一个商品记录时就只会有一个能秒杀成功了。
.
这些操作具体怎么加呢?答:拦截器

③:配置乐观锁拦截器实现锁机制对应的动态SQL语句拼装

之前分页拦截那里,再加一行代码即可,也即再加一个乐观锁拦截器
不要小看拦截器,它才是真正干活儿的(我们没写具体怎么加,因为MP都帮我们写好了)

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第49张图片

@Configuration //此类能被扫描到 此注解就会将其创建到IOC容器中
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        // 1.定义MP拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        // 2.添加具体的拦截器 (这里加的就是MP内置的分页拦截器)
        //mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        // 3.添加乐观锁的拦截器 (加上之后MP就会自动帮我们添加sql操作,保证千量级的秒杀正常执行了)
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}
④:使用乐观锁机制在修改前必须先获取到对应数据的verion方可正常进行
@Test
void testUpdateById2Version(){
    User user = new User();
    user.setId(3l);//注意挑一个没有被逻辑删除的记录
    user.setName("jerry111");
    user.setVersion(1);//注意必须指定version值
    userDao.updateById(user);
}

在这里插入图片描述

每次都要setVersion,好麻烦,肯定有标准操作
标准操作:先查再改

@Test
void testUpdateById3Version(){
    User user = userDao.selectById(3l);//先查  (注意要查没有被逻辑删除的记录)
    user.setName("tom is a cat");//再直接修改要修改的字段值
    userDao.updateById(user);//这个user里肯定有version了
}

在这里插入图片描述

上面方法执行了2次
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第50张图片

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第51张图片

如何实现加锁的呢

// 模拟秒杀时:A.B两个用户同时想修改同一条数据
@Test
void testUpdateById4Version(){
    User userA = userDao.selectById(3l);//A用户进来  version = 3
    User userB = userDao.selectById(3l);//B用户进来  version = 3 //也是3 秒杀导致的二者读到完全相同的数据

    userB.setName("jerryB");
    userDao.updateById(userB);// B用户先修改记录  version => 4

    userA.setName("jerryA");
    userDao.updateById(userA); // A用户也要修改记录  where version=3 条件不成立了 (A就没抢到)
}

在这里插入图片描述
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第52张图片

框架还得保证多个update操作是互斥的才行,也就是2条update sql语句,必定是前一条执行完毕,下一条才能开始执行。
所幸:当一个语句对表执行update,delete的时候根据条件该表就会锁定,所以我们经常说锁表,只有这条语句执行完提交或者回滚的时候第二条语句才会执行。
mysql会自动锁表,所以不用担心啦,完全可行

五、快速开发-代码生成器

问题导入

如果只给一张表的字段信息,能够推演出Domain、Dao层的代码?

1. MyBatisPlus提供模板

  • Mapper接口模板

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第53张图片
用框架,很多时候就相当于填空造句
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第54张图片

  • 实体对象类模板

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第55张图片

如果只给一张表的字段信息,能够推演出Domain、Dao层的代码?
答:MP提供好模板,我们开发者将空填好,自然就能自动生成代码了呀

03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第56张图片

2. 工程搭建和基本代码编写

  • 第0步:环境搭建
    03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第57张图片
    03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第58张图片
    03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第59张图片

    pom.xml先添加2个MP必要依赖

    
    <dependency>
    	<groupId>com.baomidougroupId>
    	<artifactId>mybatis-plus-boot-starterartifactId>
    	<version>3.4.1version>
    dependency>
    
    
    <dependency>
    	<groupId>com.alibabagroupId>
    	<artifactId>druidartifactId>
    	<version>1.1.16version>
    dependency>
    

    初始环境搭建完毕

  • 第一步:添加代码生成器相关依赖

    
    <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-generatorartifactId>
        <version>3.4.1version>
    dependency>
    
    
    <dependency>
        <groupId>org.apache.velocitygroupId>
        <artifactId>velocity-engine-coreartifactId>
        <version>2.3version>
    dependency>
    

    注意添加完刷新一下maven

  • 第二步:编写代码生成器类

    cn.whu.Generator

    public class Generator {
        public static void main(String[] args) {
            //1. 创建代码生成器对象,执行生成代码操作
            AutoGenerator autoGenerator = new AutoGenerator();
    
            //2. 数据源相关配置:读取数据库中的信息,根据数据库表结构生成代码
            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("1234");
            autoGenerator.setDataSource(dataSource);
            //autoGenerator.setTemplate()//不设置就是使用内置模板
            //autoGenerator.setTemplateEngine()//一般这两个API都用不上,因为自定义模板工作量太大
    
            //3. 执行生成操作
            autoGenerator.execute();
        }
    }
    

    执行完毕,会弹出一个窗口目录,目录下有一个com子目录,下面就有自动生成的一系列代码

    15:02:40.089 [main] DEBUG com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine - 模板:/templates/controller.java.vm;  
    文件:D://\com\baomidou\controller\Tbl_userController.java
    15:02:40.093 [main] DEBUG com.baomidou.mybatisplus.generator.AutoGenerator - ==========================文件生成完成!!!==========================
    

    自动生成了这么多
    03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第60张图片
    来看一下entity.Tbl_user

    package com.baomidou.entity;
    
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableId;
    import java.io.Serializable;
    
    /**
     * 

    * *

    * * @author ${author} * @since 2023-04-01 */
    public class Tbl_user implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Long id; private String name; private String pwd; private Integer age; private String tel; private Integer deleted; private Integer version; // get/set // toString }

    数据库下几个表,就会生成几个实体类

3. 开发者自定义配置

很明显,代码生成的位置,以及命名,主键策略配置,都不对呀
下面就再加一些(全局)配置,来修改其生成位置以及命名

复制一份,命名为CodeGenerator,在这里面加全局配置

  • 设置全局配置
//设置全局配置
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);

说明:在资料中也提供了CodeGenerator代码生成器类,根据实际情况修改后可以直接使用。

最终完整 CodeGenerator.Java

反手就创建成模板文件,下次直接用
(此文件除了包依赖外,不依赖其他任何代码,不需要其他任何配置文件或者拦截器,直接就是一个独立的工具类)

package cn.whu;

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

        // 中间三大配置 让他一步步生成我们想要的代码样式
        //设置全局配置
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java");    //设置代码生成位置
        globalConfig.setOpen(false);    //设置生成完毕后是否打开生成代码所在的目录
        globalConfig.setAuthor("whu");    //设置作者
        globalConfig.setFileOverride(true);     //★小心被覆盖了★ 设置是否覆盖原始生成的文件
        globalConfig.setMapperName("%sDao");    //设置数据层接口名,%s为占位符,指代模块名称 //默认后缀是Mapper %s就是表名
        globalConfig.setIdType(IdType.ASSIGN_ID);   //设置Id生成策略
        autoGenerator.setGlobalConfig(globalConfig);

        //设置包名相关配置
        PackageConfig packageInfo = new PackageConfig();
        packageInfo.setParent("cn.whu");   //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
        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 实体类就自动用lombok了
        autoGenerator.setStrategy(strategyConfig);


        //2. 数据源相关配置:读取数据库中的信息,根据数据库表结构生成代码
        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("1234");
        autoGenerator.setDataSource(dataSource);
        //autoGenerator.setTemplate()//不设置就是使用内置模板
        //autoGenerator.setTemplateEngine()//一般这两个API都用不上,因为自定义模板工作量太大

        //3. 执行生成操作
        autoGenerator.execute();
    }
}

执行这个文件:
03-MyBatisPlus-BaseDao、lombok、分页插件、开启日志、java代码直接指定查询条件、查询投影(部分字段)、查询API地址、类-表映射、逻辑删除、乐观锁、代码生成器(★)_第61张图片
全都按我们希望地生成了特别好的代码,太棒了~

正好到博客末尾了,贴出来看看,体会MP代码生成器的强大吧

UserController

@RestController
@RequestMapping("/user")
public class UserController {

}

UserDao

生成的是xml开发模式,我们用注解开发,就要手动加上@Mapper注解,然后删除下面mapper.xml整个mapper包了

public interface UserDao extends BaseMapper<User> {

}

User

@Data
@EqualsAndHashCode(callSuper = false) //比较时不调用父类的EqualsAndHashCode
@TableName("tbl_user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

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

    private String name;

    private String pwd;

    private Integer age;

    private String tel;

    @TableLogic
    private Integer deleted;

    @Version
    private Integer versio
}

UserMapper.xml
注解开发用不着,可以直接删了

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.whu.dao.UserDao">

</mapper>

Service都帮你抽取并继承了BaseService(这里叫IService),显而易见,用不着,service层逻辑极端复杂,每个项目有每个项目的service业务逻辑,所以这两个文件基本删了自己重新创建。
.
或者留着不删,有些简单的直接用人家的,实在有自己的业务逻辑,再重写对应的方法或者自定义新方法呗
毕竟人家生成了,要用就用,不用搁那放着呗,反正也都是继承来的,也不占地方

IUserService

public interface IUserService extends IService<User> {

}

UserServiceImpl

@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements IUserService {

}

学习时别用,实际开发时疯狂用~

你可能感兴趣的:(后端框架-SSM,#,mybatis,mybatis,spring,boot,java)