MySQL+MyBatis+MyBatisPlus

啊啊啊啊,马上就要实习工作了,但是我的Mysql之类的一直没有怎么学过,工作肯定要用gg了,来打基础来了

一、MySQL

1.DQL

image-20250930113431637

1.1 分组查询 group by

image-20250930114147353

查询年龄小于45的员工,并根据工作地址分组,获取员工数量大于等于3的工作地址
select workaddness,count(*) from emp where age 45 group by workaddness having count(*) >= 3;
1.2 排序 order by

image-20250930115521070

2. 多表查询

2.1 多表查询

image-20250930175153784

2.2 内连接 (查询两表的交集部分)

image-20250930175822560

查询每一个员工姓名,及关联的名称

  1. 隐式内连接

select emp.name , dept.name from emp , dept where emp.dept_id = dept.id

  1. 显示内连接-INNER JOIN

select e.name,d.name from emp e join dept d on e.dept_id = d.id;

2.3 左外右外链接

–外连接演示 – 1.查询emp表的所有数据,和对应的部门信息(左外连接)–表结构:emp, dept –连接条件:emp . dept_id = dept.id

select e.,d.name from emp e left join dept d on e.dept_id = d.id;

– 2.查询dept表的所有数据,和对应的员工信息(右外连接)

select d.,e.* from emp e right outer join dept d on e.dept_id = d.id;

开发中多用左外连接

2.4 自连接

image-20251002141120888

–自连接 – 1.查询员工及其所属领导的名字

–表结构: emp select a.name , b.name from empIa , emp b where a.managerid = b.id;

–2.查询所有员工 emp及其领导的名字 emp ,如果员工没有领导,也需要查询出来

–表结构: emp a , emp b

select a.name ’员工“,b.name ‘领导’ from emp a left join emp b on a.managerid = b.id;

2.5 联合查询

image-20251002143001226

image-20251002143616606

2.6 子查询

image-20251002145106643

1. 标量子查询

image-20251002145236472

查询销售部的所有员工信息

select * from emp where dept_id = (select id from dept where name = ‘销售部’)

2. 列子查询

image-20251002150020959

image-20251002150642846

3. 行子查询

image-20251002151054178

image-20251002151337534

4. 表子查询

image-20251002151420162

image-20251002151706973

3. 窗口函数

窗口函数([参数]) OVER (
[PARTITION BY <分组列>]
[ORDER BY <排序列 ASC/DESC>]
[ROWS BETWEEN 开始行 AND 结束行]
)
  • PARTITION BY 子句用于指定分组列,关键字:PARTITION BY
  • ORDER BY 子句用于指定排序列,关键字ORDER BY
  • ROWS BETWEEN 子句用于指定窗口的范围,关键字ROWS BETWEEN 即[开始行]、[结束行](这部分在“补充与总结”小节中作补充详细说明)。

其中,ROWS BETWEEN 子句在实际中可能用得相对少一些,因此有部分参考资料的语法描述省略了ROWS BETWEEN 子句,主要侧重于PARTITION BY分组与ORDER BY排序:

3.1 聚合窗口函数

许多窗口函数的教程,通常将常用的窗口函数分为两大类:聚合窗口函数 与 专用窗口函数。聚合窗口函数的函数名与普通常用聚合函数一致,功能也一致。从使用的角度来讲,与普通聚合函数的区别在于提供了窗口函数的专属子句,来使得数据的分析与获取更简便。主要有如下几个:

函数名作用
SUM求和
AVG求平均值
COUNT求数量
MAX求最大值
MIN求最小值

普通聚合函数

SELECT 
t1.*,
t2.avg_revenue FROM sales t1
LEFT JOIN (
SELECT category, AVG(revenue) AS avg_revenue
FROM sales
GROUP BY category
) t2 ON t1.category = t2.category ORDER BY t1.category

聚合窗口函数

SELECT
sales.*,
AVG( revenue ) OVER ( PARTITION BY category ) AS avg_revenue
FROM
sales

3.2 专用窗口函数

常见的专用窗口函数

