1.1.1【Mybatis概念】
问题1:Mybatis是什么,能干嘛?
MyBatis 是一款优秀的 持久层框架,用于简化 JDBC 开发(对jdbc代码进行了封装)
框架:框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
持久层:JavaEE三层架构:表现层、业务层、持久层
问题2:JDBC存在的缺点?
硬编码
注册驱动、获取连接
SQL语句
操作繁琐
手动设置参数
手动封装结果集
问题3:Mybatis是如何进行规避的?
硬编码可以配置到配置文件
操作繁琐的地方mybatis都自动完成
2.1.1【MyBatisPlus简介】
问题1:MyBatisPlus具体有哪些特性?
MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
官网:https://mybatis.plus/ https://mp.baomidou.com/
MyBatisPlus特性
无侵入:只做增强不做改变,不会对现有工程产生影响
强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD 操作
支持 Lambda:编写查询条件无需担心字段写错
支持主键自动生成
内置分页插件
……
2.2 【标准CRUD制作】
问题1:MyBatisPlus内置通用Mapper提供了哪些API?
问题2:Lombok插件的功能?
可以自动生成实体类的GET、SET方法
Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。
org.projectlombok
lombok
1.18.12
常用注解:
@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;
2.3.1 【标准分页功能制作】
问题1:MyBatisPlus实现分页功能步骤?
分页功能接口
MyBatisPlus分页使用
①:设置分页拦截器作为Spring管理的bean
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
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加分页拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
②:执行分页查询
//分页查询
@Test
void testSelectPage(){
//1 创建IPage分页对象,设置分页参数
IPage page=new Page<>(1,3);
//2 执行分页查询
userDao.selectPage(page,null);
//3 获取分页结果
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("每页显示数:"+page.getSize());
System.out.println("总页数:"+page.getPages());
System.out.println("总条数:"+page.getTotal());
System.out.println("当前页数据:"+page.getRecords());
}
问题2:Mybatis分页底层实现原理?
拦截器
3.1.1 【条件查询的三种格式】
问题1:条件查询的三种格式?
方式一:按条件查询
//方式一:按条件查询
QueryWrapper qw=new QueryWrapper<>();
qw.lt("age", 18);
List userList = userDao.selectList(qw);
System.out.println(userList);
方式二:lambda格式按条件查询
//方式二:lambda格式按条件查询
QueryWrapper qw = new QueryWrapper();
qw.lambda().lt(User::getAge, 10);
List userList = userDao.selectList(qw);
System.out.println(userList);
方式三:lambda格式按条件查询(推荐)
//方式三:lambda格式按条件查询
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.lt(User::getAge, 10);
List userList = userDao.selectList(lqw);
System.out.println(userList);
并且关系(and)
//并且关系
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
//并且关系:10到30岁之间
lqw.lt(User::getAge, 30).gt(User::getAge, 10);
List userList = userDao.selectList(lqw);
System.out.println(userList);
或者关系(or)
//或者关系
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
//或者关系:小于10岁或者大于30岁
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List userList = userDao.selectList(lqw);
System.out.println(userList);
3.2.1 【条件查询null判定】
问题1:搜索场景,在多条件查询中,有条件的值为空应该怎么解决?
if语句控制条件追加
Integer minAge=10; //将来有用户传递进来,此处简化成直接定义变量了
Integer maxAge=null; //将来有用户传递进来,此处简化成直接定义变量了
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
if(minAge!=null){
lqw.gt(User::getAge, minAge);
}
if(maxAge!=null){
lqw.lt(User::getAge, maxAge);
}
List userList = userDao.selectList(lqw);
userList.forEach(System.out::println);
问题2:MyBatisPlus是如何对NULL值进行处理的?
条件参数控制
Integer minAge=10; //将来有用户传递进来,此处简化成直接定义变量了
Integer maxAge=null; //将来有用户传递进来,此处简化成直接定义变量了
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
//参数1:如果表达式为true,那么查询才使用该条件
lqw.gt(minAge!=null,User::getAge, minAge);
lqw.lt(maxAge!=null,User::getAge, maxAge);
List userList = userDao.selectList(lqw);
userList.forEach(System.out::println);
条件参数控制(链式编程)
Integer minAge=10; //将来有用户传递进来,此处简化成直接定义变量了
Integer maxAge=null; //将来有用户传递进来,此处简化成直接定义变量了
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
//参数1:如果表达式为true,那么查询才使用该条件
lqw.gt(minAge!=null,User::getAge, minAge)
.lt(maxAge!=null,User::getAge, maxAge);
List userList = userDao.selectList(lqw);
userList.forEach(System.out::println);
3.3.1 【查询投影】
问题1:如何指定需要查询字段?
查询结果包含模型类中部分属性
/*LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.select(User::getId, User::getName, User::getAge);*/
//或者
QueryWrapper lqw = new QueryWrapper();
lqw.select("id", "name", "age", "tel");
List userList = userDao.selectList(lqw);
System.out.println(userList);
查询结果包含模型类中未定义的属性
QueryWrapper lqw = new QueryWrapper();
lqw.select("count(*) as count, tel");
lqw.groupBy("tel");
List
问题2:如果查询投影无法满足需求,应该如何处理?
在Mapper接口中,自定义SQL和查询接口
3.4.1 【查询条件设置】
问题1:多条件查询有哪些组合?
多条件查询有哪些组合?
范围匹配(> 、 = 、between)
模糊匹配(like)
空判定(null)
包含性匹配(in)
分组(group)
排序(order)
……
查询条件案例:
用户登录(eq匹配)
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
//等同于=
lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
User loginUser = userDao.selectOne(lqw);
System.out.println(loginUser);
购物设定价格区间、户籍设定年龄区间(le ge匹配 或 between匹配)
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
//范围查询 lt le gt ge eq between
lqw.between(User::getAge, 10, 30);
List userList = userDao.selectList(lqw);
System.out.println(userList);
查信息,搜索新闻(非全文检索版:like匹配)
LambdaQueryWrapper lqw = new LambdaQueryWrapper();
//模糊匹配 like
lqw.likeLeft(User::getName, "J");
List userList = userDao.selectList(lqw);
System.out.println(userList);
统计报表(分组查询聚合函数)
QueryWrapper qw = new QueryWrapper();
qw.select("gender","count(*) as nums");
qw.groupBy("gender");
List
3.5.1 【映射匹配兼容性】
问题1:如何处理数据表字段和Java实体属性不匹配问题?
表字段与编码属性设计不同步
在模型类属性上方,使用@TableField属性注解,通过value属性,设置当前属性对应的数据库表中的字段关系。
问题2:如何处理数据表字段不存在,Java实体属性存在问题?
编码中添加了数据库中未定义的属性
在模型类属性上方,使用@TableField注解,通过exist属性,设置属性在数据库表字段中是否存在,默认为true。此属性无法与value合并使用。
问题3:如何实现数据表中字段不参与查询,保证数据安全性?
在模型类属性上方,使用@TableField注解,通过==select==属性:设置该属性是否参与查询。此属性与select()映射配置不冲突。
问题4:如何处理 表名和实体名称不一致问题?
在模型类上方,使用@TableName注解,通过==value==属性,设置当前类对应的数据库表名称。
@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;
}
4.1.1 【id生成策略】
问题1:一个ID一般来说有哪些要求?
全局唯一:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
趋势递增:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。
高可用性:确保任何时候都能正确的生成ID。
带时间:ID里面包含时间,一眼扫过去就知道哪天的交易。
问题2:id生成策略有哪些?
UUID
Java自带的生成UUID的方式就能生成一串唯一随机32(32个16进制数字)位长度数据,而且够我们用N亿年,全球唯一。
数据库自增ID
中间件
开源算法Snowflake
指定机器 & 同一时刻 & 某一并发序列,是唯一的,据此可生成一个64 bits的唯一ID(long)。
id生成策略控制(@TableId注解)
名称:@TableId
类型:属性注解
位置:模型类中用于表示主键的属性定义上方
作用:设置当前类中主键属性的生成策略
相关属性
type:设置主键属性的生成策略,值参照IdType枚举值
全局策略配置
mybatis-plus:
global-config:
db-config:
id-type: assign_id
table-prefix: tbl_
id生成策略全局配置
表名前缀全局配置
问题3:实际工作最常用的id生成策略?
看具体需求,推荐使用snowflake
4.2.2 【多数据操作(删除与查询)】
问题1:mp如何实现批量删除?
//删除指定多条数据
List list = new ArrayList<>();
list.add(1402551342481838081L);
list.add(1402553134049501186L);
list.add(1402553619611430913L);
userDao.deleteBatchIds(list);
问题2:mp如何实现批量删除?
//查询指定多条数据
List list = new ArrayList<>();
list.add(1L);
list.add(3L);
list.add(4L);
userDao.selectBatchIds(list);
4.3.1 【逻辑删除】
问题1:逻辑删除使用场景?
在实际环境中,如果想删除一条数据,是否会真的从数据库中删除该条数据?
删除操作业务问题:业务数据从数据库中丢弃
问题2:如何实现逻辑删除?
逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中
逻辑删除案例
①:数据库表中添加逻辑删除标记字段
②:实体类中添加对应字段,并设定当前字段为逻辑删除标记字段
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@Data
public class User {
private Long id;
//逻辑删除字段,标记当前记录是否被删除
@TableLogic
private Integer deleted;
③:配置逻辑删除字面值
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
逻辑删除本质:逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
注意:如果不想自动带上逻辑删除字段,需要自己编写SQL。
4.4.1 【乐观锁】
问题1:加锁的场景?乐观锁使用场景?
业务并发现象带来的问题:秒杀
mp提供的乐观锁适用于小型的并发
问题2:mp乐观锁 实现流程和底层原理?
原理:update set xx=yy , version=version+1 where version=num;
mp通过拦截器进行增强
操作流程:
先查询version
修改操作(并发修改只有一个能成功,通过version的值来保证)
乐观锁案例
①:数据库表中添加锁标记字段
②:实体类中添加对应字段,并设定当前字段为逻辑删除标记字段
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;
@Version
private Integer version;
③:配置乐观锁拦截器实现锁机制对应的动态SQL语句拼装
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 {
@Bean
public 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=3
User user2 = userDao.selectById(3L); //version=3
user2.setName("Jock aaa");
userDao.updateById(user2); //version=>4
user.setName("Jock bbb");
userDao.updateById(user); //verion=3?条件还成立吗?
}