03-MyBatisPlus
学习目标
一、MyBatisPlus简介
springBoot是帮助我们快速开发spring程序的
mybatisPlus是帮助我们快速开发mybatis程序的
MyBatisPlus简称MP
1. 入门案例
问题导入
MyBatisPlus环境搭建的步骤?
1.1 SpringBoot整合MyBatisPlus入门程序
①:创建新模块,选择Spring初始化,并配置模块相关基础信息
先创建空白Project作为容器,mybatisplus所有工程都创建成其下面的子module
直接创建boot工程
②:选择当前模块需要使用的技术集(仅保留JDBC)
勾选功能:
③:手动添加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>
注意事项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操作
@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 分页功能接口
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());
}
3.4 解决日志打印过多问题
3.4.1 取消初始化spring日志打印
做法:在resources下新建一个logback.xml文件,名称固定,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration></configuration>
关于logback参考播客:https://www.jianshu.com/p/75f9d11ae011
再执行只有MP日志了,没有Spring日志了
不过其实点具体的方法,效果差不多
3.4.2 取消SpringBoot启动banner图标
resources下application.yml里
spring:main:banner-mode: off # 关闭SpringBoot启动图标(banner)
3.4.3 取消MybatisPlus启动banner图标
现在控制台切不切换到最终方法,打印的日志都差不多了
# 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查询条件进行了封装,使用编程的形式完成查询条件的组合
1.0 环境准备
和前面一样,新建一个一样的boot程序,初始操作也完全一样
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>
删除多余的,然后新建目录结构如下:
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值处理
问题导入
如下搜索场景,在多条件查询中,有条件的值为空应该怎么解决?
先定义个辅助查询类,专门封装用户前端指定的查询条件参数
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);
}
没查的字段全部为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
3.3 练习:MyBatisPlus练习
题目:基于MyBatisPlus_Ex1模块,完成Top5功能的开发。
-
说明:
①:Top5指根据销售量排序(提示:对销售量进行降序排序)
②:Top5是仅获取前5条数据(提示:使用分页功能控制数据显示数量)
4. 字段映射与表名映射
问题导入
思考表的字段和实体类的属性不对应,查询会怎么样?
4.1 问题一:表字段与编码属性设计不同步
- 在模型类属性上方,使用 @TableField 属性注解,通过 value 属性,设置当前属性对应的数据库表中的字段关系。
完全不写mybatis的mapper.xml配置文件了,直接POJO里写,个人感觉这么写好很多,那就这么写
4.2 问题二:编码中添加了数据库中未定义的属性 (防止查询时java代码真的构造sql去查它)
- 在模型类属性上方,使用 @TableField 注解,通过 exist 属性,设置属性在数据库表字段中是否存在,默认为true。此属性无法与value合并使用。
4.3 问题三:采用默认查询开放了更多的字段查看权限
- 问题:pwd密码字段,永远都不能被查询出来给前端json,只能作为条件进行比较
- 解决:在模型类属性上方,使用 @TableField 注解,通过 select 属性:设置该属性是否参与查询。此属性与select()映射配置不冲突。
4.4 问题四:表名与编码开发设计不同步
- 在模型类上方,使用 @TableName 注解,通过 value 属性,设置当前类对应的数据库表名称。
之前之所以没有这些问题,是因为我们的表名、字段名和Model类的类名、属性名,完全一样的
接下来修改表结构:
alter table user rename to tbl_user;
alter table tbl_user change password pwd varchar(32) not null; -- 注意上面执行完表名就变了
再执行之前的查询代码就报错了
修改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代码生成器类,根据实际情况修改后可以直接使用。