当前位置: 首页 > news >正文

从0开始学习Java+AI知识点总结-20.web实战(多表查询)

一、多表关系设计:数据关联的核心逻辑

在数据库设计中,表与表之间的关系是根据业务场景确定的,常见的多表关系分为三种:一对多(多对一)一对一多对多。理解这些关系是实现复杂业务功能的基础。

1.1 一对多(多对一)关系

场景:部门与员工的关系(一个部门包含多个员工,一个员工仅属于一个部门)。

实现方式:在 "多" 的一方(员工表)中添加外键字段,关联 "一" 的一方(部门表)的主键。

案例:部门表与员工表设计

-- 部门表(一的一方)

CREATE TABLE dept (

  id int unsigned PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',

  name varchar(10) NOT NULL UNIQUE COMMENT '部门名称',

  create_time datetime COMMENT '创建时间',

  update_time datetime COMMENT '修改时间'

) COMMENT '部门表';

-- 员工表(多的一方)

CREATE TABLE emp (

  id int unsigned primary key auto_increment comment '主键ID',

  name varchar(10) not null comment '姓名',

  gender tinyint unsigned not null comment '性别(1:男,2:女)',

  dept_id int unsigned comment '关联部门ID', -- 外键,关联dept表的id

  entry_date date comment '入职日期',

  create_time datetime comment '创建时间',

  update_time datetime comment '修改时间'

) comment '员工表';

核心逻辑:通过emp.dept_id关联dept.id,确保员工与部门的归属关系。

1.2 一对一关系

场景:用户与身份证信息的关系(一个用户对应一个身份证,一个身份证仅属于一个用户)。

用途:单表拆分,将高频查询字段与低频字段分离,提升查询效率。

实现方式:在任意一方添加外键字段关联另一方的主键,并设置外键为唯一(UNIQUE)。

案例:用户表与身份证表设计

-- 用户基本信息表

CREATE TABLE tb_user (

  id int unsigned primary key auto_increment comment '主键ID',

  name varchar(10) not null comment '姓名',

  phone char(11) comment '手机号'

) comment '用户基本信息表';

-- 用户身份证表(一对一关联)

CREATE TABLE tb_user_card (

  id int unsigned primary key auto_increment comment '主键ID',

  idcard char(18) not null comment '身份证号',

  issued varchar(20) not null comment '签发机关',

  user_id int unsigned not null unique comment '关联用户ID', -- 外键+唯一约束

  constraint fk_user_id foreign key (user_id) references tb_user(id)

) comment '用户身份证表';

核心逻辑tb_user_card.user_id关联tb_user.id,且user_id设为唯一,确保一对一映射。

1.3 多对多关系

场景:学生与课程的关系(一个学生可选多门课程,一门课程可被多个学生选择)。

实现方式:新增中间表,通过两个外键分别关联双方主键。

案例:学生表、课程表与中间表设计

-- 学生表

CREATE TABLE tb_student (

  id int auto_increment primary key comment '主键ID',

  name varchar(10) comment '姓名',

  no varchar(10) comment '学号'

) comment '学生表';

-- 课程表

CREATE TABLE tb_course (

  id int auto_increment primary key comment '主键ID',

  name varchar(10) comment '课程名称'

) comment '课程表';

-- 中间表(关联学生与课程)

CREATE TABLE tb_student_course (

  id int auto_increment primary key comment '主键',

  student_id int not null comment '学生ID',

  course_id int not null comment '课程ID',

  constraint fk_student_id foreign key (student_id) references tb_student(id),

  constraint fk_course_id foreign key (course_id) references tb_course(id)

) comment '学生课程关联表';

核心逻辑:中间表tb_student_course通过student_idcourse_id分别关联学生表和课程表,实现多对多关系。

1.4 外键约束:数据一致性的保障

外键约束用于保证多表数据的一致性和完整性,但在实际开发中需权衡使用:

  • 物理外键:通过foreign key关键字定义,数据库层面强制关联,缺点是影响增删改效率,不适合分布式场景。
  • 逻辑外键:仅在业务代码中维护关联关系(如查询时通过dept_id过滤),企业开发中更常用,避免数据库性能瓶颈。

建议:非强一致性场景优先使用逻辑外键,通过代码逻辑保证数据关联。

二、多表查询:从关联数据中精准取数

多表查询是获取跨表关联数据的核心操作,需解决笛卡尔积问题并选择合适的查询方式。

2.1 笛卡尔积与关联条件

多表查询时若不添加关联条件,会产生笛卡尔积(两表所有记录的组合),导致数据冗余。需通过关联条件过滤无效数据:

-- 错误:产生笛卡尔积

select * from emp, dept;

-- 正确:添加关联条件

select * from emp e, dept d where e.dept_id = d.id; -- 消除无效组合

2.2 内连接:查询交集数据

