> 文章列表 > 03-MyBatisPlus

03-MyBatisPlus

03-MyBatisPlus

学习目标

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

一、MyBatisPlus简介

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

MyBatisPlus简称MP

1. 入门案例

问题导入

MyBatisPlus环境搭建的步骤?

1.1 SpringBoot整合MyBatisPlus入门程序

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

先创建空白Project作为容器,mybatisplus所有工程都创建成其下面的子module
03-MyBatisPlus
直接创建boot工程
03-MyBatisPlus
②:选择当前模块需要使用的技术集(仅保留JDBC)
勾选功能:
03-MyBatisPlus
03-MyBatisPlus

③:手动添加MyBatisPlus起步依赖

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>

03-MyBatisPlus

注意事项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.projectlombok</groupId><artifactId>lombok</artifactId>
</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.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTCusername: rootpassword: 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 {@AutowiredUserDao userDao;@Testvoid 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

@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {@AutowiredUserDao userDao;@Testvoid testGetById(){Long id = 1l;User user = userDao.selectById(id);System.out.println(user);// User(id=1, name=tomcat, password=123456, age=12, tel=12345678910)// 果然能查到}@Testvoid testUpdate(){User user = new User();user.setId(1l);user.setName("tomcat");userDao.updateById(user);//查数据库 name真的变成 tomcat了//有值修改 null值不变 //想想如何实现? 1、动态sql  2、先根据id查一下,将user的null属性补上原来的值}@Testvoid testDel(){Long id = 1641093882043678722l;userDao.deleteById(id);//查看数据库,确实删除了}@Testvoid testSave(){User user = new User(null,"张三","123",13,"13912345678");userDao.insert(user);//查数据库确实增加了 //id还是uuid,真不错~}@Testvoid testUserDao() {//GetAllList<User> users = userDao.selectList(null);System.out.println(users);}
}

2. Lombok插件介绍

问题导入

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

  • Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><!-- boot项目肯定不需要写版本号的 --><version>1.18.12</version><!-- 打包的时候不需要,完全可以注上 --><scope>provided</scope>
</dependency>
  • 常用注解:@Data,为当前实体类在编译期设置对应的get/set方法,无参/无参构造方法,toString方法,hashCode方法,equals方法等
import lombok.*;
/*1 生成getter和setter方法:@Getter、@Setter生成toString方法:@ToString生成equals和hashcode方法:@EqualsAndHashCode2 统一成以上所有:@Data3 生成空参构造: @NoArgsConstructor生成全参构造: @AllArgsConstructor4 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

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 {@Beanpublic 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.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTCusername: rootpassword: 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

3.4 解决日志打印过多问题

3.4.1 取消初始化spring日志打印

03-MyBatisPlus

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

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

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

再执行只有MP日志了,没有Spring日志了
不过其实点具体的方法,效果差不多
03-MyBatisPlus

3.4.2 取消SpringBoot启动banner图标

03-MyBatisPlus

resources下application.yml里

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

03-MyBatisPlus
现在控制台切不切换到最终方法,打印的日志都差不多了

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

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

此时完整的yml配置:

server:port: 80spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTCusername: rootpassword: 1234main:banner-mode: off # 关闭SpringBoot启动图标(banner)# 开启MP日志(控制台输出,其中有sql语句)
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:banner: off # 关闭mybatisplus启动图标

三、DQL编程控制

1. 条件查询方式

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

03-MyBatisPlus

1.0 环境准备

和前面一样,新建一个一样的boot程序,初始操作也完全一样
03-MyBatisPlus
03-MyBatisPlus
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>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>

删除多余的,然后新建目录结构如下:
03-MyBatisPlus
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: 80spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTCusername: rootpassword: 1234main:banner-mode: off # 关闭SpringBoot启动图标(banner)# 开启MP日志(控制台输出,其中有sql语句)
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:banner: off # 关闭mybatisplus启动图标

logback.xml

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

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

@SpringBootTest
class Mybatisplus02DqlApplicationTests {@AutowiredUserDao userDao;@Testvoid 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<18List<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<10List<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<10List<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<30lqw.gt(User::getAge,20);//where age>20List<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>20List<User> users = userDao.selectList(lqw);for (User user : users) {System.out.println(user);}
}
1.2.2 或者关系(or)
//或者关系
@Test
void testGetAll5() {// 多条件查询:orLambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();// 中间链接一个or就行了lqw.gt(User::getAge,30).or().lt(User::getAge,10);//where age>30 or age<10List<User> users = userDao.selectList(lqw);for (User user : users) {System.out.println(user);}
}

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

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

1.3 NULL值处理

问题导入

如下搜索场景,在多条件查询中,有条件的值为空应该怎么解决?
03-MyBatisPlus

先定义个辅助查询类,专门封装用户前端指定的查询条件参数
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<xx}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>xxlqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());//第一个参数为true, 条件才会加上 age<xxList<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);
}

03-MyBatisPlus
没查的字段全部为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");// 查一条数据用selectOneUser 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 betweenlqw.between(User::getAge,10,20);//有between直接做简单范围查询List<User> users = userDao.selectList(lqw);users.forEach(System.out::println);
}
  • 查信息,搜索新闻(非全文检索版:like匹配)

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

 @Testvoid 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

  • 更多查询条件设置参看 https://baomidou.com/pages/10c804/#notinsql
    03-MyBatisPlus

3.3 练习:MyBatisPlus练习

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

  • 说明:

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

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

4. 字段映射与表名映射

问题导入

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

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

  • 在模型类属性上方,使用 @TableField 属性注解,通过 value 属性,设置当前属性对应的数据库表中的字段关系。
    03-MyBatisPlus

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

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

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

03-MyBatisPlus

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

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

03-MyBatisPlus

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

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

03-MyBatisPlus

之前之所以没有这些问题,是因为我们的表名、字段名和Model类的类名、属性名,完全一样的
03-MyBatisPlus
接下来修改表结构:

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

03-MyBatisPlus

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

修改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编程控制

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

问题导入

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

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

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

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

  • 名称:@TableId

  • 类型:属性注解

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

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

  • 相关属性

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S2MHT3RN-1680173846640)(assets/image-20210801192449901.png)]

1.2 全局策略配置

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNklSqUB-1680173846644)(assets/image-20210801183128266.png)]

表名前缀全局配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gKllcond-1680173846644)(assets/image-20210801183157694.png)]

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

问题导入

MyBatisPlus是否支持批量操作?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lCWZ4z4A-1680173846644)(assets/image-20210801183334584.png)]

2.1 按照主键删除多条记录

//删除指定多条数据
List<Long> list = new ArrayList<>();
list.add(1402551342481838081L);
list.add(1402553134049501186L);
list.add(1402553619611430913L);userDao.deleteBatchIds(list);

2.2 根据主键查询多条记录

//查询指定多条数据
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(3L);
list.add(4L);
userDao.selectBatchIds(list);

3. 逻辑删除(Delete/Update)

问题导入

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8qR7DeJp-1680173846644)(assets/image-20210801183459750.png)]

3.1 逻辑删除案例

①:数据库表中添加逻辑删除标记字段

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xmnrdhlm-1680173846645)(assets/image-20210801183545635.png)]

②:实体类中添加对应字段,并设定当前字段为逻辑删除标记字段
package com.itheima.domain;import com.baomidou.mybatisplus.annotation.*;import lombok.Data;@Data
public class User {private Long id;//逻辑删除字段,标记当前记录是否被删除@TableLogicprivate Integer deleted;}
③:配置逻辑删除字面值
mybatis-plus:global-config:db-config:table-prefix: tbl_# 逻辑删除字段名logic-delete-field: deleted# 逻辑删除字面值:未删除为0logic-not-delete-value: 0# 逻辑删除字面值:删除为1logic-delete-value: 1

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99KsNTDe-1680173846645)(assets/image-20210801223228871.png)]

4. 乐观锁(Update)

问题导入

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dQgaJv1L-1680173846645)(assets/image-20210801183851887.png)]

4.1 乐观锁案例

①:数据库表中添加锁标记字段

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CZ1QhDmH-1680173846645)(assets/image-20210801183929041.png)]

②:实体类中添加对应字段,并设定当前字段为逻辑删除标记字段
package com.itheima.domain;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;@Data
public class User {private Long id;@Versionprivate Integer version;
}
③:配置乐观锁拦截器实现锁机制对应的动态SQL语句拼装
package com.itheima.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MpConfig {@Beanpublic MybatisPlusInterceptor mpInterceptor() {//1.定义Mp拦截器MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();//2.添加乐观锁拦截器mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return mpInterceptor;}
}
④:使用乐观锁机制在修改前必须先获取到对应数据的verion方可正常进行
@Test
public 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);//1.先通过要修改的数据id将当前数据查询出来User user = userDao.selectById(3L);     //version=3User user2 = userDao.selectById(3L);    //version=3user2.setName("Jock aaa");userDao.updateById(user2);              //version=>4user.setName("Jock bbb");userDao.updateById(user);               //verion=3?条件还成立吗?
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hjZmXk0j-1680173846646)(assets/image-20210801223855681.png)]

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

问题导入

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

1. MyBatisPlus提供模板

  • Mapper接口模板

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vKbsYwcF-1680173846646)(assets/image-20210801224114650.png)]

  • 实体对象类模板

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-11ygvZ0i-1680173846646)(assets/image-20210801184632944.png)]

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

  • 第一步:创建SpringBoot工程,添加代码生成器相关依赖,其他依赖自行添加
<!--代码生成器-->
<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>
  • 第二步:编写代码生成器类
package com.itheima;import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;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("root");autoGenerator.setDataSource(dataSource);//3. 执行生成操作autoGenerator.execute();}
}

3. 开发者自定义配置

  • 设置全局配置
//设置全局配置
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代码生成器类,根据实际情况修改后可以直接使用。