函数名分类说明
RANK排序函数类似于排名,并列的结果序号可以重复,序号不连续
DENSE_RANK排序函数类似于排名,并列的结果序号可以重复,序号连续
ROW_NUMBER排序函数对该分组下的所有结果作一个排序,基于该分组给一个行数
PERCENT_RANK分布函数每行按照公式 (rank-1) / (rows-1) 进行计算
CUME_DIST分布函数分组内小于、等于当前 rank 值的行数 / 分组内总行数
函数名涵义
CUME_DIST计算一组值中值的累积分布。
DENSE_RANK根据ORDER BY子句为其分区中的每一行分配一个排名。 它为具有相同值的行分配相同的排名。 如果两行或更多行具有相同的等级,则排序值序列中将没有间隙。
FIRST_VALUE返回指定表达式相对于窗口框架中第一行的值。
LAG返回分区中当前行之前的第N行的值。 如果不存在前一行,则返回NULL。
LAST_VALUE返回指定表达式相对于窗口框架中最后一行的值。
LEAD返回分区中当前行之后的第N行的值。 如果不存在后续行,则返回NULL。
NTH_VALUE返回窗口框架第N行的参数值
NTILE将每个窗口分区的行分配到指定数量的已排名组中。
PERCENT_RANK计算分区或结果集中行的百分位数
RANK与DENSE_RANK()函数类似,只是当两行或更多行具有相同的排名时,排序值序列中存在间隙。
ROW_NUMBER为其分区中的每一行分配一个连续整数

例题:力扣178分数排名

05

SELECT 
score,
DENSE_RANK() OVER(ORDER BY score DESC) AS `rank`
FROM Scores
ORDER BY score DESC

4. 零碎知识点

4.1 关键字IN的用法

在 SQL 中,IN 关键字主要用于判断某个值是否在一个指定的集合(可以是值列表、子查询结果等)中,起到过滤数据的作用,以下是其详细用法:

1. 与值列表配合使用

语法:column IN (value1, value2, ..., valuen)作用:判断列 column 的值是否是 value1valuen 中的某一个。示例:

-- 查询部门 ID 为 1 或 2 的员工信息
SELECT * FROM Employee
WHERE DepartmentId IN (1, 2);
2. 与子查询配合使用

语法:column IN (subquery)作用:判断列 column 的值是否在子查询返回的结果集中。示例:

-- 查询薪资等于各部门最高薪资的员工信息(结合子查询)
SELECT * FROM Employee e
WHERE (e.Salary, e.DepartmentId) IN (SELECT MAX(Salary), DepartmentId FROM Employee GROUP BY DepartmentId);
3. INNOT 结合使用

语法:column NOT IN (value1, value2, ..., valuen)column NOT IN (subquery)作用:判断列 column 的值不在指定的集合或子查询结果集中。示例:

-- 查询部门 ID 不是 1 或 2 的员工信息
SELECT * FROM Employee
WHERE DepartmentId NOT IN (1, 2);

需要注意的是,当 IN 配合的子查询返回空结果集时,column IN (subquery) 会返回 FALSE;而 column NOT IN (subquery) 会因为逻辑判断的特殊性(空值参与时结果可能不符合预期),建议谨慎使用,或通过其他方式(如 LEFT JOIN + IS NULL)替代。

二、MyBatisPlus

2.1 常见注解

@TableName:用来指定表名

@TableId:用来指定表中的字段信息

@TableField:用来指定表的普通字段信息

image-20251011162539023

不指定自增长默认为雪花算法

image-20251117150816707

2.2 常见自定义配置

image-20251121095250227

2.3 核心功能

案例1

查询出名字带o的存款大于等于1000的人的id,username

ge大于等于 gt大于 le小于大于 lt 小于

// 构建查询条件
QueryWrapper.<USer> wapper =new QueryWrapper<User>()
.select("id","username","info","balance")
.like("username","o")
.ge("balance",1000);
// 2. 查询
List<User> uers =userMapper.selectList(wapper);
user.forEach(System::println);

更新用户名为jack的用户的余额为2000

User user =new User();
user.setBalance(2000);
QueryWrapper.<USer> wapper =new QueryWrapper<User>().eq("username","jack");
usermapper.update(user,wapper);

案例2

更新id为1,2,4的用户余额扣200