内连接仅返回两表中满足关联条件的记录(交集部分),语法有两种:

隐式内连接(常用)

-- 查询员工姓名及所属部门名称

select e.name as emp_name, d.name as dept_name

from emp e, dept d

where e.dept_id = d.id;

显式内连接(推荐,可读性更高)

select e.name as emp_name, d.name as dept_name

from emp e

inner join dept d on e.dept_id = d.id;

适用场景:需同时存在关联数据的场景(如查询有部门的员工)。

2.3 外连接:保留主表全部数据

外连接用于查询主表所有记录,即使关联表中无匹配数据,分为左外连接和右外连接。

左外连接

以左表为主表,返回左表所有记录及右表匹配记录(无匹配则为null):

-- 查询所有员工及所属部门(包括无部门的员工)

select e.name as emp_name, d.name as dept_name

from emp e

left join dept d on e.dept_id = d.id;

右外连接

以右表为主表,返回右表所有记录及左表匹配记录:

-- 查询所有部门及下属员工(包括无员工的部门)

select d.name as dept_name, e.name as emp_name

from emp e

right join dept d on e.dept_id = d.id;

技巧:左外连接和右外连接可互换,只需调整表的顺序。

2.4 子查询:嵌套查询的灵活应用

子查询是嵌套在主查询中的select语句,按返回结果可分为四类:

1. 标量子查询(返回单个值)

用于 where 条件中,作为比较值:

-- 查询最早入职的员工信息

select * from emp

where entry_date = (select min(entry_date) from emp);

2. 列子查询(返回单列多行)

常用innot in判断是否在结果集中:

-- 查询"教研部"和"咨询部"的员工

select * from emp

where dept_id in (select id from dept where name in ('教研部', '咨询部'));

3. 行子查询(返回单行多列)

用于匹配多行多列的条件:

-- 查询与"李忠"薪资和职位相同的员工

select * from emp

where (salary, job) = (select salary, job from emp where name = '李忠');

4. 表子查询(返回多行多列)

作为临时表参与主查询:

-- 查询2006年后入职的员工及部门信息

select e.*, d.name

from (select * from emp where entry_date > '2006-01-01') e

left join dept d on e.dept_id = d.id;

优势:将复杂查询拆分为简单步骤,逻辑更清晰。

三、分页查询:高效展示大量数据

当数据量较大时,分页查询是必备功能,通过限制每页条数提升性能和用户体验。

3.1 原始分页实现

通过limit关键字实现,需计算起始索引总记录数

核心公式
  • 起始索引 = (当前页码 - 1) × 每页条数
  • 总页数 = 总记录数 ÷ 每页条数(向上取整)
代码实现

// 1. 计算总记录数

Long total = empMapper.count();

// 2. 计算起始索引

Integer start = (page - 1) * pageSize;

// 3. 查询当前页数据

List<Emp> empList = empMapper.list(start, pageSize);

// 4. 封装结果

return new PageResult(total, empList);

对应的 Mapper 接口:

-- 统计总记录数

select count(*) from emp e left join dept d on e.dept_id = d.id;

-- 查询分页数据

select e.*, d.name as dept_name

from emp e left join dept d on e.dept_id = d.id

limit #{start}, #{pageSize};

3.2 PageHelper 插件:简化分页操作

PageHelper 是 MyBatis 的分页插件,可自动完成分页逻辑,无需手动计算索引。

步骤 1:引入依赖

<dependency>

  <groupId>com.github.pagehelper</groupId>

  <artifactId>pagehelper-spring-boot-starter</artifactId>

  <version>1.4.7</version>

</dependency>

步骤 2:代码实现

@Override

public PageResult page(Integer page, Integer pageSize) {

  // 设置分页参数

  PageHelper.startPage(page, pageSize);

  // 执行查询(无需手动加limit)

  List<Emp> empList = empMapper.list();

  // 解析分页结果

  Page<Emp> pageResult = (Page<Emp>) empList;

  return new PageResult(pageResult.getTotal(), pageResult.getResult());

}

实现原理

PageHelper 通过拦截 SQL,自动添加count(*)查询总记录数,并在原 SQL 后拼接limit语句,简化开发。

注意事项

  • SQL 语句结尾不要加分号(;),否则插件可能无法正确解析。
  • 仅对紧跟PageHelper.startPage()的第一条 SQL 生效。

四、条件分页与动态 SQL:灵活应对查询需求

实际场景中,用户常需通过多条件筛选数据(如按姓名、性别、日期范围),动态 SQL 可根据条件自动拼接 SQL 语句。

4.1 条件参数封装

当查询参数较多时,建议用实体类封装参数,提升代码可读性:

@Data

public class EmpQueryParam {

  private Integer page = 1; // 默认页码

  private Integer pageSize = 10; // 默认每页条数

  private String name; // 姓名模糊查询