List<Long> ids = List.of( 1L2L,4L);
Updatewrapper<User> wrapper = new Updatewrapper<User>()
.setsql(balance = balance -200")
.in(column: "id", ids);
userMapper.update( null, wrapper) ;

填null,是因为UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分来更新。 第一个参数传 null 是为了告诉 MyBatis-Plus,更新的字段和值已经写在 UpdateWrapper 里,不需要实体对象来更新字段。 当你用 UpdateWrapper 来设置更新的字段和条件时,更新的字段和新值都是写在 UpdateWrapper 里的,这时不需要一个实体传入去覆盖字段的值。因此,entity 参数传 null,表示用 updateWrapper 里的SQL片段来执行更新。

// 1.构建查询条件
LambdaQuerywrapper<User> wrapper = new LambdaQuerywrapper<User>()
.select(User::getId,User::getUsername,User::getInfo,User::getBalance)
.like(User: :getUsername,val: "o"")
.ge(User: :getBalance,val: 1000);
List<User> users = userMapper.selectList(wrapper);users.forEach(System.out : :println);

2.4 自定义SQL

为编号1,2,3的用户扣减指定余额

---------------Controller-------------
// 1.更新条件
List<Long> ids = List.of( 1L2L4L);int amount = 200;
// 2.定义条件
Querywrapper<User> wrapper = new Querywrapper<User> ().in( column: "id",ids);
// 3.调用自定义sQl方法
userMapper.updateBalanceyIds(wrapper,amount) ;

----------Service--------------
void updateBalanceEy.Idis(@Param(Constants.MRAPPER)Querywrapper<User> wrapper,Par am("amount") int amount);

------Mapper-----
<update id="updateBalanceByIds">
UPDATE tb_user SET balance = balance - #{amount} $ {ew.customsqlsegment</update>

image-20251121155028645

2.5 IService

image-20251121173659825

语法

public interface 方法名称 extends Iservice<实体名称>{}
public class 方法名称 extends ServiceImpl<实体名称+Mapper,实体名称> implements 接口名称{}

2.6 静态工具

image-20251124093528473

普通的Isservice接口有可能会出现循环依赖的问题,这时候使用静态工具就可以解决循环依赖的问题

image-20251124094542544

使用DB.lambdaQuery来实现

2.7 枚举处理器

image-20251124095701825

import lombok.Getter;
import com.baomidou.mybatisplus.annotation.EnumValue;
@Getter
public enum UserStatus {
/**
* 正常状态
*/
NORMAL(1, "正常"),
/**
* 冻结状态
*/
FROZEN(2, "冻结");

/**
* 枚举值(MyBatis-Plus映射数据库字段)
*/
@EnumValue
private final int value;

/**
* 状态描述
*/
private final String desc;

/**
* 构造方法
*
* @param value 枚举值
* @param desc 状态描述
*/
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}

配置

configuration:
default-enum-type-handler: com. baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

2.8 JSON处理器

image-20251124101322881

2.9 分页插件

首先,要在配置类中注册MyBatisPlus的核心插件,同时添加分页插件:

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

/**
* MyBatis-Plus 配置类
* 主要配置分页插件等核心插件
*/
@Configuration
public class MybatisConfig {

/**
* 配置 MyBatis-Plus 拦截器(核心插件入口)
* 此处添加分页插件,可扩展添加乐观锁、防全表更新等插件
*
* @return MybatisPlusInterceptor 核心拦截器
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 1. 初始化 MyBatis-Plus 核心拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

// 2. 创建分页插件,指定数据库类型为 MySQL
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置分页上限,最大可查询 1000 条数据
pageInterceptor.setMaxLimit(1000L);

// 3. 将分页插件添加到核心拦截器中
interceptor.addInnerInterceptor(pageInterceptor);

return interceptor;
}
}

通用分页结果

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;

/**
* 通用分页结果DTO
* 用于统一封装分页查询的返回数据
* @param <T> 分页数据的泛型类型,支持任意业务实体
*/
@Data
@ApiModel(description = "分页结果数据")
public class PageDTO<T> {

/**
* 总记录数
*/
@ApiModelProperty(value = "总记录数", example = "100")
private Long total;

/**
* 总页数
*/
@ApiModelProperty(value = "总页数", example = "10")
private Integer pages;

/**
* 当前页码
*/
@ApiModelProperty(value = "当前页码", example = "1")
private Long current;

/**
* 每页显示条数
*/
@ApiModelProperty(value = "每页显示条数", example = "10")
private Long size;

/**
* 分页数据列表
*/
@ApiModelProperty(value = "分页数据列表")
private List<T> records;
}

通用分页工具

分页查询实体PageQuery

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import cn.hutool.core.util.StrUtil;

@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {

@ApiModelProperty("页码")
private Integer pageNo = 1;

@ApiModelProperty("每页条数")
private Integer pageSize = 5;

@ApiModelProperty("排序字段")
private String sortBy;

@ApiModelProperty("是否升序")
private Boolean isAsc;

/**
* 转换为MyBatis-Plus分页对象(基础版)
*/
public <T> Page<T> toMpPage() {
// 1. 分页条件
Page<T> page = Page.of(pageNo, pageSize);
// 2. 排序条件
if (StrUtil.isNotBlank(sortBy)) {
// 不为空时,使用指定排序
page.addOrder(new OrderItem(sortBy, isAsc));
}
return page;
}

/**
* 转换为MyBatis-Plus分页对象(支持自定义排序项)
* @param items 自定义排序项
*/
public <T> Page<T> toMpPage(OrderItem... items) {
// 1. 分页条件
Page<T> page = Page.of(pageNo, pageSize);
// 2. 排序条件
if (StrUtil.isNotBlank(sortBy)) {
// 不为空时,使用指定排序
page.addOrder(new OrderItem(sortBy, isAsc));
} else if (items != null) {
// 为空时,使用默认排序项
page.addOrder(items);
}
return page;
}

/**
* 转换为MyBatis-Plus分页对象(支持指定默认排序)
* @param defaultSortBy 默认排序字段
* @param defaultAsc 是否默认升序
*/
public <T> Page<T> toMpPage(String defaultSortBy, Boolean defaultAsc) {
return toMpPage(new OrderItem(defaultSortBy, defaultAsc));
}

/**
* 默认按创建时间降序排序
*/
public <T> Page<T> toMpPageDefaultSortByCreateTime() {
return toMpPage(new OrderItem("create_time", false));
}

/**
* 默认按更新时间降序排序
*/
public <T> Page<T> toMpPageDefaultSortByUpdateTime() {
return toMpPage(new OrderItem("update_time", false));
}
}

分页结果PageDto

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;

@Data
@ApiModel(description = "分页结果")
public class PageDTO<VO> {

@ApiModelProperty("总条数")
private Long total;

@ApiModelProperty("总页数")
private Long pages;

@ApiModelProperty("集合")
private List<VO> list;

/**
* 将 MyBatis-Plus 的 Page 分页结果转换为 PageDTO
* 并将 PO 列表转换为 VO 列表
*
* @param page MyBatis-Plus 分页查询结果(包含 PO 数据)
* @param clazz 目标 VO 类的 Class 对象
* @param <PO> 原始数据类型(如实体类)
* @param <VO> 目标数据类型(如视图对象)
* @return 转换后的分页结果 PageDTO
*/
public static <PO, VO> PageDTO<VO> of(Page<PO> page, Class<VO> clazz) {
PageDTO<VO> dto = new PageDTO<>();
// 1. 设置总条数
dto.setTotal(page.getTotal());
// 2. 设置总页数
dto.setPages(page.getPages());
// 3. 处理当前页数据(PO 转 VO)
List<PO> records = page.getRecords();
if (CollectionUtils.isEmpty(records)) {
dto.setList(Collections.emptyList());
return dto;
}
// 4. 通过 BeanUtil 复制列表(PO -> VO)
dto.setList(BeanUtil.copyToList(records, clazz));
// 5. 返回转换后的分页结果
return dto;
}
public static <PO, VO> PageDTO<VO> of(Page<PO> p, Function<PO, VO> converter) {
PageDTO<VO> dto = new PageDTO<>();
// 1. 设置总记录数
dto.setTotal(p.getTotal());
// 2. 设置总页数
dto.setPages(p.getPages());
// 3. 处理当前页数据(通过converter转换PO为VO)
List<PO> records = p.getRecords();
if (records == null || records.isEmpty()) {
dto.setList(Collections.emptyList());
return dto;
}
// 4. 流式处理:通过converter函数转换每条数据,收集为VO列表
dto.setList(records.stream()
.map(converter) // 应用转换函数
.collect(Collectors.toList())); // 收集结果
return dto;
}

}
  • 需引入 Bean 复制工具类(如 Hutool 的 BeanUtil 或 Spring 的 BeanUtils 适配工具)
  • 需引入 MyBatis-Plus 核心包(Page 类)