  private Integer gender; // 性别(1:男,2:女)

  @DateTimeFormat(pattern = "yyyy-MM-dd")

  private LocalDate begin; // 入职开始日期

  @DateTimeFormat(pattern = "yyyy-MM-dd")

  private LocalDate end; // 入职结束日期

}

4.2 动态 SQL 实现

使用 MyBatis 的<if><where>标签动态拼接条件:

<select id="list" resultType="com.example.pojo.Emp">

  select e.*, d.name as dept_name

  from emp e left join dept d on e.dept_id = d.id

  <where>

    <!-- 姓名模糊查询:仅当name不为空时拼接 -->

    <if test="name != null and name != ''">

      and e.name like concat('%', #{name}, '%')

    </if>

    <!-- 性别筛选:仅当gender不为空时拼接 -->

    <if test="gender != null">

      and e.gender = #{gender}

    </if>

    <!-- 日期范围查询:仅当begin和end都不为空时拼接 -->

    <if test="begin != null and end != null">

      and e.entry_date between #{begin} and #{end}

    </if>

  </where>

  order by e.update_time desc

</select>

标签作用

  • <if>:条件判断,true 则拼接 SQL 片段。
  • <where>:自动添加where关键字,并去除多余的and/or

4.3 完整条件分页流程

  1. Controller 接收参数

@GetMapping("/emps")

public Result page(EmpQueryParam param) {

  PageResult result = empService.page(param);

  return Result.success(result);

}

  1. Service 层实现

public PageResult page(EmpQueryParam param) {

  PageHelper.startPage(param.getPage(), param.getPageSize());

  List<Emp> empList = empMapper.list(param); // 传入条件参数

  Page<Emp> page = (Page<Emp>) empList;

  return new PageResult(page.getTotal(), page.getResult());

}

  1. Mapper 接口

List<Emp> list(EmpQueryParam param);

五、实战总结与最佳实践

  1. 多表关系设计
    • 一对多:在多的一方加外键(如emp.dept_id)。
    • 一对一:外键 + 唯一约束(如tb_user_card.user_id)。
    • 多对多:通过中间表关联(如tb_student_course)。
  1. 查询方式选择
    • 简单关联查询用内连接 / 外连接。
    • 复杂条件查询用子查询或表连接 + 动态 SQL。
  1. 分页优化
    • 小数据量用原始limit分页。
    • 大数据量用 PageHelper 插件,配合索引提升性能。
  1. 动态 SQL 技巧
    • <if>判断条件,<where>处理关键字。
    • 参数较多时封装实体类,避免方法参数冗余。

通过本文的知识点,你可以轻松实现员工管理系统中的多表关联、分页查询和条件筛选功能。这些技术在电商、CRM 等各类系统中均有广泛应用,掌握后能显著提升后端开发效率。收藏本文,关注后续实战进阶内容,带你深入更多 Java 后端核心技术!

http://www.dtcms.com/a/340680.html

相关文章:

  • HTTPS 原理
  • 模拟tomcat接收GET、POST请求
  • jvm三色标记
  • LLM常见名词记录
  • 《高中数学教与学》期刊简介
  • 109、【OS】【Nuttx】【周边】效果呈现方案解析:workspaceStorage(下)
  • Pytest项目_day20(log日志)
  • Redis--day9--黑马点评--分布式锁(二)
  • 基于门控循环单元的数据回归预测 GRU
  • 【ansible】3.管理变量和事实
  • 拆分工作表到工作簿文件,同时保留其他工作表-Excel易用宝
  • NAS在初中信息科技实验中的应用--以《义务教育信息科技教学指南》第七年级内容为例
  • AI面试:一场职场生态的数字化重构实验
  • 如何使用matlab将目录下不同的excel表合并成一个表
  • Kafka如何保证「消息不丢失」,「顺序传输」,「不重复消费」,以及为什么会发送重平衡(reblanace)
  • 稳压管损坏导致无脉冲输出电路分析
  • 【Linux仓库】进程等待【进程·捌】
  • week3-[分支嵌套]方阵
  • React15.x版本 子组件调用父组件的方法,从props中拿的,这个方法里面有个setState,结果调用报错
  • setup 函数总结
  • 买卖股票的最佳时机III
  • C++STL-list 底层实现
  • Adobe Adobe Illustrator Ai 2025最新版软件安装包下载与详细图文安装教程!!
  • 代码随想录Day57:图论(寻宝prim算法精讲kruskal算法精讲)
  • 【自动化运维神器Ansible】Roles中Tags使用详解:提升自动化效率的利器
  • STM32 外设驱动模块五:DHT11 温湿度传感器
  • 【Express零基础入门】 | 构建简易后端服务的核心知识
  • 如何查看和修改网络接口参数?
  • 计算机网络模型
  • 2025年Java后端最新场景题+八股文面试题