JavaWeb学习全笔记

Web_Note

Preface

2025.7.4

Java学习进入新阶段,Web开发,新老师,新笔记,启动启动.

在线讲义知识梳理: JavaWeb在线讲义

2025.7.5

这次md一级标题不再采用,采用教学知识点为引子

2025.7.7

项目结构比较混乱,代码也零散,所以记笔记的时候,尽量都附带项目地址,方便以后查阅

Eg: D:\BH\Cloud\Web开发\Web_Project\web-prject01\maven-project02

2025.7.9

救命,Emoji都可以格式渲染,喜欢本宫的斜白眼吗?

🙄🙄🙄🙄🙄 白眼

🙄🙄🙄🙄🙄 划线

🙄🙄🙄🙄🙄 斜眼

🙄🙄🙄🙄🙄 打码

2025.7.12

2025.7.12
不得不说最近这些实战都是代码实践,这些就没有啥需要特别注意的了。
做成笔记也不现实,在线讲义也挺好的。
笔记Part只记录知识点吧,不会涉及太多的代码实践。

2025.7.15

Temp_Note 有点撑不住了,现在的笔记知识点群星罗列的,认知偏差的,一个Temp_Note根本不够用。
最后观察几天,用不到的话,Temp_Note我们就说再见吧。
全新集群式小罗列笔记更适合我🤗✌

2025.7.21

教学顺序就是先后端再前端,所以学习笔记也按照教学顺序好了~

Win 技巧

2025.7.6
感觉白用了这么多年windows了,今天突然发现全新技巧 😱

微操 描述
直接拖动 (同一磁盘内) 移动文件
Ctrl + 拖动 复制文件 (所有位置)
Shift + 拖动 跨磁盘移动 (覆盖默认复制行为)
Alt + 拖动 创建快捷方式
Alt + 双击文件 直接显示文件属性 (大小/修改日期)

IDEA设置

2025.7.1 2025.7.5 补课ing

IDEA的设置,居然是分为 全局设置,局部设置的。😨
救命我今天才知道,我真的不行了。

老师的笔记挺不错的,直接参考老师的笔记进行调整吧。
老师的笔记: IDEA设置.md

数据库

2025年7月4日

Web基础

什么是Web

  • Web:万维网
  • 面向C端(customer)
  • 浏览器能够访问的网站

Web网站工作流程

浏览器 → 前端 → 后端 → 数据库服务器

  • 常用浏览器:Chrome、Firefox、Edge
  • 核心技术:Socket服务器
  • 开发方向:前后端开发~

Web开发课程安排

模块 技术栈
前端 H5/C3/JS/Vue/Ajax
后端基础 Maven/HTTP
后端实战 IOC/DI/MySQL
后端原理 SpringBoot
项目部署 Linux/Docker
客户端 PC/APP/小程序 → 前端

框架(矿价)~ 神秘发音来了哦 😄

数据库核心概念

基本定义

  • 数据库(DB):存储和管理数据的仓库
  • DBMS:数据库管理系统
  • SQL:结构化查询语言

关系型数据库 : MySQL(推荐)、Oracle(收费不)

  • 二维表结构
  • 格式统一,便于维护
  • 支持复杂查询

MYSQL 安装

老师提供 Crack 工具,跟着文档走,没有特殊难度

MySQL 登录命令

mysql -h主机名 -P端口 -u用户名 -p密码 
# 不安全(密码暴露在历史记录)
mysql -uroot -pmypassword  

# 推荐方式(交互式输入)
mysql -uroot -p  # 第二行加密输入密码

SQL 语言分类

简称 全称 功能说明
DDL Data Definition Language 定义数据库对象(库/表/字段)
DML Data Manipulation Language 增删改表中的数据
DQL Data Query Language 查询表中的记录
DCL Data Control Language 控制用户权限(了解)

DDL 操作详解

数据库操作

-- 查询所有数据库
SHOW DATABASES;

-- 查询当前库
SELECT DATABASE();

-- 切换数据库
USE 数据库名;

-- 创建数据库
CREATE DATABASE [IF NOT EXISTS] 库名 [DEFAULT CHARSET utf8mb4];

-- 删除数据库
DROP DATABASE [IF EXISTS] 库名;

表操作

创建表语法

CREATE TABLE 表名(
    字段1 类型 [约束] [COMMENT '注释'],
    字段2 类型 [约束] [COMMENT '注释'],
    ...
)[COMMENT '表注释'];

约束类型

约束 关键字 作用
非空约束 NOT NULL 禁止NULL值
唯一约束 UNIQUE 值必须唯一
主键约束 PRIMARY KEY 唯一标识(非空+唯一)
默认约束 DEFAULT 未指定时用默认值
外键约束 FOREIGN KEY 关联其他表
无符号约束 UNSIGNED 禁止负数
自增约束 AUTO_INCREMENT 自动生成递增值

MySQL数据类型

数据类型 说明 存储范围/示例
数值类型
TINYINT 非常小的整数 有符号:-128127<br>无符号:0255
INT 标准整数(常用) 有符号:-21474836482147483647<br>无符号:04294967295
BIGINT 大整数(用于极大数值) 有符号:±9.2×10¹⁸
无符号:0~1.8×10¹⁹
DECIMAL 精确小数(适用于财务计算) DECIMAL(5,2) 可存储 123.45
字符串类型
CHAR 定长字符串(固定分配空间) CHAR(10) 始终占用10字符空间
VARCHAR 变长字符串(按需分配空间) VARCHAR(100) 最大100字符
TEXT 长文本数据(存储文章、日志等) 最大65,535字符
日期时间
DATE 日期值 '2023-08-15'
DATETIME 日期+时间值 '2023-08-15 14:30:00'

表结构修改

命令格式 功能描述
SHOW TABLES; 查询当前数据库的所有表
DESC 表名; 查询表结构
SHOW CREATE TABLE 表名; 查询建表语句
ALTER TABLE 表名 ADD 字段名 类型(长度) [COMMENT '注释'] [约束]; 添加字段
ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度); 修改字段类型
ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT '注释'] [约束]; 修改字段名与类型
ALTER TABLE 表名 DROP COLUMN 字段名; 删除字段
ALTER TABLE 表名 RENAME TO 新表名; 修改表名
DROP TABLE [IF EXISTS] 表名; 删除表

DML 数据操作

INSERT

-- INSERT 插入数据
-- 指定字段插入
INSERT INTO 表 (字段1,字段2) VALUES (值1,值2);

-- 批量插入
INSERT INTO 表 (字段1,字段2) VALUES (值1,值2),(值3,值4),...;

注意:字符串和日期需加引号,插入顺序必须匹配表结构

UPDATE

-- UPDATE 更新数据  
UPDATE 表名 SET 字段1=值1, 字段2=值2 [WHERE 条件];

警告:无WHERE条件 → 全表更新!

DELETE

-- DELETE 删除数据
DELETE FROM 表名 [WHERE 条件];

TRUNCATE

-- TRUNCATE 格式化表
-- 删除表并重建结构(重置自增ID)
TRUNCATE 表名;

DQL 数据查询

基本查询

功能描述 SQL 语法示例
查询多个字段 SELECT 字段1, 字段2, 字段3 FROM 表名;
查询所有字段 SELECT * FROM 表名;
为字段设置别名 SELECT 字段1 [AS] 别名1, 字段2 [AS] 别名2 FROM 表名;
去除重复记录 SELECT DISTINCT 字段列表 FROM 表名;

实际开发过程中不能用 通配符 *
as 可以省略

条件查询

比较运算符

运算符 功能描述
> 大于
>= 大于等于
< 小于
<= 小于等于
= 等于
<>!= 不等于
BETWEEN ... AND ... 在范围内(包含边界)
IN(...) 在指定列表中
LIKE 模糊匹配
IS NULL 是 NULL 值

模糊匹配

  • 这里是LIKE不是= !!!!!!!!!!! 😡🤬🤬
  • _ 匹配单个任意字符(如 _三% 匹配 "张三丰")
  • % 匹配任意多个字符(如 %北京% 匹配 "中国北京市")

逻辑运算符

运算符 功能描述
AND&& 并且(多个条件同时成立)
OR 或 `
NOT! 非(条件取反)

逻辑优先级:NOT > AND > OR,建议用 () 明确优先级

聚合函数

函数 功能
count 统计数量
max 最大值
min 最小值
avg 平均值
sum 求和

null 值不参与聚合函数的运算

统计数量可以使用 count(*)

分组查询

-- 分组查询
select 字段列表 from 表名 [where 条件列表] group by 分组字段名 [having 分组后过滤条件];
  • WHERE:分组前过滤(原始数据)
  • HAVING:分组后过滤(聚合结果)

执行顺序: where > 聚合 > having

排序查询

-- 排序查询
select 字段列表 from 表名 [where 条件列表] [group by 分组字段名 having 分组后过滤条件] 
order by 排序字段 排序方式;

asc 正序
desc 倒序

-- 2025.7.8 错题
-- 根据 入职时间 对公司的员工进行 升序排序 , 入职时间相同, 再按照 ID 进行降序排序
select * from emp order by entry_date asc, id desc ;

分页查询

-- 分页查询
select 字段 from 表名 [where 条件] [group by 分组字段 having 过滤条件] 
[order by 排序字段] limit 起始索引,查询记录数;

起始索引从0开始,起始索引 = (查询页码 - 1)* 每页显示记录数。
分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是LIMIT。
如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 10。

执行顺序

select
	字段列表
from
	表名列表
where
	条件列表
group  by
	分组字段列表
having
	分组后条件列表
order by
	排序字段列表
limit
	分页参数
from - where - group by - 聚合函数 - having - select - order by - limit

SQL 补充知识点

2025.7.12 补充知识点
SQL 还需要多练,以下为自测,补充

GROUP BY

核心原则

使用 GROUP BY 时,SELECT 子句只能包含:

  • 分组列

  • 聚合函数(COUNT/SUM/AVG/MAX/MIN

小案例:员工表 test

id name salary dept_id
1 舒勇达 10000 1
2 张浩飞 8000 1
3 何阳 12000 2
4 毛润阶 7000 NULL
# 查看所有数据
select * from test;
# 分组查询,SELECT子句只能包含分组列or聚合函数
select dept_id from test GROUP BY dept_id;
# dept_id 分组,同一组存在多个name值会导致数据丢失
select dept_id,name from test GROUP BY dept_id;

COUNT

核心原则

  • COUNT(*) → 统计所有行(含NULL行)
  • COUNT(列) → 统计该列非NULL

案例:部门员工统计

employee

id name dept_id
1 张浩飞 1
2 何阳 1
3 许一智 2
4 沈键瑚 (Null)

department

id dept_name
1 销售部
2 技术部
3 行政部

查询语句

-- 查询比较
SELECT 
    d.dept_name AS 部门,
    COUNT(*), -- 统计所有行
    COUNT(e.id) -- 忽略NULL值
FROM department d
LEFT JOIN employee e ON d.id = e.dept_id
GROUP BY d.dept_name;

结果对比

部门 COUNT(*) COUNT(e.id)
销售部 2 2
技术部 1 1
行政部 1 0

关键解析

行政部问题

  • 无真实员工 → e.id 全为 NULL
  • COUNT(*) 计数空行 → 错误显示1人
  • COUNT(e.id) 忽略 NULL正确显示0人

COUNT(列) 优势

  • 自动过滤 NULL
  • 精确统计 有效数据行
  • 特别适合外连接统计

Maven

日期: 2025.7.5
代码: D:\BH\Cloud\Web开发\Web_Project\web-prject01\maven-project02

Maven概述

什么是Maven

  • Maven: 依赖管理和项目构建的工具
  • Apache 开源软件基金会
  • 作用: 统一目录,依赖管理,项目构建

Maven 安装与配置

解压安装

下载:

下载地址: https://maven.apache.org/download.cgi
教学采用的是 apache-maven-3.9.4-bin.zip

目录结构:

image-20250705195642762
  • bin目录: 存放的是可执行命令
  • conf目录: 存放Maven的配置文件
  • lib目录: 存放Maven依赖的jar包

配置仓库

新建本地仓库

Maven安装目录 新建一个本地目录 mvn_repo (本地仓库,用来存放jar包)

image-20250705195755916

配置本地仓库

进入conf目录修改settings.xml配置文件

定位到53行左右, 建立 <localRepository> 标签,标识本地仓库路径

<localRepository> 本地仓库路径 </localRepository>

配置阿里云镜像

进入conf目录修改settings.xml配置文件

定位到160行左右, 在mirrors 标签下,添加子标签 <mirror> ,标识

    <mirror>
        <id>alimaven</id>
        <name>aliyun maven</name>
        <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        <mirrorOf>central</mirrorOf>
    </mirror>

配置Maven环境变量

环境变量

  1. 在系统变量处新建一个变量
    变量名: MAVEN_HOME
    变量值: 设置为maven的解压安装目录 (解压安装目录即可,不需要进入bin)

    D:\BH\Local\Develpop_Tools\Maven\apache-maven-3.9.4
    
  2. PATH中新建一个: %MAVEN_HOME%\bin

  3. cmd输入测试代码,验证是否成功

    mvn -v
    

IDEA集成Maven

待完善...

03-Web后端基础(Maven基础) - 飞书云文档

待个der,永远都不会被完善了呵呵 2025.7.15🙄

XML

XML 概念

可扩展标记语言 (Extensible Markup Language)
核心用途: 结构化存储和传输数据 (配置文件最常见)

XML 规则

<?xml version="1.0" encoding="UTF-8"?> <!-- 固定第一行 -->
<root>                                 <!-- 唯一根元素 -->
  <tag>内容</tag>                       <!-- 成对标签 -->
  <selfClosingTag/>                   <!-- 自闭合标签 -->
  <!-- 注释写在这里 -->                 <!-- 注释格式 -->
  <element attribute="value">...</element> <!-- 属性定义 -->
</root>
  • 第一行声明不可修改
  • 根元素必须且唯一
  • 所有内容必须包含在标签内
  • 严格区分大小写, 必须正确嵌套

XML 约束

约束: 当前这个xml文件中能写什么
约束分为两类: DTD(Mybatis)、Schema(Spring)

依赖管理

pom.xml

Project Object Model (项目对象模型)
符合 Maven XSD 约束的 XML 文件

依赖配置

基本结构

<dependencies>
  <dependency>
    <groupId>junit</groupId>     <!-- 依赖组织名 -->
    <artifactId>junit</artifactId> <!-- 依赖项目名 -->
    <version>4.13.2</version>   <!-- 依赖版本 -->
    <scope>test</scope>          <!-- 作用域 -->
  </dependency>
</dependencies>

项目坐标

GAV:项目唯一标识

  • groupId:公司/组织域名反写(如 com.公司名)
  • artifactId:项目名(小写+连字符)
  • version:版本号(主.次.修订)
<!-- 公司/组织域名倒序 (全小写, 点分隔) -->
<groupId>com.itheima</groupId>  
<!-- 项目/模块名 (推荐 xxx-yyy 格式) -->
<artifactId>maven-demo01</artifactId>  
<!-- 版本号定义规则 -->
<version>1.0-SNAPSHOT</version> <!-- SNAPSHOT = 开发版 -->
<version>1.0</version>          <!-- RELEASE/无后缀 = 稳定版 -->

JDK 配置

<properties>
    <maven.compiler.source>17</maven.compiler.source>  <!-- 编译JDK -->
    <maven.compiler.target>17</maven.compiler.target>  <!-- 运行JDK -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- 编码 -->
</properties>

依赖范围

scope值 主程序 测试程序 打包运行 范例
compile (默认) log4j
test junit
provided servlet-api
runtime jdbc驱动

依赖排除

<dependency>
    <groupId>org.example</groupId>
    <artifactId>example-lib</artifactId>
    <exclusions>
        <exclusion>  <!-- 排除不需要的传递依赖 -->
            <groupId>com.unwanted</groupId>
            <artifactId>unwanted-module</artifactId>
        </exclusion>
    </exclusions>
</dependency>

生命周期

阶段 作用
clean 清理 (删除target目录)
compile 编译源代码
test 执行单元测试
package 打包 (jar/war)
install 安装到本地仓库
deploy 部署到远程仓库

单元测试

目的

  • 单元测试的目的: 保证每个单元最小方法功能的正确性
  • 如何编写单元测试: 让测试的方法的覆盖率达到99%以上
  • 注解: @Test

测试覆盖率有什么要求:

每一个程序员写完每一个业务功能方法都需要进行测试,只有这个测试方法通过了,才能进行下一步业务编写,我们测试通过的标准是当前这个测试方法的覆盖率达到99%以上。

测试分类

测试类型 执行者 覆盖目标
单元测试 开发者 方法级 (99%)
集成测试 开发者 模块交互
系统测试 测试工程师 完整系统
验收测试 客户/需求方 业务需求

白盒测试/黑盒测试/灰盒测试

自测,否则项目经理会屌你的🙄🙄🙄

JUnit 实践

依赖配置

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.9.1</version>
    <scope>test</scope> <!-- 仅测试环境有效 -->
</dependency>

命名规范

  • 测试类: 被测试类名 + Test (如 UserServiceTest)
  • 测试方法: test + 被测试方法名 (如 testGetAge)

核心注解

注解 作用
@Test 标记测试方法
@BeforeEach 每个测试方法前执行 (初始化资源)
@AfterEach 每个测试方法后执行 (释放资源)
@BeforeAll 所有测试前执行一次 (静态方法)
@AfterAll 所有测试后执行一次 (静态方法)
@ParameterizedTest 参数化测试 (替代@Test)
@ValueSource 参数化测试的参数来源 (配合使用)
@DisplayName 指定测试类、测试方法显示的名称

规范: 注释应该位于注解前面

断言

断言の本质

JUnit提供了一些辅助方法,用来帮我们确定被测试的方法是否按照预期的效果正常工作,
这种方式称为断言。

Assertions 类

  • org.junit.jupiter.api.Assertions
  • 包含一组静态断言方法
  • 可以通过静态导入简化使用:
    import static org.junit.jupiter.api.Assertions.*;

断言方法

断言方法 用途
assertEquals(expected, actual) 验证两个值相等
assertNotNull(object) 验证对象非空
assertTrue(condition) 验证条件为真
assertSame(expected, actual) 验证是同一个对象引用

断言方法特性

  • 无返回值: 所有方法均为 void
  • 成功时: 静默通过, 继续执行后续代码
  • 失败时: 抛出 AssertionFailedError 异常
  • 核心作用: 自动化验证代码行为是否符合预期

JUnit 统计原理

  • 捕获断言抛出的 AssertionFailedError
  • 标记当前测试方法为失败状态
  • 继续执行其他独立测试方法
  • 最终报告显示:
    • 总测试方法数
    • 失败测试方法数
    • 通过测试方法数

Assertions工具类, 提供 assertEquals()静态方法
断言失败抛出的异常会被 JUnit 框架捕获并记录为测试失败

AssertThrows 补

2025.7.6
这个方法上课没讲,但是ds说挺重要的,源码勉强能理解,自己小记一下。

源码解析

public static <T extends Throwable> T assertThrows(
    Class<T> expectedType, 
    Executable executable
) {
    return AssertThrows.assertThrows(expectedType, executable)
}

泛型声明 <T extends Throwable>

声明泛型类型 T
约束: T 必须是 Throwable 或其子类
确保方法只处理可抛出对象

关键参数 Class<T> expectedType

Class 是 Java 特殊类 表示类型信息
示例: String.class 表示 String 类型
作用: 运行时传递具体类型信息
<T> 表示具体异常类型 如 DateTimeParseException.class

行为参数 Executable executable

本质: 函数式接口 (类似 Runnable)
定义:

@FunctionalInterface
public interface Executable {
    void execute() throws Throwable
}

使用场景: 封装可能抛出异常的代码

() -> { 
    empService.getAge("110110201019124726") 
}

返回值 T

作用: 返回实际抛出的异常对象
价值: 可进一步验证异常详情

Exception ex = assertThrows(...)
assertEquals("Invalid month value: 19", ex.getMessage())

报错机制

使用了 AssertThrows 方法,如果 类型不匹配 或 未抛出异常,测试代码会报错。

郭讲

开发准则

约定 > 配置 > 编码

  • 约定: Maven 目录结构
  • 配置: pom.xml 依赖和插件
  • 编码: 业务逻辑实现

POJO Domain

  • POJO: 纯 Java 对象 (字段 + getter/setter), 无框架依赖
  • Domain: 承载业务逻辑的对象 (如 User/Order), 通常用 POJO 实现

原子性

老葛没讲过,DeepSeek一下

在 Java 中,原子性(Atomicity) 是并发编程的核心概念之一,指一个操作或一系列操作不可被中断地执行,要么全部成功完成,要么完全不执行(如同从未发生过),不会出现“部分执行”的中间状态。这一特性在多线程环境下对保障数据一致性至关重要。

为什么需要原子性?

当多个线程并发访问共享资源(如变量)时,非原子操作可能导致竞态条件(Race Condition)。例如:

int count = 0;
// 线程 A 和线程 B 同时执行以下操作:
count++; // 非原子操作!

看似简单的 count++ 实际包含三个步骤:

  1. 读取 count 的当前值
  2. 将值 +1
  3. 写回新值到内存

若线程 A 和 B 同时执行

  • 线程 A 读 count=0
  • 线程 B 读 count=0
  • 线程 A 写 count=1
  • 线程 B 写 count=1(结果应为 2,实际得到 1)

Java 中实现原子性的方式

1. 使用 synchronized 关键字

  • 通过互斥锁保证代码块/方法的原子性。
private int count = 0;

public synchronized void increment() {
    count++; // 现在操作是原子的
}

2. 使用 java.util.concurrent.atomic 包中的原子类

  • 基于 CAS(Compare-And-Swap)指令实现无锁原子操作。
  • 常见类:AtomicInteger, AtomicLong, AtomicBoolean 等。
private AtomicInteger count = new AtomicInteger(0);

public void increment() {
    count.incrementAndGet(); // 原子自增
}

3. 使用显式锁(如 ReentrantLock

private Lock lock = new ReentrantLock();
private int count = 0;

public void increment() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();
    }
}

4. volatile 关键字(有限原子性)

  • 仅保证单次读/写操作的原子性(如 volatile int a = 1;)。
  • 不适用于复合操作(如 i++i = i + 10)。

原子性 vs 可见性 vs 有序性

原子性是并发安全的三大支柱之一:

  1. 原子性:操作不可中断。
  2. 可见性:一个线程修改共享变量后,其他线程能立即看到新值(通过 volatile 或锁实现)。
  3. 有序性:防止指令重排序(通过 volatilesynchronized 实现)。

关键注意事项

  1. 复合操作需显式同步
    即使单次读写是原子的(如 long 在 64 位 JVM 上),类似 check-then-act(如 if (a < 10) a++)仍需锁或原子类。
  2. 原子类的高效性
    在低竞争场景下,AtomicInteger 等性能优于锁(基于 CPU 硬件的 CAS 指令)。
  3. 数据库事务的原子性
    区别于并发编程,数据库事务的原子性指多个 SQL 操作的整体原子性(通过提交/回滚机制实现)。

示例对比

// 非原子操作(线程不安全)
public class UnsafeCounter {
    private int count;
    public void increment() { count++; } // 可能丢失更新
}

// 使用 synchronized 保证原子性
public class SafeCounter {
    private int count;
    public synchronized void increment() { count++; }
}

// 使用 AtomicInteger(推荐)
public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);
    public void increment() { count.incrementAndGet(); }
}

结论:在 Java 中确保原子性是多线程编程的基础,正确使用锁、原子类或 volatile(仅限单操作)可避免数据竞争,保障程序正确性。

Web 基础知识

哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米哈吉米😸

SpringBoot Web 入门

Web 基础知识

静态资源: HTML CSS JavaScript
动态资源: Spring 框架

B/S 架构

  • Browser/Server 浏览器/服务器架构
  • 客户端只需浏览器 应用逻辑和数据在服务端
  • 维护方便 体验一般

C/S 架构

  • Client/Server 客户端/服务器架构
  • 需单独开发客户端
  • 体验流畅 开发维护复杂

SpringBoot

Spring Framework

  • Java 开发核心框架
  • 配置繁琐 入门难度大

**Spring Boot **

  • 简化配置 快速开发
  • 内置 IOC DI 机制
  • 官方推荐 企业主流

入门程序

开发步骤
1 创建 SpringBoot 工程

  • 勾选 Web 开发依赖

2 编写控制器

@RestController  
public class HelloController {  
    @GetMapping("/hello")  
    public String hello() {  
        return "Hello World";  
    }  
}  

3 启动应用

  • 自动启动内嵌 Tomcat
  • 默认端口 8080

4 访问测试
http://localhost:8080/hello

SpringBoot官方初始化器构建Spring

  • 选择 Spring Boot - 设置 Name - 选择 Maven
  • 设置版本 - 添加 Web - Spring Web - Create
  • 等待自动初始化结束即可
image-20250707101122009 image-20250707101129095

阿里云初始化器

  • Server URL 需要从官方的修改成阿里云的
  • 阿里云的结构和版本 和官方会存在差异
  • 以后上课都统一都用阿里云的
image-20250707101757528 image-20250707101422692 image-20250707101426992

HTTP协议

全称: Hyper Test Transfer Protocol 超文本传输协议

URI: Uniform Resource Identifier 统一资源标识符

URL: Uniform Resource Locator 统一资源定位符

OSI 模型:

  • 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层

TCP/IP 模型:

  • 应用层 HTTP
  • 传输层 TCP
  • 网络层
  • 物理层

HTTP 核心特点:

  • 基于TCP协议: 面向连接,可靠传输
  • 请求-响应模型: 客户端发起请求 → 服务器返回响应
  • 无状态协议: 依赖 Cookies/Session 维持状态

HTTP 请求数据格式

POST /brand HTTP/1.1             // 请求行
Host: www.example.com            // 请求头
Content-Type: application/json   // 关键头字段
Content-Length: 56
                                 // 空行 (分隔头与体)
{"name": "NewBrand"}             // 请求体

请求协议

说明 内容
请求行 请求数据第一行 方法(GET/POST) + 资源路径 + HTTP版本
请求头 第二行开始 格式: Key: Value
空行 分隔头部与主体
请求体 POST请求 存放请求参数 json

请求头

请求头 含义
Host 表示请求的主机名
User-Agent 浏览器版本。
Accept 表示浏览器能接收的资源类型,如text/,image/或者/表示所有;
Accept-Language 表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
Accept-Encoding 表示浏览器可以支持的压缩类型,例如gzip, deflate等。
Content-Type 请求主体的数据类型
Content-Length 数据主体的大小(单位:字节)

GET & POST

特性 GET POST
参数位置 URL 末尾 (?key=value) 请求体中
数据长度限制 有 (受 URL 长度限制) 理论上无
安全性 较低 (参数暴露) 相对较高

HTTP 响应数据格式

HTTP/1.1 200 OK                 // 响应行
Content-Type: application/json  // 响应头
Set-Cookie: sessionId=abc123    // 关键头字段
                                 // 空行
{"data": "success"}             // 响应体

响应协议

说明 内容
响应行 响应数据的第一行 HTTP版本 + 状态码(如200) + 状态描述(如OK)
响应头 响应数据的第二行开始 格式: key:value
空行 分隔头部与主体
响应体 响应数据的最后一部分 存储响应的数据

响应头

响应头 说明
Content-Type 必需, 声明响应体的数据类型(MIME Type)
Content-Length 声明响应体的字节长度
Content-Encoding 声明响应体使用的压缩算法
Cache-Control 重要, 指示客户端/代理如何缓存该响应
Set-Cookie 重要, 指示浏览器存储 Cookie

状态码

分类 含义 常见状态码
1xx 响应中 临时状态码
2xx 成功 200 OK, 201 Created
3xx 重定向 301 永久, 302 临时
4xx 客户端错误 400 请求错误, 401 未认证, 404 资源不存在
5xx 服务器错误 500 服务器异常, 502 网关错误

开发Web案例

@RestController
@RestController = @Controller + @ResponseBody
Spring MVC 中用于构建 RESTful Web 服务的复合注解

@Controller
标记当前类是一个控制器
用于接收并处理客户端 HTTP 请求

@ResponseBody
表示方法的返回值是以响应体的形式返回
如果是对象或者集合,则会转成JSON字符串返回给客户端
该注解标记在类上,等同于该类中每个方法都标记了该注解

@RequestMapping
映射请求到处理方法
value: 定义访问路径(支持类级与方法级路径组合)
method: 可指定 HTTP 方法(GET/POST/PUT/DELETE)
Eg: @RequestMapping(value = "/depts",method = RequestMethod.GET)

分层解耦

三层架构

Controller 层 (控制层 / 表现层)

  • 注解: @Controller

  • 接收前端发送的 HTTP 请求

  • 对请求进行处理,并响应数据。

Service 层 (业务逻辑层 / 服务层)

  • 注解: @Service
  • 处理具体业务逻辑

Dao 层 (数据访问层 / 持久层)

  • 数据访问层(Data Access Object)
  • 注解: @Repository
  • 负责数据访问操作

实现解耦

声明 Bean

基础注解: @Component (通用 Bean 声明)

衍生注解:

  • @Controller 标注控制层类
  • @Service 标注业务层类
  • @Repository 标注数据访问层类,将来用Myatis后会换成@Mapper
  • 以上注解可以使用value来指定bean的名称,默认为类名首字母小写

用法:

  • 映射创建对象,交给Spring容器管理,生成Bean对象

依赖注入

  • 核心注解 @Autowired
  • 作用 Spring 容器自动将匹配的 Bean 注入到标记属性/构造器/方法参数中
  • 效果 上层对象无需创建下层实例 声明依赖即可自动注入

组件扫描 (生效前提)

核心注解: @ComponentScan

扫描规则:

  • 默认包含在 @SpringBootApplication
  • 扫描范围: 启动类所在包及其所有子包

解耦本质

@Component 生产 Bean,@Autowired 消费 Bean,容器自动匹配连接,彻底解耦层间依赖。

1. 组件注册@Component

  • Spring 容器扫描到带 @Component 的类
  • 自动创建实例并存入容器(Map 结构)

2. 依赖声明@Autowired

  • 在类中标记需要注入的字段/构造器
  • 向容器声明:"我需要这个类型的实例"

3. 容器协作

  • 初始化 Bean 时发现 @Autowired 标记
  • 自动查找匹配类型的 Bean
  • 注入依赖(赋值给标记的字段/参数)
步骤 解耦效果
@Component 注册 对象创建交给容器 → 消除 new 耦合
@Autowired 声明 对象获取由容器注入 → 消除查找耦合

以上注解可以使用value来指定bean的名称,默认为类名首字母小写?
这个也是可圈可点呀,有待补充.

项目结构很重要

项目结构目录很重要

容器设计思想

23种设计原则

开闭原则: 对拓展开放, 对修改关闭(核心设计思想)

高内聚低耦合:

  • 耦合: 模块/层间的依赖关联程度(越低越好)
  • 内聚: 模块内部功能联系的紧密程度(越高越好)

控制反转 IOC

  • IOC: Inversion Of Control
  • 核心思想: 将对象的创建控制权从程序内部转移到外部容器
  • IOC 容器: Spring 框架的核心容器, 负责管理对象的生命周期和依赖关系
  • Bean: IOC 容器中创建和管理的对象统称为 Bean

依赖注入 DI

  • DI: Dependency Injection

  • 与 IOC 的关系: DI 是 IOC 的具体实现方式

  • 核心思想: 容器在运行时动态为对象提供依赖资源(如其他 Bean)

  • 实现方式:

    // 示例: 通过注解注入依赖
    @RestController
    public class UserController {
        @Autowired  // Spring 按类型注入
        private UserService userService;
    }
    

DI 注入冲突

问题场景

当容器中存在多个相同类型的 Bean 时, 注入会失败(Spring 无法确定使用哪个实现)

解决方案

@Primary 注解

当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。

@Primary
@Service
public class UserServiceImpl implements UserService {
}

@Qualifier 注解

  • 指定当前要注入的bean对象
  • 在@Qualifier的value属性中,指定注入的bean的名称。
  • 需配合 @Autowired 使用
@Autowired
@Qualifier("userServiceImpl")  // 指定 Bean 名称
private UserService userService;

@Resource 注解

  • JavaEE 规范提供的注解, 默认按 Bean 名称注入
  • 通过name属性指定要注入的bean的名称
@Resource(name = "userServiceImpl")  // 按名称注入
private UserService userService;

推荐实践:

  • 开发推荐: @Resource(解耦性强, 符合 Java 标准)
  • 学习推荐: @Qualifier(帮助理解 Spring 注入机制)

注解对比

注解 来源 注入方式 解决策略
@Autowired Spring 优先按类型 → 失败时按变量名 需配合 @Qualifier
@Qualifier Spring 直接指定 Bean 名称 配合 @Autowired 使用
@Resource JavaEE 优先按名称 → 失败时按类型 通过 name 属性指定

@Autowired 注入逻辑

  1. 按类型匹配 Bean
  2. 找到多个类型时, 将变量名作为 Bean 名称尝试匹配
  3. 匹配失败则报错 NoUniqueBeanDefinitionException
  4. 出现第三步错的时候,就需要配合 @Qualifier,来指定Bean名称了

Maven依赖

Target 转移: 收录至末尾

Java 操作数据库

日期: 2025.7.8
后记: 今天的笔记难得的AI味较少,手工量大~
后后记: md破笔记老娘要整理死过去了

Maven工程的细节

工程,IDEA直接打开即可

工程内部会有两个目录:

  • .idea: 是当前环境打开了项目后,自动由IDEA根据当前环境生成的一个目录,内部是一些IDEA相关的信息

  • target: Maven工程编译后的产物:包括classes文件和配置文件

我们在把代码给别人的时候,如果希望别人直接能够打开你的项目并且使用别人的环境,
需要删除.idea目录和target

Maven项目打开后,一定要等到右侧的 M 图标出现,且里面没有爆红。

爆红最大原因: 当前工程的Maven环境没有配置正确

Spring初始化

项目启动图

JDBC

JDBC 概念

Java DataBase Connectivity (JDBC) 是 Java 操作关系型数据库的标准 API
Java操作数据库的一套规范(接口及API)

ORM 映射原理

Object Relation Mapping (对象关系映射)

数据库结构 Java 对象
表名 类名
列[字段] 类属性
行[记录] 对象实例
字段类型 Java 数据类型
多单词下划线命名 驼峰命名

ResultSet

光标控制

  • next(): 移动光标到下一行, 返回 boolean 表示是否有数据
  • 初始位置: 第一行之前

数据获取

  • getXXX() 可以根据列的编号获取,也可以根据列名获取(推荐)。
while(rs.next()) {
    int id = rs.getInt("id");
    String name = rs.getString("username");
}

预编译 SQL

对比项 静态 SQL 预编译 SQL
安全性 易受 SQL 注入攻击 自动过滤特殊字符, 防止注入
性能 每次执行需编译 一次编译多次执行
可读性 参数硬编码, 可读性差 参数占位符(?), 结构清晰
用法 Statement PreparedStatement

用法

String sql = "SELECT * FROM user WHERE username=? AND password=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "daqiao");  // 参数索引从 1 开始
pstmt.setString(2, "123456");
ResultSet rs = pstmt.executeQuery();

查你马🙄

?这是咋啦?学到入魔了? 人之常情🥱

MyBatis

介绍

定位与作用
基于ORM规则的开源的优秀的持久层框架 (前身: ibatis)

发展历史
起源 Apache 开源项目 iBatis
2010 迁移至 Google Code 更名为 MyBatis
2013 迁移至 GitHub

入门程序

代码: D:\BH\Cloud\Web开发\Web_Project\boot-project06-mybatis

这几把上课越来越魔幻了,怎么记笔记???
自己看在线讲义去吧: 06-Web后端基础(java操作数据库) - 飞书云文档

配置文件 - 下一个Part SpringBoot 详解 有详细说明

  1. 数据库

  2. 创建工程[导入依赖]

  3. 配置文件(yml)

  4. 创建实体(满足ORM)

  5. 先写Mapper接口,再写Mapper映射文件

  6. 测试[集成SpringBoot单元测试]

    测试类需要在引导类的包下或者子包下
    加注解@SpringBootTest
    没在子包下@SpringBootTest(classes=引导类名.class)
    

辅助配置

配置日志输出

默认情况下,在Mybatis中,SQL语句执行时,我们看不到SQL语句的执行日志。

application.properties加入如下配置,即可查看日志:

#mybatis的配置
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

数据库连接池

数据库连接池: 是一个容器,负责分配、管理数据库连接(Connection)

优势:资源复用、提升系统响应速度
接口:DataSource
产品:C3P0、DBCP、Druid(阿里的)、Hikari(默认)

Spring Bean 构建

核心前提

所有 Mapper 接口 (无论注解/XML开发) 都必须通过以下方式之一注册为 Spring Bean:

  • @Mapper 标记单个接口
  • @MapperScan 批量扫描包

@Mapper

// 步骤1:在每个Mapper接口添加注解
@Mapper  // 关键注解
public interface UserMapper {
    @Select("SELECT * FROM user")
    List<User> findAll();
}

@Mapper  // 每个接口都要加
public interface ProductMapper {
    // ...
}

@MapperScan

// 步骤1:Spring Boot 启动类上添加扫描注解(只需一次)
@SpringBootApplication
@MapperScan("com.example.mapper")  // 扫描整个包
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// 步骤2:包内接口无需加@Mapper
public interface UserMapper {  // 自动注册
    @Select("SELECT * FROM user")
    List<User> findAll();
}

SQL 实现方式

在Mybatis中,既可以通过注解配置SQL语句,也可以通过XML配置文件配置SQL语句。

注解 开发

接口声明

// Mapper接口需要 @Mapper 注解,标识为MyBatis接口
@Mapper
public interface UserMapper {

抽象方法 + SQL注解

// 在Mapper接口,开发抽象方法
// @Insert @Delete @Update @Select
@Insert("INSERT INTO user(name) VALUES(#{name})")
int insertUser(String name);  // 参数自动绑定#{name}

@Select("SELECT * FROM user WHERE id=#{id}")
User findById(int id);  // 返回值自动映射

直接测试

// 在单元测试类中,注入Mapper调用方法即可测试。
@SpringBootTest
public class UserMapperTest {
    @Autowired
    private UserMapper userMapper;
    
    @Test
    void testFindById() {
        User user = userMapper.findById(1); // 调用Mapper方法
        System.out.println(user);           // 打印结果
    }
}

XML 开发

规则

XML 文件要和对应的 Mapper 接口文件同名

XML 文件和 Mapper 接口要放在同一个文件夹(包)里

XML 映射文件:

  • namespace 属性值 = 接口的全名(包含包名的完整类名)

  • 每条 SQL 语句的 id = 接口里对应方法的方法名

  • 并且 SQL 语句的 resultType 返回值类型 要和接口方法的返回值类型相同

    resultType

    只会出现在SELECT语句中,当返回值是一个List<User>的时候,
    resultType 应设置为单个结果对象的类型(例如 com.yourpackage.User),
    MyBatis 会自动包装成 List<User>

XML映射模板

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.boot06.mapper.UserMapper">

    <!--查询操作-->
    <select id="findAllUseXml" resultType="com.itheima.boot06.domain.pojo.User">
        select * from user
    </select>

</mapper>

内部SQL语句,换行是没有限制的哦~

MybatisX 插件

一只正在飞翔的小鸟,这个插件可以快速定位对应插件。 🦜

增删改查

笔记重新归档下来,增删改查只有接口实现的具体例子。
使用XML配置实现也不难,就先不写了,你自己脑测吧~

INSERT

// Mapper 接口方法
@Insert("insert into user(username,password,name,age) values(#{username},#{password},#{name},#{age})")
public void insert(User user);
// 单元测试类
@Test
public void testInsert(){
    User user = new User();
    user.setUsername("admin");
    user.setPassword("123456");
    user.setName("管理员");
    user.setAge(30);
    userMapper.insert(user);
}

※ 主键自增

在主键自增的情况下,通过 @Options 实现主键回填

  • useGeneratedKeys 使用主键生成策略
  • keyProperty 得到的主键值赋值给对象的哪个属性
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into user (username,password,name,age) " +
        " values (#{username}, #{password}, #{name}, #{age})")
Integer insert(User user);

DELETE

// Mapper 接口方法
@Delete("delete from user where id = #{id}")
public void deleteById(Integer id);
// 单元测试类
@Test
public void testDeleteById(){
    userMapper.deleteById(36);
}

UPDATE

// Mapper 接口方法
@Update("update user set username = #{username},password = #{password},name = #{name},age = #{age} where id = #{id}")
public void update(User user);
// 单元测试类
@Test
public void testUpdate(){
    User user = new User();
    user.setId(6);
    user.setUsername("admin666");
    user.setPassword("123456");
    user.setName("管理员");
    user.setAge(30);
    userMapper.update(user);
}

SELECT

// Mapper 接口方法
@Select("select * from user where username = #{username} and password = #{password}")
public User findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
// 单元测试类
@Test
public void testFindByUsernameAndPassword(){
    User user = userMapper.findByUsernameAndPassword("admin666", "123456");
    System.out.println(user);
}

@Param @param注解的作用是为接口的方法形参起名字的。
(由于用户名唯一的,所以查询返回的结果最多只有一个,可以直接封装到一个对象中)

返回值

增删改会有三种返回值

返回值
void 返回值为空
int/Integer 返回受影响行数
boolean 受影响行数>0,则为 true, 否则为 false

查询操作

? 上课讲的不多一笔带过

查询场景 返回值写法 示例
单条记录 实体类对象 User findById(int id)
多条记录 List<实体类> List<User> findAll()
统计数量 int/Long int countUsers()
返回单个字段 字段类型(如 String String getNameById(int id)
返回部分字段 Map<String,Object> 或 DTO Map<String,Object> getInfo()

需要配合resultType属性 -2025.7.16 😭😭😭

#{} ${} 参数占位符

#{} ${} (文本替换)
符号 (预编译占位符) 拼接符
本质 参数化查询 → 编译为 ? 字符串直接拼接
处理阶段 运行时参数绑定 SQL 解析阶段直接替换
安全性 自动防 SQL 注入 高危注入漏洞
执行过程 先编译 SQL 结构 → 再传参 拼接后整体编译执行
场景 参数值传递 表名 字段名动态设置时使用

Q: 为什么动态表名/列名必须用 ${}

A: 先拼接,预编译SQL结构,再传参

  • 表名/列名属于 SQL 结构部分
  • SQL 结构必须在执行前确定

参数绑定法则


单基本类型参数

// #{任意名称} 
@Select("SELECT * FROM user WHERE id = #{anyName}")
User getUserById(int id);

**多参数 @Param **

// 顺序自由
@Select("SELECT * FROM user WHERE name=#{name} AND age=#{age}")
List<User> getUsers(
    @Param("age") int userAge,
    @Param("name") String userName
);

数组参数

// 数组一定要有@Param  xml需要<foreach>实现遍历
void deleteByIds(@Param("ids") Integer[] ids);

集合参数

// 集合同数组一定要有@Param  xml需要<foreach>实现遍历
Integer insertBatch(@Param("exprList") List<EmpExpr> exprList);
<!--  xml需要<foreach>实现遍历  -->
<insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
    insert into emp_expr
    (begin, end, company, job, emp_id)
    values
        <foreach collection="exprList" item="expr" separator=",">
            (#{expr.begin},#{expr.end}, #{expr.company}, #{expr.job}, #{expr.empId})
        </foreach>
</insert>

如果传递给xml的是一个对象可以直接用属性。
但是如果传递的是集合类型(Eg:列表<对象>)就一定要用<foreach>遍历
<foreach>详解


对象参数

// 底层自动调用 user.getName()/getAge()
@Insert("INSERT INTO user(name,age) VALUES(#{name}, #{age})")
void insertUser(User user);

Map 参数(Key 名直接访问)

@Update("UPDATE user SET status=#{newStatus} WHERE id=#{userId}")
void updateUserStatus(Map<String, Object> params);
// 调用示例:
Map<String, Object> map = new HashMap<>();
map.put("userId", 100);
map.put("newStatus", "active");
updateUserStatus(map);  // Key必须匹配

多参数+对象参数

// SQL预编译语句,使用 #{对象名.属性} 访问嵌套对象
@Update("update Emp set username = #{emp.username}, password = #{emp.password} where id = #{id}")
Integer updateEmpById(@Param("id") Integer id, @Param("emp") Emp emp);
// 调用示例
@Test
void testUpdateEmpById(){
    int id = 88;
    Emp emp = Emp.builder().username("123456").password("答辩").build();
    empMapper.updateEmpById(id, emp);
}

参数分离设计

  • 基本类型参数 id 直接使用 #{id}
  • 对象类型参数 emp 通过 #{emp.属性} 访问

香喷喷表格

场景 写法 关键约束
单基本类型参数 #{任意名称} 仅能有一个参数
多参数 必须用 @Param("名称") 注解 注解名与SQL中#{}名一致
数组/数组参数 必须用 @Param("名称") 注解 xml用<foreach>实现遍历
对象参数 #{对象属性名} 属性必须有getter方法
Map参数 #{Map键名} Key必须存在且非null

参数顺序: 使用@Param后可自由调整顺序,不过需要保持类型匹配

常见问题

数据库密码错误

错误信息:

Caused by: java.sql.SQLException: 
Access denied for user 'root'@'localhost' (using password: YES)

解决方案:

application.properties 修改password

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db01
spring.datasource.username=root
spring.datasource.password=root123

找不到数据库

错误信息:

Caused by: java.sql.SQLSyntaxErrorException: 
Unknown database 'db011'

解决方案:

application.properties 修改url

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db011
spring.datasource.username=root
spring.datasource.password=root

表不存在

错误信息:

Caused by: java.sql.SQLSyntaxErrorException: 
Table 'db01.tuser' doesn't exist

解决方案:

com/itheima/boot06/mapper/UserMapper.java 修改@Select的value值

@Mapper
public interface UserMapper {
    @Select("select * from tuser")
    List<User> findAll();
}

依赖注入失败

错误信息:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'com.itheima.boot06.mapper.UserMapper' available: 
expected at least 1 bean which qualifies as autowire candidate. 
Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

方案一:

com/itheima/boot06/BootProject06MybatisApplication.java 修改@MapperScan设置

@SpringBootApplication
@MapperScan(basePackages = "com.itheima.boot06.mapper")
public class BootProject06MybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootProject06MybatisApplication.class, args);
    }

}

方案二:

com/itheima/boot06/mapper/UserMapper.java 添加@Mapper注解

@Mapper
public interface UserMapper {
    @Select("select * from user")
    List<User> findAll();
}

映射异常

错误现象

// 对象参数映射异常
Caused by: org.apache.ibatis.reflection.ReflectionException:
There is no getter for property named 'id_card' in 'class com.itheima.pojo.Student'

问题代码

// 实体类
public class Student {
    private String phone; // 手机号
    private String idCard; // 身份证号
    ...
}
// 测试类
@Update("update student set id_card = #{id_card}, phone = #{phone} where id = 3")
Integer updateStudent(Student student);

错误分析

属性名与占位符命名不一致

在实体类中,Student的属性名是idCard,
属性名 idCard(实体类)必须与占位符 #{idCard}(SQL)严格一致,
这是 MyBatis 对象属性映射的基本规则。

解决方案

// 测试类 
@Update("update student set id_card = #{idCard}, phone = #{phone} where id = 3")
Integer updateStudent(Student student);

多条结果异常

Caused by: org.apache.ibatis.exceptions.TooManyResultsException: 
Expected one result (or null) to be returned by selectOne(), but found: 2

解释: 这是因为返回了两个结果,但是只有一个参数接受。
原因: 数据库中存在相同用户名,且相同密码的数据

没有 bean 定义异常

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'com.itheima.mapper.StudentMapper' available: 
expected at least 1 bean which qualifies as autowire candidate.

解决方案: 接口缺少@Mapper注解

非法参数异常

ava.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@433d9680 testClass = com.itheima.MybatisHomework03ApplicationTests, locations = [], classes = [com.itheima.MybatisHomework03Application], contextInitializerClasses = [], activeProfiles = [], propertySourceLocations = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@306f16f3, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@2dde1bff, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@50b5ac82, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@9da1, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5fbdfdcf, org.springframework.boot.test.context.SpringBootTestAnnotation@87013e81], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]

Caused by: java.lang.IllegalArgumentException: 
Mapped Statements collection already contains value for com.itheima.mapper.EmpMapper.findAllMale. 
please check com/itheima/mapper/EmpMapper.xml and com/itheima/mapper/EmpMapper.java (best guess)
    
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.itheima.mapper.EmpMapper.findAllMale. please check com/itheima/mapper/EmpMapper.xml and com/itheima/mapper/EmpMapper.java (best guess)

上述这些,可能的原因是: 同时写了注解实现,以及XML实现,导致报错冲突。

参数绑定异常

Caused by: org.apache.ibatis.binding.BindingException: 
Parameter 'password' not found. Available parameters are [emp, id, param1, param2]

SQL语句参数传递有误,检查你的传参代码段

未知主机地址

2025-07-1 OTI ERROR 1896 - - [nio-8080-exec-1]
[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception 
[Request processing failed: org.mybatis.spring.MyBatisSystemException] with root cause
java.net.UnknownHostException: 不知道这样的主机 (local)

检查yml文件,url是否写错.

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/tlias

待讲知识点

  • 动态SQL
  • 结果映射

SpringBoot 配置文件

核心特性

支持格式: properties > yml = yaml

优先级规则: properties > yml/yaml

格式对比:

特性 properties yml
层级结构 扁平 树形缩进
易读性 简单键值 结构化视觉区分
特殊值处理 无需特殊处理 0开头需加引号(16进制问题)

properties

application.properties 是默认自带的配置文件,提供模板可以直接复制

# 数据库驱动类类名
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库访问的url地址
spring.datasource.url=jdbc:mysql://localhost:3306/db01
spring.datasource.username=root
spring.datasource.password=root
# 数据库连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# 配置Mybatis相关内容
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

yml

格式规范:

  • 大小写敏感
  • 数值前边必须有空格,作为分隔符
  • 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能用空格(idea中会自动将Tab转换为空格)
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可 (缩进建议2空格)
  • # 表示注释,从这个字符一直到行尾,都会被解析器忽略
  • 不建议写中文

注意:
在yml格式的配置文件中,如果配置项的值是以 0 开头的值需要使用 '' 引起来,
因为以0开头在yml中表示8进制的数据。

数据结构

# 定义 对象/Map 集合
user:
  name: zhangsan
  age: 18
  password: 123456

# 定义 数组/List/Set 集合
hobby: 
  - java
  - game
  - sport

yml模板

# 数据源异常
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db01
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource

# mybatis配置
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

yml 常见错误

异常类型 典型原因
BindingException 接口方法与 SQL 语句绑定失败
BuilderException namespace 写错了
ExecutorException 结果映射缺失
SQLSyntaxErrorException SQL 语法错误

这么点B错误老子都兰的真理😅

对不起我错了,错误真的有必要整理啊啊啊~😭

IDEA 微操

关键词: 快捷键

按住 Alt + 鼠标上下,可以实现多行光标生成,同时修改多行数据

后端Web实战 🦃

前瞻直播

  • 部门管理

  • 员工管理

  • 报表统计

  • 登录认证

  • 日志管理

  • 班级学员管理

部门管理

日期: 2025.7.10
代码: D:\BH\Cloud\Web开发\Web_Project\boot-tlias-project01

准备工作

开发规范

开发模式
前后端混合开发
前后端分离开发
API (接口文档)

接口文档
需求分析
接口设计(API接口文档)
前后端并行开发
测试 (单侧)
联调测试

接口四要素
请求url
请求方式
请求参数
响应数据

Restful 风格

REST(Representational State Transfer),表述性状态转换,它是一种软件架构风格。

REST风格url 请求方式 含义
http://localhost:8080/users GET 查询所有
http://localhost:8080/users/1 GET 查询id为1的用户
http://localhost:8080/users/1 DELETE 删除id为1的用户
http://localhost:8080/users POST 新增用户
http://localhost:8080/users PUT 修改用户

Apifox

  • 介绍:Apifox是一款集成了Api文档、Api调试、Api Mock、Api测试的一体化协作平台。
  • 作用:接口文档管理、接口请求测试、Mock服务。
  • 官网: https://apifox.com/

工程搭建

  1. 创建SpringBoot工程,并引入web开发起步依赖、mybatis、mysql驱动、lombok。

  2. 创建数据库表,并在application.yml中配置数据库的基本信息。

  3. 准备基础代码结构,并引入实体类Dept及统一的响应结果封装类 Result。

application.yml

# Spring 配置
spring:
  # 连接数据库信息
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://local:3306/tlias
    username: root
    password: root
  # 应用名称 (目前暂时没什么用)
  application:
    name: boot-tlias

# MyBatis配置
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Result.java

package com.itheima.domain.vo;

import lombok.Data;

import java.io.Serializable;

/**
 * 后端统一返回结果
 */
@Data
public class Result {

    private Integer code; //编码:1成功,0为失败
    private String msg; //错误信息
    private Object data; //数据

    public static Result success() {
        Result result = new Result();
        result.code = 1;
        result.msg = "success";
        return result;
    }

    public static Result success(Object object) {
        Result result = new Result();
        result.data = object;
        result.code = 1;
        result.msg = "success";
        return result;
    }

    public static Result error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }

}

一个健康的目录结构

image-20250711142521405

JSON

概念

JSON (JavaScript Object Notation) JavaScrpit 对象表示法

Java对象的字符串表现形式

JSON 对象

// Java
public class Dept {
    private Integer id;
    private String name;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}
// JSON
{
	"id": 1,
    "name": "教研部",
    "createTime": "2002-12-11 12:18:12",
    "updateTime": "2002-12-11 12:18:12"
}

JSON 数组 [Java 集合]

List<Dept>
[
	{
		"id": 1,
    	"name": "教研部",
    	"createTime": "2002-12-11 12:18:12",
    	"updateTime": "2002-12-11 12:18:12"
	},
    {
		"id": 2,
    	"name": "学工部",
    	"createTime": "2002-12-11 12:18:12",
    	"updateTime": "2002-12-11 12:18:12"
	}
]

嵌套 [对象中有数组]

// Java
public class Dept {
    private Integer id;
    private String name;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    private List<Emp> emps;
}
// JSON
{
	"id": 1,
    "name": "教研部",
    "createTime": "2002-12-11 12:18:12",
    "updateTime": "2002-12-11 12:18:12",
    "emps": [
        {
        	"id": 1001,
    		"name": "jack",
        },
        ...
    ]
}

Spring MVC 框架

MVC 设计模式

模型(Model):数据处理核心

  • 管理业务数据和业务规则
  • Service,Mapper

视图(View):数据展示层

  • 负责呈现模型数据

控制器(Controller):请求协调者

  • 接收用户输入,调用模型,选择视图
  • Controller

MVC分层协作全景图

sequenceDiagram participant C as 客户端 participant V as View(前端) participant CTRL as Controller participant SVC as Service participant MAP as Mapper participant DB as Database C->>V: 用户操作 V->>CTRL: HTTP请求(PUT /depts/1) CTRL->>CTRL: 1. 参数校验<br>2. 协议转换 CTRL->>SVC: 调用 updateDept(dept) SVC->>SVC: 1. 业务规则校验<br>2. 事务管理 SVC->>MAP: 调用 update(dept) MAP->>DB: 执行UPDATE SQL DB-->>MAP: 返回结果 MAP-->>SVC: 返回影响行数 SVC-->>CTRL: 返回业务状态 CTRL-->>V: 返回JSON响应 V-->>C: 更新UI

三层处理

分层 技术
Web 层 (Controller层) HTTP 请求映射,请求参数处理
Service/Business Logic 层
Data Access 层 (数据访问层 / 数据库交互层) 数据访问与字段映射

HTTP 请求映射

实现 HTTP 请求映射的主要工具: @RequestMapping

作用: 用于将HTTP请求映射到MVC和REST控制器的处理方法上

属性:

@RequestMapping(value = "/depts", method = RequestMethod.GET)
public Result findAll() {
    // 业务逻辑
    return Result.success();
}
  • value:定义请求路径 /depts
  • method:指定请求类型 RequestMethod.GET

统一请求前缀:

  • 在类上添加 @RequestMapping("/depts") 作为所有方法的前缀
  • 不同的请求方式对应的新注解 @GetMapping, @DeleteMapping, @PostMapping, @PutMapping
  • 方法上仍需单独定义路径(如 @PutMapping("/{id}")
@RestController
@RequestMapping("/depts")
public class DeptController {
    @PutMapping("/{id}")
    public Result updateDept(...) { ... }
}

请求参数处理

参数解析链

参数解析链: HandlerMethodArgumentResolvers

sequenceDiagram Client->>DispatcherServlet: HTTP请求 DispatcherServlet->>ArgumentResolver: 解析参数 alt 路径参数 ArgumentResolver->>PathVariableResolver: 处理@PathVariable else 查询参数 ArgumentResolver->>RequestParamResolver: 处理@RequestParam else JSON主体 ArgumentResolver->>RequestBodyResolver: 处理@RequestBody end ArgumentResolver-->>HandlerMethod: 组装参数 HandlerMethod->>BusinessLogic: 调用业务方法

简单参数处理

基本方案三

方案一:通过原始的 HttpServletRequest 对象获取请求参数

/**
* 方式一 (HttpServletRequest)
*/
@DeleteMapping("/depts")
public Result delete(HttpServletRequest request){
    String idStr = request.getParameter("id");
    int id = Integer.parseInt(idStr);
    
    System.out.println("根据ID删除部门: " + id);
    return Result.success();
}

方案二:通过Spring提供的 @RequestParam 注解,将请求参数绑定给方法形参

@DeleteMapping("/depts")
public Result delete(@RequestParam("id") Integer deptId){
    System.out.println("根据ID删除部门: " + deptId);
    return Result.success();
}

@RequestParam 注解的value属性,需要与前端传递的参数名保持一致 。

方案三:如果请求参数名与形参变量名相同,直接定义方法形参即可接收。(省略@RequestParam)

@DeleteMapping("/depts")
public Result delete(Integer id){
    System.out.println("根据ID删除部门: " + id);
    return Result.success();
}

数组参数接收

2025.7.14 - 新增知识点

EmpController 中增加 delete 方法处理批量删除员工操作

使用数组接收 Array

  • 原理: Spring MVC 默认将多个同名请求参数自动封装到数组

  • 要求: 前端参数名必须与 Controller 方法形参名称一致

  • 代码示例

    /**
     * 批量删除员工 数组方式
     * @param ids 要删除的员工ID数组
     * @return 操作结果
     */
    @DeleteMapping
    public Result delete(Integer[] ids) {
        log.info("批量删除员工: ids={}", Arrays.asList(ids))
        // 调用 Service 处理逻辑
        return Result.success()
    }
    
  • 特点: 简单直接, 无需额外注解

使用集合接收 List 推荐

  • 原理: 将多个同名请求参数自动封装到 List 集合

  • 要求

    1. 集合形参前必须添加 @RequestParam 注解
    2. 前端参数名必须与 @RequestParam 指定名称一致
  • 代码示例

    /**
     * 批量删除员工 集合方式
     * @param ids 要删除的员工ID集合
     * @return 操作结果
     */
    @DeleteMapping
    public Result delete(@RequestParam List<Integer> ids) {
        log.info("批量删除员工: ids={}", ids)
        empService.deleteByIds(ids)
        return Result.success()
    }
    
  • 特点: List 集合操作元素比数组更便捷, 适合直接传递给 Service 层

方案对比

  • 通用性: 两种方式均可实现批量删除功能
  • 推荐方案
    集合接收方式 List + @RequestParam
    业务逻辑处理更灵活, 代码可读性更高

特殊类型接收

2025.7.11 - 新增知识点

自动转换
日期格式通过注解自动处理

// 在DTO 属性注解
@DateTimeFormat(pattern = "yyyy-MM-dd") 
private LocalDate begin; // 字符串自动转日期
// 直接在@RequestParam 注解 
@GetMapping("/records")
public Result getRecords(
    @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate start,
    @RequestParam @DateTimeFormat(pattern = "HH:mm:ss") LocalTime endTime
) {
    // 自动转换日期时间类型
    return Result.success();
}

@RequestParam 详解

@RequestParam(
    value = "id",          // 请求参数名
    required = true,       // 是否必传(默认true)
    defaultValue = "1"     // 默认值(required=false时生效)
)

使用场景

  • 参数名 ≠ 方法参数名时
  • 需要设置非必传参数时
  • 需要设置默认值时

@RequestParam 参数映射规则

@GetMapping
    public Result findByJson(
            @RequestParam(value = "name",required = false) String name,
            @RequestParam(value = "begin",required = false) LocalDate beginDate,
            @RequestParam(value = "end",required = false) LocalDate endDate,
            @RequestParam(value = "page",required = true) Integer page,
            @RequestParam(value = "pageSize",required = true) Integer pageSize
            ){
        List<Clazz> clazzes = clazzService.findByJson();
        return Result.success(clazzes);
    }
  • value 属性:必须和前端传递的参数名完全一致(页面/URL中的参数名)
  • 方法参数名:右侧的变量名(如 name/beginDate)是方法内部使用的变量名,不需要和前端参数名一致
  • 实体类属性:服务层处理时,需确保服务方法内部使用的字段名与实体类属性名一致(与 @RequestParam 无关)

路径参数处理

参数接收

  • 使用 @PathVariable 获取路径中的动态参数
@GetMapping("/depts/{id}") // 这里要{},记得
public Result getById(@PathVariable Integer id) {
    // 查询部门
}

JSON 参数处理

  • 使用 @RequestBody 注解接收 JSON 格式参数
  • 规则:JSON 的键名必须与实体类的属性名一致
@PostMapping("/depts")
public Result addDept(@RequestBody Dept dept) {
    // 业务逻辑
}

三大参数处理器

注解 参数类型 处理逻辑
@RequestParam 普通请求参数 从查询字符串解析 ?name=value
@PathVariable 路径参数,必须传 从URL路径模板中提取值 /depts/{id}
@RequestBody JSON参数 用实体或者数组接收JSON参数

字段映射解决方案

基础字段映射方案

字段映射解决方案(Field Mapping Solution)
它是 ORM(对象关系映射)框架中处理数据库字段与对象属性不匹配问题的关键技术。

原因: 数据库字段名与Java属性名的不一致 -- 字段映射解决方案

方案一
手动结果映射:使用 @Results + @Result 逐字段映射

@Results({
    @Result(column = "create_time", property = "createTime"),
    @Result(column = "update_time", property = "updateTime")
})
@Select("select id, name, create_time, update_time from dept")
public List<Dept> findAll();

方案二
SQL 别名: SQL 中直接为字段起别名

@Select("select id, name, create_time createTime, update_time updateTime from dept")
public List<Dept> findAll();

方案三
开启驼峰映射(配置文件启用):

mybatis:
  configuration:
    map-underscore-to-camel-case: true

2025.7.15 - 补

map-underscore-to-camel-case
仅作用于查询结果集(SELECT 操作返回的数据映射),对 INSERT/UPDATE 的参数映射完全无效

以下是示例场景:学生查询 vs 学生插入

  1. 数据库表结构
CREATE TABLE student (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    clazz_id INT,  -- 数据库使用下划线命名
    graduation_date DATE
);
  1. Java 实体类(使用驼峰命名)
public class Student {
    private Integer id;
    private String name;
    private Integer clazzId;       // 驼峰:clazzId
    private Date graduationDate;   // 驼峰:graduationDate
    // getters/setters
}

情况1:查询操作(受影响 ✅)

<select id="selectStudent" resultType="Student">
    SELECT id, name, clazz_id, graduation_date 
    FROM student WHERE id = #{id}
</select>

map-underscore-to-camel-case=true 时:

  • MyBatis 自动将下划线字段映射到驼峰属性:
    • clazz_idclazzId
    • graduation_dategraduationDate
  • 不需要在 SQL 中写别名

情况2:插入操作(不受影响 ❌)

<insert id="insertStudent" parameterType="Student">
    INSERT INTO student (name, clazz_id, graduation_date)
    VALUES (#{name}, #{clazz_id}, #{graduation_date}) <!-- 错误写法! -->
</insert>

会发生什么:

  1. MyBatis 尝试调用 student.getClazz_id() 方法 → 不存在
  2. 尝试调用 student.getGraduation_date()不存在
  3. 最终抛出异常:
    There is no getter for property named 'clazz_id' in 'class Student'

正确解决方案

必须使 #{} 中的名称严格匹配 Java 属性名

<insert id="insertStudent" parameterType="Student">
    INSERT INTO student (name, clazz_id, graduation_date)
    VALUES (#{name}, #{clazzId}, #{graduationDate}) <!-- 匹配Java属性 -->
</insert>

ResultMap高级映射

2025.7.14 - 新增高级知识点
基础字段映射本质是MyBatis自动生成的简化版ResultMap

@Results + @Result 是注解形式的 <resultMap>,功能完全等价。

一对多关系的高级映射技巧 - 7.16? 才过两天就忘光光了...

为什么要用 ResultMap

解决以下场景的映射问题:

  1. 字段名与属性名不一致(如 entry_dateentryDate)
  2. 存在嵌套对象/集合(如员工包含工作经历列表)
  3. 需要特殊处理数据类型转换
  4. 多表关联查询(自动映射无法处理)

resultType vs resultMap

resultType
查询结果字段名与实体类属性名完全一致时使用,MyBatis 自动映射

resultMap
需手动定义映射关系的场景:

  • 字段名与属性名不一致 (entry_date → entryDate)
  • 存在嵌套对象/集合 (如员工包含工作经历列表)
映射方式 适用场景
resultType 单表简单查询,字段名与属性名一致
resultMap 多表关联查询/嵌套集合/字段不一致

用法示例

场景: 查询员工信息 (包含多条工作经历)

<resultMap id="EmpAndExprListResultMap" type="com.itheima.domain.pojo.Emp">
    <!-- 主键字段映射 (标识对象唯一性) -->
    <id column="id" property="id"/>
    
    <!-- 普通字段映射 (解决字段名不一致问题) -->
    <result column="username" property="username"/>
    <result column="password" property="password"/>
    <result column="name" property="name"/>
    <result column="gender" property="gender"/>
    <result column="phone" property="phone"/>
    <result column="job" property="job"/>
    <result column="salary" property="salary"/>
    <result column="image" property="image"/>
    <result column="entry_date" property="entryDate"/>
    <result column="dept_id" property="deptId"/>
    <result column="create_time" property="createTime"/>
    <result column="update_time" property="updateTime"/>
    
    <!-- 工作经历集合映射 -->
    <collection property="exprList" ofType="com.itheima.domain.pojo.EmpExpr">
        <id column="ee_id" property="id"/>  <!-- 子对象主键 -->
        <result column="ee_company" property="company"/>
    </collection>
</resultMap>

<select id="findById" resultMap="EmpAndExprListResultMap">
    select e.*, 
           ee.id ee_id, 
           ee.company ee_company
    from emp e
    left join emp_expr ee on e.id = ee.emp_id
    where e.id = #{id}
</select>

属性解析

标签/属性 作用说明
id 主键映射: 标识对象唯一性,避免嵌套查询时重复创建对象
result 普通字段映射: 解决字段名与属性名不一致问题
collection 映射集合类型属性 (一对多关系)
property 实体类中对应的集合属性名 (如 exprList)
ofType 集合内元素的完整类路径 (如 EmpExpr)

Java 代码调用示例

@GetMapping("/{id}")
public Result findById(@PathVariable Integer id) {
    Emp emp = empService.findById(id)
    return Result.success(emp)
}

日志技术

核心作用

  • 数据追踪、性能优化、问题排查、系统监控
  • 常用框架:Logback(Spring Boot 默认集成) + SLF4J(日志门面)

日志级别

级别 使用场景 示例
TRACE 追踪程序运行轨迹(极少使用) log.trace("...")
DEBUG 调试信息(开发阶段常用) log.debug("...")
INFO 关键运行信息(如 DB 连接、IO 操作) log.info("...")
WARN 警告信息(潜在问题) log.warn("...")
ERROR 错误信息(异常捕获) log.error("...")

快速使用

  • 依赖:Spring Boot 已内置 spring-boot-starter-logging(无需手动引入)

  • 注解:使用 @Slf4j(Lombok 提供,自动生成 log 对象)

    @Slf4j
    @RestController
    public class UserController {
        @GetMapping("/users")
        public Result list() {
            log.debug("查询用户列表...");
            log.info("连接数据库成功");
        }
    }
    
  • 配置:在 application.yml 中调整日志级别

    logging:
      level:
        root: info
        com.example: debug  # 指定包路径日志级别
    

Nginx

常用指令

nginx -s reload   # 重启 Nginx
nginx -s stop     # 停止 Nginx
start nginx       # 启动 Nginx

核心功能

功能 说明
反向代理 Nginx去代理WEB服务器,客户端直接访问Nginx,并不知道WEB服务器的真实地址
负载均衡 根据负载均衡的策略去分配请求具体交给哪个WEB服务器去处理
动静分离 动态资源和静态资源分离部署(Nginx部署静态资源)

配置文件路径:/conf/nginx.conf

前后端联调配置

server {
    listen 90;  # 监听端口
    
    # 处理 /api/ 开头的请求
    location ^~ /api/ {
        # 移除 /api 前缀 (重写URL)
        rewrite ^/api/(.*)$ /$1 break;
        
        # 转发到后端服务
        proxy_pass http://localhost:8080;
    }
}

路径重写
rewrite ^/api/(.*)$ /$1 break;

  • /api/users/users
  • break 标志停止后续重写规则

请求转发
proxy_pass http://localhost:8080;

  • 实际转发到 8080 端口的后端服务

精准匹配
location ^~ /api/

  • ^~ 优先匹配带 /api/ 的路径
  • 避免与其他 location 规则冲突

员工管理 一

日期: 2025.7.11
工程: D:\BH\Cloud\Web开发\Web_Project\boot-tlias-project03

讲的时候负责听 别做其他Anything 🤓👍

大纲

  • 多表关系
  • 多表查询
  • 员工列表查询

多表关系

在项目开发中,设计数据库表结构时需根据业务需求和模块间关系进行分析。
表结构间的关系主要分为三种: 一对多,一对一,多对多

一对多

场景举例

  • 部门 与 员工
  • 班级 与 学生

实现方式

  • 的一方(子表/从表)添加一个 逻辑外键 字段, 该字段关联 的一方(父表/主表)的主键.

外键约束

物理外键 (Foreign Key Constraint)

  • 概念: 使用 FOREIGN KEY 在数据库层面强制定义关联.

  • 语法

    -- 创建表时指定
    CREATE TABLE 子表名 (
        字段名 数据类型,
        ...,
        [CONSTRAINT 外键名称] FOREIGN KEY (外键字段名) REFERENCES 主表名(主键字段名)
    );
    
    -- 建表后添加
    ALTER TABLE 子表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名) REFERENCES 主表名(主键字段名);
    
  • 缺点

    1. 影响增、删、改效率(数据库需检查外键约束).
    2. 不利于分布式、集群和分库分表场景.
    3. 可能引发数据库死锁, 消耗性能.
  • 行业现状: 在实际企业开发中 较少使用

逻辑外键

  • 概念: 不在数据库层面创建物理外键约束, 仅在业务逻辑代码层面维护表之间的关联关系.
  • 优点: 避免了物理外键的性能和灵活性缺点, 是目前主流的实现方式.

一对一

场景举例

  • 用户 与 身份证信息
  • 一夫 与 一妻 (概念性举例)

实现方式

  • 任意一方 添加一个 逻辑外键 字段, 关联另一方的主键.
  • 对该外键字段添加 唯一约束 (UNIQUE Constraint), 确保其值唯一.

目的: 常用于单表拆分, 将常用基础字段与不常用/大字段分开存储, 提升操作效率.

多对多

场景举例

  • 学生 与 课程
  • 用户 与 角色
  • 商品 与 订单

关系描述: 一个 A 实体对应多个 B 实体, 同时一个 B 实体也对应多个 A 实体.

实现方式

  • 创建 第三张中间表(关联表).

  • 中间表 至少包含两个外键字段, 分别关联两张主表的主键.

  • 通常这两个外键组合在一起作为中间表的联合主键, 或单独设置一个主键.

  • 示例

    CREATE TABLE 学生课程 (
        student_id INT NOT NULL,
        course_id INT NOT NULL,
        PRIMARY KEY (student_id, course_id), -- 联合主键 (常见方式)
        FOREIGN KEY (student_id) REFERENCES 学生(id), -- 物理外键 (可选, 实际常用逻辑关联)
        FOREIGN KEY (course_id) REFERENCES 课程(id)  -- 物理外键 (可选, 实际常用逻辑关联)
    );
    

多表查询

三种连接

内连接: 相当于查询A、B交集部分数据

外连接

  • 左外连接:查询左表所有数据(包括两张表交集部分数据)
  • 右外连接:查询右表所有数据(包括两张表交集部分数据)

子查询

内连接

结果: 结果会返回为两个表的交集

-- 1.隐式内连接 (常见)
select 字段列表 from 表1 , 表2 where 连接条件 ...;

-- 2.显式内连接
select 字段列表 from 表1 [inner] join 表2 on 连接条件 ... ;
-- 1.给表起别名,来简化书写
select 字段列表 from 表1 [as] 别名1, 表2 [as] 别名2 where 条件 ...;

Eg:

# 隐式内连接,也叫等值连接
select * FROM emp e,dept d where e.dept_id = d.id;

# 当查询时有字段出现了重复,一定要把重复的字段进行取别名操作
select e.id,e.name ,d.name dept_name FROM emp e,dept d where e.dept_id = d.id;
# 显式内连接
SELECT
	e.*,
	d.NAME dept_name 
FROM
	emp e
	INNER JOIN dept d ON e.dept_id = d.id;

左外连接

结果: 结果会返回为左表满足条件的数据,右表满足条件则显示,不满足则以NULL填充

-- 1.左外连接 (常见)
select 字段列表 from 表1 left [outer] join 表2 on 连接条件 ...;

-- 2.右外连接
select 字段列表 from 表1 right [outer] join 表2 on 连接条件 ...;

Eg:

# 左外连接: 左表显示匹配条件的全部数据,右表能匹配则显示数据,匹配失败则NULL 填充
SELECT e.id,e.name,d.name dept_name FROM emp e
LEFT JOIN dept d
on e.dept_id = d.id;

# 右外连接
SELECT e.id,e.name,d.name dept_name FROM emp e
RIGHT JOIN dept d
on e.dept_id = d.id;

子查询

介绍: SQL语句中嵌套select语句,称为嵌套查询,又称子查询。

形式: select * from t1 where column1 = (select column1 from t2 …);

说明: 子查询外部的语句可以是insert/update/delete/select的任何一个,最常见的是 select。

分类:

  • 标量 子查询: 子查询返回的结果为单个值
  • 列 子查询: 子查询返回的结果为一列
  • 行 子查询: 子查询返回的结果为一行
  • 表 子查询: 子查询返回的结果为多行多列

返回结果情况:

  • 一个值,常用的操作符: =、!=、>、>=、<、<=
  • 多个值,常用的操作符:IN 、NOT IN
  • 一行数据,常用的操作符:= 、<> 、IN 、NOT IN
  • 多行数据(临时表)、常用的是连表查询

课堂练习

SQL语句还是要多学多练呀

# 子查询
# 案例1:查询 最早入职 的员工信息
# 查询最早入职
# 查询入职时间=1的值
select * from emp where entry_date = (select min(entry_date) from emp);

# 案例2:查询在  入职之后入职的员工信息
select * from emp where entry_date > (select entry_date from emp where name = '阮小五');

# 查询 "教研部" 和 "咨询部" 的所有员工信息
select * from emp where dept_id in (select id from dept where name = '教研部' or name = '咨询部');

# 查询与 "李忠" 的薪资 及 职位都相同的员工信息
select * from emp where 
salary = (select salary from emp where name = '李忠') 
and 
job = (select job from emp where name = '李忠');
# 合并写法
select * from emp where 
(salary,job) = (select salary,job from emp where name = '李忠');

# 查询每个部门中薪资最高的员工信息
select * from emp e inner JOIN (select dept_id,MAX(salary) sal from emp GROUP BY dept_id) t
on e.dept_id = t.dept_id and e.salary = t.sal;

高阶练习

练习多练爽歪歪~ 🤓👍

# 查询 "教研部" 性别为 男,且在 "2011-05-01" 之后入职的员工信息
select * from emp 
where
	dept_id = ( select id from dept where name = '教研部' ) 
	and gender = 1 
	and entry_date > '2011-05-01';
	
	
# 查询工资 低于公司平均工资的 且 性别为男 的员工信息
select * from emp 
where
	salary < ( select avg( salary ) from emp ) 
	and gender = 1;

# 查询部门人数超过 10 人的部门名称
select name from dept 
where
	id in ( select dept_id from emp group by dept_id having count(*)> 10 );
	
select d.name, count(*) from emp e, dept d where e.dept_id=d.id group by d.name having count(*)>10;


# 查询在 "2010-05-01" 后入职,且薪资高于 10000 的 "教研部" 员工信息,并根据薪资倒序排序
select * from emp e 
where
	e.entry_date > '2010-05-01' 
	and e.salary >= 10000 
	and dept_id = ( select id from dept where name = '教研部' ) 
order by
	e.salary desc;
	
select * 
from
	emp e
	inner join dept d 
where
	e.dept_id = d.id 
	and e.entry_date > '2010-05-01' 
	and e.salary > 10000 
	and d.name = '教研部' 
order by
	e.salary desc;


# 查询工资 低于本部门平均工资的员工信息
select * from emp e inner join (select dept_id,avg(salary) avg_salary from emp group by dept_id) t
where e.dept_id = t.dept_id and e.salary < t.avg_salary;

update 的连表

2025.7.13
update 操作是可以用到连表的.
但是今天的代码是我想复杂了,实际上用不到~

分页查询

DTO 设计模式

DTO (Data Transfer Object) 本质是层间通信的契约对象, 核心价值在于构建清晰的数据传输边界.
通过专用对象在不同系统层级间传递数据, 实现业务逻辑与接口表示的分离.

解耦隔离
隔离业务层与表示层, 避免领域模型 (Entity) 直接暴露给外部接口
防止底层数据结构变更影响接口契约

按需定制
裁剪数据字段仅传输必要属性
合并多源数据实现跨实体聚合
适配不同场景提供定制数据结构

传输优化
减少网络传输数据量
避免循环引用问题
精确控制序列化过程

稳定契约
保持接口长期兼容性
支持多版本 API 并行
隐藏内部实现细节

请求参数封装类

// 请求参数 DTO
public class EmpPageQueryDTO {
    private Integer page = 1;       // 默认值
    private Integer pageSize = 10;  // 默认值
    private String name;
    private Integer gender;
    @DateTimeFormat(pattern = "yyyy-MM-dd") 
    private LocalDate begin;        // 自动格式转换
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate end;
}
  • DTO的字段名必须和URL参数名一致(如beginend),这样Spring才能自动绑定参数。
  • 实体类的字段名是独立的,通常与数据库表字段映射(如beginDateendDate)。
// Controller 层
@GetMapping("/emps")
public Result pageQuery(EmpPageQueryDTO dto) {
    PageResult<Emp> result = empService.pageQuery(dto);
    return Result.success(result);
}
// Service 层
public PageResult<Emp> pageQuery(EmpPageQueryDTO dto) {
    PageHelper.startPage(dto.getPage(), dto.getPageSize());
    // 下面两句可以合并
    List<Emp> list = empMapper.listByCondition(dto);
    Page<Emp> page = (Page<Emp>) list;
    // 合并成为如下
    Page<Emp> page = empMapper.listByCondition(dto);
    return new PageResult<>(page.getTotal(), page.getResult());
}
<!-- Mapper 动态 SQL -->
<select id="listByCondition" resultType="Emp">
    SELECT * FROM emp
    <where>
        <if test="name != null and name != ''">
            name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="gender != null">
            AND gender = #{gender}
        </if>
        <if test="begin != null and end != null">
            AND entry_date BETWEEN #{begin} AND #{end}
        </if>
    </where>
</select>
// 分页响应结构
@Data
public class PageResult<T> {
    private Long total;      // 总记录数
    private List<T> records; // 当前页数据
}

// 统一响应结构
public class Result<T> {
    private Integer code;
    private String msg;
    private T data;
    
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.code = 200;
        result.msg = "成功";
        result.data = data;
        return result;
    }
}

实现要点

DTO 解耦
请求参数 EmpPageQueryDTO 与响应结构 PageResult 分离, 各自独立演进

自动转换
日期格式通过注解自动处理

@DateTimeFormat(pattern = "yyyy-MM-dd") 
private LocalDate begin; // 字符串自动转日期

动态查询
MyBatis 直接使用 DTO 属性构建 SQL

<if test="name != null">  <!-- 使用 DTO 属性 -->

分页封装
PageHelper 分页结果自动转换

Page<Emp> page = (Page<Emp>) list;
return new PageResult<>(page.getTotal(), page.getResult());

统一响应
标准化 API 输出格式

return Result.success(pageResult); // 统一结构

PageHelper 分页实现

核心依赖

<!--分页插件PageHelper-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
</dependency>

Mapper接口SQL

@Select("SELECT e.*, d.name deptName FROM emp e LEFT JOIN dept d ON e.dept_id = d.id")
List<Emp> list(); // 注意:SQL结尾不能加分号

Service层分页处理

public PageResult page(Integer page, Integer pageSize) {
    PageHelper.startPage(page, pageSize);  // 1. 设置分页参数
    List<Emp> empList = empMapper.list();  // 2. 执行查询(紧跟startPage)
    Page<Emp> p = (Page<Emp>) empList;     // 3. 强制转换为Page对象
    return new PageResult(p.getTotal(), p.getResult()); // 4. 封装结果
}

注意事项

  • SQL结尾禁止使用分号;
  • 分页仅对startPage()后的第一条SQL生效
  • PageResult是自定义类,包含total(总记录数)和data(当前页数据)

<where><if>

2025.7.15 新撰写笔记,原来的没干货,纯废话

<select id="getStudentsByConditions" resultType="com.itheima.domain.pojo.Student"
        parameterType="com.itheima.domain.query.StudentQueryDto">
    select s.*,c.name from student s left join clazz c on s.clazz_id = c.id
    <where>
        <if test="name != null and name != ''">
            and s.name = #{name}
        </if>
        <if test="degree != null">
            and s.degree = #{degree}
        </if>
        <if test="clazzId != null">
            and s.clazz_id = #{clazzId}
        </if>
    </where>
</select>
标签 作用
<where> 自动生成WHERE关键字,并移除首个条件前的AND/OR
<if test="条件"> 条件判断:test属性值为true时拼接SQL片段

原则

  • 条件开头必写 AND,会被 <where> 自动处理
  • 字符串参数双重检查,test="name != null and name != ''"
  • 参数名严格一致,test 属性名与 Java 参数名保持相同(区分大小写)
  • test 属性对应的是传入 SQL 的参数对象
  • 这里使用 clazzId(Java 属性名)而不是 clazz_id(数据库字段名)

参数来源对照? 好像还没接触到???

Java 参数类型 test 写法 使用示例
Map 的 key key名 test="age !=null"
POJO 的属性 属性名 test="name !=null"
单参数 _parameter test="_parameter>0"

旧笔记: 没干货,都是废话,仅保留做参考

动态SQL

Mapper接口添加方法

List<Emp> listByCondition(EmpPageQueryDTO dto);

XML映射文件实现动态SQL

<select id="listByCondition" resultType="com.example.pojo.Emp">
SELECT e.*, d.name deptName 
FROM emp e LEFT JOIN dept d ON e.dept_id = d.id
<where>
   <!-- 姓名模糊查询 -->
   <if test="name != null and name != ''">
       e.name LIKE CONCAT('%', #{name}, '%')
   </if>
   <!-- 性别精确查询 -->
   <if test="gender != null">
       AND e.gender = #{gender}
   </if>
   <!-- 入职日期范围 -->
   <if test="begin != null and end != null">
       AND e.entry_date BETWEEN #{begin} AND #{end}
   </if>
</where>
</select>

动态SQL标签解析

标签 作用
<where> 自动生成WHERE关键字,并移除首个条件前的AND/OR
<if> 条件判断:test属性值为true时拼接SQL片段
麻辣快捷键
Ctrl + Q 打开新查询窗口
Ctrl + R 执行整个Query
执行选中的SQL
Ctrl + Shift + R 执行光标处的SQL

查看表DDL语句

打开目标表,
在整个Navicat界面的右下角有一个隐藏或显示信息窗格选项,
点开之后右侧就会出现信息窗格,三个小table页,第二点开就是展示DDL


丝滑小连招

查看 - 结果 - 在新页中显示
这样子可以让底部的结果台,移动到新页面(好用)


员工管理 二

Preface

日期: 2025.7.12
代码: D:\BH\Cloud\Web开发\Web_Project\boot-tlias-project04

为以前的笔记补充两个知识点:

  • SQL count group by
  • Stream.peek() - 吞并至7.15笔记

但是以后知识点多起来的话,可能会存在管理困难.
所以现阶段考虑使用Obsidian来做笔记管理(待办事项)


[SQL count group by 超链接点我](##SQL 补充知识点)


启动 mysql

net start mysql 

cmd窗口需要 以管理员身份运行

我的电脑一点都不自觉,每天开机都不会自己启动(Maybe是我自己关闭了?)

关键词: mysql启动、mysql 启动、启动mysql


新增员工

2025.7.12
不得不说其实这些实战都是代码实践,这些就没有啥需要特别注意的了。
做成笔记也不现实,在线讲义也挺好的。
笔记Part只记录知识点吧,不会涉及太多的代码实践。

2025.7.13 08:49
被知识塞满了。
今天彭正鑫怎么没有早来?张淑涵也躲藏了?🥱
TreeMap的,隔壁308点名批评: 胡俊涛,沈键瑚 自习干什么这么早来卷!!!😡
我真的是不行了,我的HY宝贝怎么没有来!!!
大哥别废话了,启动学习模式.

MyBatis

主键回填

目的: 执行 INSERT 操作后, 自动将数据库生成的主键值 (如自增 ID) 填充回传入的 Java 实体对象

实现方式: 在 MyBatis 的 <insert> 标签中添加两个属性

  • useGeneratedKeys="true" 告知 MyBatis 使用数据库生成的主键
  • keyProperty="主键列对应的Java实体的属性名" 指定将生成的主键值设置到传入参数对象的哪个属性上

示例

<insert id="insertEmployee" parameterType="Employee" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO employee (name, department) VALUES (#{name}, #{department})
</insert>

批量插入

目的: 高效地将一个集合(如List)中的多个对象一次性插入数据库

核心标签: <foreach> 遍历

<foreach> 属性

  • collection 指定要遍历的集合

    要遍历的集合名称,写的是@Param注解中的名称。
    如果没有指定@Param,写集合的别名, 如List, 写list

  • item 正在遍历的元素

  • separator 遍历每个元素的分割符

  • open 遍历开始前要拼接的SQL片段

  • close 遍历结束后要拼接的SQL片段

  • index 遍历的下标(索引)

示例

@Mapper
public interface EmpExprMapper {
    Integer insertBatch(@Param("exprList") List<EmpExpr> exprList);
}
<insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
    insert into emp_expr
    (begin, end, company, job, emp_id)
    values
        <foreach collection="exprList" item="expr" separator=",">
            (#{expr.begin},#{expr.end}, #{expr.company}, #{expr.job}, #{expr.empId})
        </foreach>
</insert>

如果传递给xml的是一个对象可以直接用属性。

但是如果传递的是列表<对象>就一定要用<foreach>遍历

事务管理

概念

定义:
事务是数据库操作的逻辑单元, 包含一组不可分割的 SQL 操作,
这些操作要么全部成功(提交), 要么全部失败(回滚)

操作

MYSQL 事务控制
begin: 开启事务 commit: 提交事务 rollback: 回滚事务

-- 开启事务 (或 START TRANSACTION)
BEGIN;

-- 操作1: 插入员工数据
INSERT INTO emp VALUES (39, 'Tom', '123456', '汤姆', 1, '13300001111', 1, 4000, '1.jpg', '2023-11-01', 1, NOW(), NOW())

-- 操作2: 插入工作经历
INSERT INTO emp_expr (emp_id, BEGIN, END, company, job) 
VALUES (39, '2019-01-01', '2020-01-01', '百度', '开发'), 
       (39, '2020-01-10', '2022-02-01', '阿里', '架构')

-- 根据执行结果选择
COMMIT;  -- 全部成功时提交
ROLLBACK; -- 任一失败时回滚

@Transactional

概念

容器化执行框架: Spring 事务管理

@Service
public class EmployeeService {
    @Transactional // 核心注解
    public void addEmployeeWithExperience(Employee emp, List<Experience> expList) {
        employeeDao.insert(emp)          // 操作1
        experienceDao.batchInsert(expList) // 操作2
    }
}

位置:业务层(service)的方法上、类上、接口上

  • 方法上: 当前方法交给spring进行事务管理
  • 类上: 当前类中所有的方法都交由spring进行事务管理
  • 接口上: 接口下所有的实现类当中所有的方法都交给spring 进行事务管理

事务日志监控

可以在application.yml配置文件中开启事务管理日志,这样就可以在控制看到和事务相关的日志信息了

# application.yml
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

异常回滚控制

@Transactional注解 --> 来实现事务管理。
默认情况下,只有出现 RuntimeExceptionError 才回滚异常。

rollbackFor 属性用于控制出现何种异常类型,回滚事务

@Override
@Transactional(rollbackFor = Exception.class)
public void addEmpAndExprList(Emp emp) {
    try {
        // 业务逻辑 是否回滚
    } finally {
        // 独立事务管理,确保日志一定输出
        EmpLog empLog = new EmpLog();
        empLog.setOperateTime(LocalDateTime.now());
        empLog.setInfo(emp.toString());
        empLogMapper.insert(empLog);
}

事务传播行为

propagation 属性

Q: 什么是事务的传播行为呢?

A: 就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

传播行为

传播行为 特性
REQUIRED 加入当前事务, 无事务时新建 (默认)
REQUIRES_NEW 挂起当前事务, 始终新建独立事务
SUPPORTS 如果外部方法有事务则加入,无事务则无事务

应用场景 - 订单日志独立事务

@Override
@Transactional(rollbackFor = Exception.class)
public void addEmpAndExprList(Emp emp) {
    try {
        // 业务逻辑 是否回滚
    } finally {
        // 日志管理
        EmpLog empLog = new EmpLog();
        empLog.setOperateTime(LocalDateTime.now());
        empLog.setInfo(emp.toString());
        empLogMapper.insert(empLog);
}
@Service
public class EmpLogServiceImpl implements EmpLogService {
    @Autowired
    private EmpLogMapper empLogMapper;
    // REQUIRES_NEW 独立事务管理,确保日志一定输出
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void insertLog(EmpLog empLog) {
        empLogMapper.insert(empLog);
    }
}

事务特性

ACID
atomicity 原子性 事务是一个不可分割的单元,里面的操作要么全部成功,要么全部失败
consistency 一致性 事务开始前到事务结束后,数据总量保持不变
isolation 隔离性 事务之间相互隔离,互不影响
durability 持久性 事务一旦提交,数据永久的持久化

文件上传

阿里云对象存储 OSS 知识梳理

OSS 简介

  • 阿里云对象存储服务 (Object Storage Service)
  • 核心特性: 海量, 安全, 低成本, 高可靠
  • 用途: 通过网络存储和调用非结构化数据文件 (文本, 图片, 音频, 视频)

使用前准备

  • 账号准备 需要有效阿里云账号

  • 开通 OSS 服务

    1.登录阿里云控制台
    2.进入 产品 > 存储 > 对象存储 OSS

    点击 开通服务

AccessKey (AK) & SecretKey (SK)

  • 作用: 身份验证和授权访问 OSS API/SDK 的核心凭证
  • 组成
    • AccessKey ID: 用户标识
    • AccessKey Secret: 身份验证密钥 (必须严格保密)
  • 获取: 阿里云控制台 AccessKey 管理页面

参数配置化

目标: 分离 OSS 连接参数 (Endpoint, BucketName, AK/SK), 提升安全性与灵活性

方法一: @Value 注解 (属性逐个注入)

  • 配置文件 (application.yml)

    aliyun:
      oss:
        endpoint: https://oss-cn-hangzhou.aliyuncs.com
        bucketName: like91
    
  • Bean 中使用

    @Component
    public class AliyunOSSOperator {
        @Value("${aliyun.oss.endpoint}")
        private String endpoint;
        @Value("${aliyun.oss.bucketName}")
        private String bucketName;
        // ... 其他属性注入
    }
    
  • 适用场景: 少量属性注入

方法二: @ConfigurationProperties 注解 (属性批量绑定)

  • 配置属性类

    @Component
    @ConfigurationProperties(prefix = "aliyun.oss")
    public class AliyunOSSProperties {
        private String endpoint;
        private String bucketName;
        // Getter/Setter 方法 (必须提供)
        public String getEndpoint() { return endpoint; }
        public void setEndpoint(String endpoint) { ... }
    }
    
  • 注入使用

    @Component
    public class AliyunOSSOperator {
        private final AliyunOSSProperties ossProperties;
        @Autowired
        public AliyunOSSOperator(AliyunOSSProperties ossProperties) {
            this.ossProperties = ossProperties;
        }
    }
    
  • 适用场景: 多属性集中管理, 类型安全

方案对比

  • @Value: 简单直接, 适合分散属性
  • @ConfigurationProperties: 结构化, 适合批量属性 (推荐属性多时使用)

员工管理 三

日期: 2025.7.14
代码: D:\BH\Cloud\Web开发\Web_Project\boot-tlias-project04

失误了,忘记和昨天的代码区分了,直接接着写了,下次不可以犯这种毛病了~

Spring Web 基础技术

这里可以和参数接收,绑定一下

基本参数处理-数组参数接收

MyBatis 进阶交互

古往今来,时间的长河,整合 v9.1

字段映射解决方案-结果映射

SQL 逻辑控制结构

埋下伏笔??? 有机会好好整理一下

  • 核心 MyBatis 动态 SQL 标签共 9 个<if>, <choose>/<when>/<otherwise>, <trim>, <where>, <set>, <foreach>, <bind>。(<sql><include> 是片段定义和引用,不算严格意义上的“动态”标签,但密切相关)。
  • CASE 表达式是 SQL 标准语法。
  • IF() 函数是特定数据库的函数。
  • WHERE IF” 是 <where><if> 组合使用的常见模式。

CASE 表达式

功能: 在 SQL 查询中进行条件判断和值转换

两种形式

  • 条件判断式 (多条件分支)

    SELECT CASE
        WHEN salary > 10000 THEN '高级'
        WHEN salary > 5000 THEN '中级'
        ELSE '初级'
    END AS level
    
  • 等值匹配式 (字段值匹配)

    SELECT CASE job
        WHEN 1 THEN '班主任'
        WHEN 2 THEN '讲师'
        ELSE '其他'
    END AS pos
    

核心特性

  • 必须用 END 闭合
  • 结果类型由第一个 THEN 决定
  • 支持嵌套使用
  • 可用于 SELECT, WHERE, GROUP BY 等子句

IF 函数

功能: 简单条件判断

语法形式

IF(condition, true_value, false_value)

应用场景

-- 性别统计
SELECT 
    IF(gender = 1, '男', '女') AS gender_name,
    COUNT(*) AS count
FROM emp
GROUP BY gender;

-- 空值处理
SELECT IFNULL(email, '未填写') AS email

IFNULL 函数

功能: 空值兜底处理
语法: IFNULL(expr, replace_value)

SELECT IFNULL(email, '未填写') AS email FROM user;

<set> 标签

功能 动态生成 UPDATE 语句的 SET 子句

XML 示例

<update id="update">
  UPDATE emp
  <set>
    <if test="name != null">name = #{name},</if>
    <if test="salary != null">salary = #{salary},</if>
    update_time = NOW()
  </set>
  WHERE id = #{id}
</update>

核心特性

  • 自动处理末尾逗号
  • 无有效条件时忽略 SET 关键字
  • 通常配合 <if> 标签使用
  • 支持所有字段的条件判断

对比表

技术 层级 主要用途 特点
CASE 表达式 SQL 复杂条件分支与值转换 支持多分支,功能强大
IF 函数 SQL 简单二元条件判断 语法简洁,易于理解
<set> 标签 MyBatis XML 动态生成 UPDATE 语句 自动逗号处理,防止语法错误

异常处理机制

全局异常处理

核心注解

  • @RestControllerAdvice
    组合注解: @ControllerAdvice + @ResponseBody
    作用: 声明全局异常处理器, 自动将方法返回值转为 JSON 格式响应

异常捕获方法

  • @ExceptionHandler
    作用: 标记处理特定异常的方法
    value: 指定捕获的异常类型(如 value = Exception.class), 可省略(默认捕获方法形参类型)

方法定义规范

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler
    public Result handleException(Exception e) {
        log.error("全局异常处理器捕获异常", e);
        return Result.error("操作失败, 请稍后重试");
    }
}

执行流程

  • 发生异常时, Spring 扫描所有 @RestControllerAdvice
  • 匹配 @ExceptionHandler 中定义的异常类型
  • 执行匹配的异常处理方法, 返回 JSON 响应

组件

组件 作用
@RestControllerAdvice 声明全局异常处理器, 自动转换返回值为 JSON
@ExceptionHandler 标记异常处理方法, 通过 value 指定捕获的异常类型(可省略)
异常处理参数 方法形参声明捕获的异常类型(如 Exception e)
返回值 自动转为 JSON 响应, 推荐统一封装结果对象(如 Result)

2025.7.16-补
自定义异常要继承RuntimeException,否则全局异常无法捕获

public class CustomException extends RuntimeException {
    public CustomException() {
        super();
    }

    public CustomException(String message) {
        super(message);
    }
}

功能实现

删除员工

批量删除接口设计
Service 层逻辑实现

修改员工

复杂对象查询 (ResultMap 嵌套映射)
动态字段更新 (结合 )

信息统计

职位分布统计
CASE 实现职位转文字
分组聚合统计人数
JobOption VO 设计:

  • jobList: 职位名称列表
  • dataList: 对应人数列表

性别比例统计
IF() 函数转换性别标识
分组聚合统计数量

自己实战

日期: 2025.7.15
代码: D:\BH\Cloud\Web开发\Web_Project\boot-tlias-project05

日期: 2025.7.16
代码: D:\BH\Cloud\Web开发\Web_Project\July\tlias-project0716

Stream 知识补充

Stream.peek() 方法

2025.7.12 补充知识点 JavaSE 笔记同步补充

作用: 中间操作, 允许对流元素执行副作用操作(如修改状态), 不中断流处理.

案例

// peek() 修改集合中的属性值,收集后生效
emp.getExprList().stream()
    .peek(item -> item.setEmpId(emp.getId()))  // 遍历修改每个元素的 empId
    .toList();  // 终止操作触发执行

Stream.map() 方法

2025.7.15 核心操作 Java Stream API 精要

作用: 中间操作,对流中每个元素执行1:1转换操作,生成新元素类型的新流.

案例

@Override
public ClazzNumberOption getStudentCountData() {
    // 获取数据库数据 ClazzNumberInfo 接收 clazzName,count(*)
    List<ClazzNumberInfo> clazzNumberInfos = studentMapper.getStudentCountData();
    // 处理数据,处理成前端需要的双列表
    List<String> clazzList = clazzNumberInfos
        .stream()
        .map(ClazzNumberInfo::getClazzName)
        .toList();
    
    List<Integer> dataList = clazzNumberInfos
        .stream()
        .map(ClazzNumberInfo::getStudentNumber)
        .toList();
    
    return new ClazzNumberOption(clazzList,dataList);
}

idea 调试

操作
Step Over 执行此行,停在下一行,执行整个函数,停在函数后一行
Step Into 执行此行,停在下一行,进入函数内部,停在函数第一行
Step Out 执行当前函数剩余部分,回到调用处下一行(仅在函数内有效)
Resume Program 跳到下一个断点

登录认证

日期: 2025.7.17
代码: D:\BH\Cloud\Web开发\Web_Project\July\boot-tlias-project06

登录功能

DTO 核心概念

  • Data Transfer Object: 数据传输对象, 用于封装业务层与表示层之间的数据
  • 设计目的: 解耦领域模型与表示层, 避免暴露敏感数据
  • 典型应用: 登录参数传递

实现代码

@Override
public LoginVO login(LoginDTO loginDTO) {
    // 参数校验
    String username = loginDTO.getUsername();
    String password = loginDTO.getPassword();
    if(StrUtil.isBlank(username) || StrUtil.isBlank(password)){
        throw new CustomException("用户名和密码都必须填写");
    }
    
    // 业务处理
    Emp emp = empMapper.selectByUsername(username);  // SQL 优化: limit 1
    if(emp == null){
        throw new CustomException("用户名不存在");
    }
    
    if(!emp.getPassword().equals(password)){  // 实际需 MD5 加密比对
        throw new CustomException("用户名或者密码错误");
    }
    
    // 状态判断(如账号冻结检测)
    
    // 数据封装
    return LoginVO.builder()
        .id(emp.getId())
        .name(emp.getName())
        .username(username)
        .token("TOKEN123")  // 实际应生成 JWT
        .build();
}

多拿1k+

SQL 优化: SELECT * FROM emp WHERE username = ? LIMIT 1

安全规范

  • 密码必须加密存储(MD5/SHA256)
  • 禁止WHERE username=? AND password=?明文查询

错误提示: 统一返回"用户名或密码错误"避免信息泄露

登录校验

会话技术

  • 会话: 浏览器-服务器从连接到断开的过程
  • 会话跟踪: 识别多次请求是否来自同一浏览器

三大方案

方案 原理 优点 缺点
Cookie 客户端存储键值对(Set-Cookie 响应头/Cookie 请求头) HTTP 原生支持 移动端兼容差, 不安全, 用户可禁用, 不能跨域
Session 服务端存储会话数据, 通过 SessionID Cookie 识别客户端 数据存储在服务端, 相对安全 集群环境无效, 依赖 Cookie 的缺点
令牌技术 客户端携带自包含令牌(如 JWT)访问服务端 支持多端, 无状态, 跨域, 集群 需自行实现令牌管理

工程实践: 现代系统首选 JWT 方案

JWT 技术

  • 全称: JSON Web Token (https://jwt.io/)

  • 核心组成:

    graph LR A[JWT] --> B[Header] A --> C[Payload] A --> D[Signature] B -->|存储| E[算法/类型] C -->|存储| F[用户数据] D -->|生成| G[Header+Payload+密钥]
  • Base64 编码: 基于 64 个可打印字符的二进制数据编码方式

  • 依赖配置:

    <!-- JWT依赖-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    <!-- JWT 如果出现异常,需要添加以下依赖 -->
    <!-- java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter -->
    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.3.1</version>
    </dependency>
    

统一拦截技术

过滤器

核心概念: JavaWeb 三大组件之一, 可拦截资源请求

开发流程

  • 定义一个类,实现Filter接口,重写抽象方法
  • 在类上标记@WebFilter("urlPatterns=要拦截路径")
  • 在启动类加@ServletComponentScan

完整生命周期

@WebFilter("/*")
public class DemoFilter implements Filter {
    // Tomcat 反射创建调用Filter
    public DemoFilter() {
        System.out.println("构造函数");
    }
    
    // 初始化方法,在Filter被创建后调用,只执行一次
    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("初始化方法");
    }
    
    // 拦截操作都在这里执行
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
        System.out.println("[Filter 前] 请求处理前逻辑");
        // 放行请求(关键点)
        chain.doFilter(req, res);
        System.out.println("[Filter 后] 响应返回前逻辑");
    }
    
    // Filter 被销毁时调用,只执行一次
    // 销毁时机在 Tomcat 停止前一刻
    @Override
    public void destroy() {
        System.out.println("销毁方法");
    }
}

拦截路径

匹配模式 示例 说明
精确路径 /login 仅拦截 /login
目录拦截 /admin/* 拦截 /admin 下所有一级路径
(/admin也会拦截)
多级目录 /api/** 拦截 /api 所有子路径
全路径拦截 /* 访问所有资源, 都会被拦截

拦截器

核心概念: Spring 提供的动态拦截机制

开发流程

  • 创建一个类,实现 HandlerInterceptor 接口,重写抽象方法

  • 添加@Component,将拦截器交给Spring管理

  • 增加一个配置类,实现WebMvcConfigurer接口,重写addInterceptors方法,内部注册拦截器:

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Autowired
        private AuthInterceptor authInterceptor;
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(authInterceptor)
                    .addPathPatterns("/**")     // 拦截所有路径
                    .excludePathPatterns("/login");  // 排除登录
        }
    }
    

核心方法执行顺序

@Component
public class DemoInterceptor implements HandlerInterceptor {
    // Controller 方法执行前
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("[Interceptor 前] 在 Handler 执行之前按执行");
        // true=放行, false=拦截
        return true;
    }
    
    // Controller 方法执行后, 视图渲染前
    @Override 
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        System.out.println("[Interceptor 中] 在 Handler 执行之后,渲染视图之前执行");
    }
    
    // 整个请求完成后(视图渲染完毕)
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("[Interceptor 后] 在渲染视图之后执行,做一些释放资源的动作");
    }
}

拦截路径

拦截路径 含义 举例
/* 一级路径 能匹配/depts,/emps,/login,不能匹配 /depts/1
/** 任意级路径 能匹配/depts,/depts/1,/depts/1/2
/depts/* /depts下的一级路径 能匹配/depts/1,不能匹配/depts/1/2,/depts
/depts/** /depts下的任意级路径 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1

Filter vs Interceptor

特性 Filter Interceptor
规范 Servlet API(jakarta.servlet) Spring 框架
接口规范 过滤器需要实现 Filter 接口 拦截器需要实现 HandlerInterceptor 接口
拦截范围 所有资源(静态文件, JSP) 仅拦截Spring环境中的资源。
执行顺序 优先于 Interceptor 后于 Filter
路径配置 @WebFilter注解 addPathPatterns()方法
生命周期 服务器启动到关闭 Spring 上下文生命周期

AOP

日期: 2025.7.18
代码: D:\BH\Cloud\Web开发\Web_Project\July\boot-tlias-project07

AOP 基础

概念

Spring两大基石

  • IOC:控制反转,管理对象依赖关系
  • AOP:面向切面编程,核心编程思想

核心概念

  • 面向切面编程(Aspect Oriented Programming)
  • SpringAOP是AOP思想的实现, 底层基于动态代理
  • 核心目标:在不修改源码的情况下对方法进行功能增强

动态代理实现方式

代理类型 要求 特点
JDK动态代理 需接口 基于接口实现代理
CGLib动态代理 无需接口 基于子类继承实现代理

AOP 核心价值

  1. 减少重复代码(如日志、权限等通用逻辑)
  2. 代码无侵入(业务逻辑与增强逻辑解耦)
  3. 提高开发效率(聚焦核心业务开发)
  4. 维护方便(横切关注点集中管理)

快速入门

SpringAOP开发步骤

graph LR A[引入AOP依赖] --> B[编写切面类] B --> C[定义切入点] C --> D[实现增强逻辑]
// 表示当前类是一个切面类
@Aspect
@Component
@Slf4j
public class RecordTimeAspect {
    @Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        // 记录Start
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        // 记录End
        long end = System.currentTimeMillis();
        // 统计时长
        log.warn("Spend time: {} ms", end - start);
        return result;
    }
}

AOP 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

AOP 进阶

核心概念

生肉

  • 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
  • 通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)
  • 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
  • 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
  • 目标对象:Target,通知所应用的对象
熟肉
目标对象 Target 要增强功能的类的对象
连接点 JoinPoint 目标对象中所有的方法
切入点 PonintCut 真正增强了功能的链接点
通知 Advice 增强了功能的代码
切面 Aspect 切入点+通知

通知类型

通知类型
@Around 环绕通知 所有通知的功能都具备
必须要有返回值(Object)
必须要有明确的调用目标方法
@Before 前置通知 在目标方法执行之前执行
@After 后置通知 无论目标方法执行是否出异常,都要执行
@AfterReturning 返回后通知 在目标方法正确执行之后执行
@AfterThrowing 异常后通知 在目标方法出异常时执行

执行时机: AOP在拦截器之后执行

实际开发过程中大多数情况用的是 @Around

通知顺序

当有多个切面的切入点都匹配到了目标方法, 目标方法运行时, 多个通知方法都会被执行。

执行顺序

不同切面类中, 默认按照切面类的类名字母排序:

  • 目标方法前的通知方法:字母排名靠前的先执行
  • 目标方法后的通知方法:字母排名靠前的后执行

@Order(数字) 加在切面类上来控制顺序

  • 目标方法前的通知方法:数字小的先执行
  • 目标方法后的通知方法:数字小的后执行

郭: @Order(值):值越小优先级越高

切入点表达式

execution 表达式

作用: 通过方法签名信息(返回值、类名、方法名、参数等)匹配连接点

完整语法

execution(访问修饰符? 返回值 包名.类名.?方法名(参数列表) throws 异常?)

符号说明

  • ? 表示可省略部分(如访问修饰符、类名、异常声明)
  • * 通配符,匹配任意单个元素(返回值、包名、类名、方法名、单参数类型)
  • .. 通配符,匹配任意多个连续元素(多级子包、任意个数参数)

语法规则

部分 规则
访问修饰符 可省略(如public)
返回值 可用 * 匹配任意类型
包名 单层包用 *(com.*.service),多层子包用 ..(com..service)
类名 可用 * 匹配任意类(*Service)
方法名 可用 * 匹配任意方法(get*)
方法参数 * 匹配单参数,.. 匹配任意个数参数,() 匹配无参方法
异常声明 可省略

示例

// 匹配 com.service 包下任意类的无参方法
execution(* com.service.*.*()) 

// 匹配 com..service 包及其子包中 delete 开头的方法
execution(* com..service.*.delete*(..))

// 匹配 UserService 接口的所有方法
execution(* com.service.UserService.*(..)) 

根据业务需要,可以使用 && || ! 来组合比较复杂的切入点表达式。

@annotation 表达式

作用: 通过自定义注解标记匹配方法,解决无规则方法匹配问题
使用场景: 替代复杂的 execution 组合(如 list() + delete())

实现步骤

  1. 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyLog {}
  1. 标记方法
public class UserService {
    @MyLog
    public void delete() { ... }
}
  1. 配置切面
@Pointcut("@annotation(com.example.MyLog)")
public void logPointcut() {}

AOP 案例

封装有一定难度的,可以自己去翻上课的代码,笔记中不过多赘述展示

pjp 操作

// 获取目标类全限定名
String className = pjp.getTarget().getClass().getName();
operateLog.setClassName(className);

// 获取执行的方法名
String methodName = pjp.getSignature().getName();
operateLog.setMethodName(methodName);

// 获取方法参数
Object[] args = pjp.getArgs();
if (args != null && args.length > 0) {
    // 将参数数组转换为字符串
    String methodParams = Arrays.asList(args).toString();
    operateLog.setMethodParams(methodParams);
}

方法详解表

方法调用 作用说明 返回示例
pjp.getTarget() 获取被代理的原始对象(实际执行业务的 Controller 实例) DeptController@4856
getTarget().getClass() 获取目标对象的 Class 对象 class com.itheima.controller.DeptController
getClass().getName() 获取完整的类名(包名 + 类名) "com.itheima.controller.DeptController"
pjp.getSignature() 获取方法签名对象(包含方法声明信息) MethodSignature 对象
getSignature().getName() 从签名中提取方法名称 "deleteDept"
pjp.getArgs() 获取被拦截方法的参数值数组 [1001, "行政部"]
Arrays.asList(args).toString() 将参数数组转换为可读的字符串形式 "[1001, "行政部"]"

核心概念说明

ProceedingJoinPoint (pjp) 对象

  • AOP 切面的核心操作对象
  • 封装了被拦截方法的所有上下文信息
  • 相当于方法的 "快照"

方法调用链关系

graph LR A[pjp.getTarget] --> B[获取目标对象] B --> C[getClass] C --> D[getName] E[pjp.getSignature] --> F[获取方法签名] F --> G[getName] H[pjp.getArgs] --> I[获取参数数组] I --> J[Arrays.toString]

实际应用场景

  • 类名 + 方法名 → 确定具体操作(例如 DeptController.update)
  • 参数列表 → 记录操作内容(例如 更新 ID=100 的部门)
  • 返回值 → 记录操作结果(成功/失败)

自定义注解

自定义注解

  • Java 中的一种特殊接口类型(使用 @interface 声明)
  • 用于为代码添加元数据(描述数据的数据)
  • 本身不包含业务逻辑
  • 可被编译器、框架或反射机制读取并执行特定操作

定义自定义注解

// 定义注解保留策略为运行时
@Retention(RetentionPolicy.RUNTIME)
// 定义注解目标为方法
@Target({ElementType.METHOD})
// 定义注解
public @interface LogAnnotation {
    // 可在此处添加注解属性
    // 例如: String value() default "";
}

元注解 (修饰注解的注解)

@Retention: 定义注解的生命周期

  • SOURCE: 仅保留在源码中(例如 @Override)
  • CLASS: 保留到字节码文件(默认值)
  • RUNTIME: 运行时可通过反射读取(示例代码的选择)

@Target: 定义注解可应用的位置

  • METHOD: 方法(示例代码的选择)
  • TYPE: 类或接口
  • FIELD: 字段
  • 其他: PARAMETER, CONSTRUCTOR

ThreadLocal

概念

ThreadLocal 并不是一个Thread,而是Thread的局部变量。
ThreadLocal 为每个线程提供一份单独的存储空间,具有线程隔离的效果,不同的线程之间不会相互干扰。
用于在同一个线程上去共享数据的一个类,内部使用 ThreadLocalMap 来存储数据

常用方法
public void set(T value) 设置当前线程对应线程局部变量的值
public T get() 获取当前线程对应线程局部变量的值
public void remove() 移除当前线程对应线程局部变量

内存泄漏问题

ThreadLocal 变量存储在 Thread 内部 ThreadLocalMap 中
ThreadLocalMap 的 Key 是 ThreadLocal 对象的弱引用(WeakReference)
ThreadLocalMap 的 Value 是实际变量值的强引用
弱引用在GC回收时会被回收,强引用不会,
所以强引用就会在内存中一直存在,可能会出现内存泄漏问题。

过滤器 vs 拦截器清理:

  • 过滤器:

    destroy():应用关闭执行(全局资源清理)
    doFilter()finally块清理 (chain.doFilter()阻塞至请求完成)

  • 拦截器:

    afterCompletion:每个请求执行(请求级清理)
    afterCompletion()清理 (请求完全结束,视图渲染后)

Java 反射

老葛~ 没讲反射 ~ 洋哥 ~ 补课 ~

概念

反射(Reflection): 是 Java 在运行时动态操作类/对象的能力
核心作用: 加载类并解剖类结构
操作目标:

  • 获取类信息 (Class 对象)
  • 操作构造器 (Constructor)
  • 操作成员变量 (Field)
  • 操作成员方法 (Method)

获取 Class 对象

@Test
public void testGetClass() throws Exception {
    // 三种获取class的方法
    // 方式1: 类名.class
    Class c1 = Stu.class;
    // 方式2: 对象.getClass()
    Class c2 = new Stu().getClass();
    // 方式3: Class.forName("全限定类名")
    Class c3 = Class.forName("com.itheima.reflect.Stu");
    // 验证三种方式获取相同 Class 对象
    System.out.println(c1);
    System.out.println(c2);
    System.out.println(c3);
    System.out.println(c1 == c2);
    System.out.println(c2 == c3);
}

关键特性: 相同类加载器下, 类字节码在 JVM 中唯一存在

操作

构造器 (Constructor)

Class clazz = Class.forName("com.itheima.reflect.Stu");

// 获取所有构造器 (含私有)
Constructor[] constructors = clazz.getDeclaredConstructors();

// 创建无参实例 (过时方法)
Object obj = clazz.newInstance();

// 获取无参构造器创建实例
Constructor c1 = clazz.getConstructor();
Object o1 = c1.newInstance();

// 获取带参构造器创建实例
Constructor c2 = clazz.getDeclaredConstructor(Integer.class);
Object o2 = c2.newInstance(1001);

// 访问私有构造器
Constructor c3 = clazz.getDeclaredConstructor(Integer.class, String.class);
// 突破封装性
c3.setAccessible(true); 
Object o3 = c3.newInstance(1001, "Lucy");

成员变量 (Field)

Class clazz = Class.forName("com.itheima.reflect.Stu");
Object obj = clazz.newInstance();

// 获取所有 public 字段
clazz.getFields();

// 获取指定字段 (含私有)
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
// 设置字段值 参数一: 对象    参数二: 属性
nameField.set(obj, "Lucy");
System.out.println(nameField.get(obj));

成员方法 (Method)

Class clazz = Class.forName("com.itheima.reflect.Stu");
Object obj = clazz.newInstance();

// 获取所有方法 (含私有)
Method[] methods = clazz.getDeclaredMethods();

// 调用带参方法
Method eatMethod = clazz.getMethod("eat", String.class);
Object result = eatMethod.invoke(obj, "Lucy");

作用

解剖类结构: 运行时获取类完整信息

绕过泛型约束:

List<String> strList = new ArrayList<>();
strList.add("合法");

// 通过反射添加整数类型 (绕过泛型检查)
Method addMethod = strList.getClass().getMethod("add", Object.class);
addMethod.invoke(strList, 100);

突破封装性: 访问私有成员 (setAccessible(true))

动态代理: AOP 编程实现基础

框架设计: Spring, MyBatis 等框架底层核心

SpringBoot 原理

日期: 2025.7.20
代码: D:\BH\Cloud\Web开发\Web_Project\July\boot-tlias-project08
代码: D:\BH\Cloud\Web开发\Web_Project\July\Web-boot

这课程疑似有点科幻了🐷 AI 启动

WOC 我终于发现之前写阿里云的时候,问题出在哪里了!!!

我配置文件中写的是 region: cn-hangzhou; 我真的不行了哪里来的 ; ??????
难怪一直写错我真的不行啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊🦅🦅🦅🦅🦅🐢

?

孩子们无敌了,就这样吧,笔记凑合着看吧,今天不记了,今天有今天的课程(7.21)

配置优先级

配置文件

properties yml yaml

配置参数

Java系统属性

-Dserver.port=9090

命令行参数

--server.port=10010

IDEA 中如何设置?

在启动项中选择 - Edit Configurations - Build and Run 选项中 Modify options -

添加 Add VM options - 勾选 Program arguments

配置优先级(高->低)

  1. 命令行参数
  2. Java系统属性
  3. application.properties
  4. application.yml
  5. application.yaml

Bean 的管理

Bean 的作用域

设置作用域: @Scope

singleton: 默认值,单例

prototype: 原型,多例

第三方 Bean

@Bean

  • 加方法上
  • 默认BeanName为方法名,可以通过value属性手动指定bean名称
  • 形参可以完全自动注入

核心作用

第三方库的对象(如 DataSource, RestTemplate)纳入 Spring 容器管理

三大核心特性

位置
@Bean 加在配置类的方法上(配置类需标注 @Configuration)

@Configuration
public class AppConfig {
    @Bean  // 关键注解
    public DataSource dataSource() {
        return new DruidDataSource(); // 返回第三方对象
    }
}

Bean 命名规则

  • 默认: 使用方法名(如上例 Bean 名为 dataSource)

  • 自定义: @Bean("myBean") 显式指定名称

    // 自定义 Bean 名
    @Bean("primaryDB")
    public DataSource masterDataSource() {...}
    

自动依赖注入 (核心优势)
方法形参由 Spring 自动注入容器中的 Bean:

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) { 
    // ↑ Spring 自动注入已存在的 DataSource Bean
    return new JdbcTemplate(dataSource);
}

解决依赖歧义

当容器存在多个同类型 Bean 时, 用 @Qualifier 指定:

@Bean
public JdbcTemplate primaryJdbcTemplate(
        @Qualifier("primaryDB") DataSource ds) { // 明确指定 Bean
    return new JdbcTemplate(ds);
}

起步依赖

None

起步依赖就是starter --> 用来启动 autoconfiguration

自动配置

实现方案

@ComponentScan 的局限性

  • 原理: 扫描启动类所在包(com.itheima)及其子包, 注册 @Component 等注解的类。
  • 问题: 无法加载第三方依赖(路径如 com.example), 因默认扫描范围不包含外部包。

@Import 动态导入

三种形式

  • 导入普通类
    @Import(SomeClass.class) → 注册为 Bean(需默认构造器)。

  • 导入配置类
    @Import(Config.class) → 加载 @Configuration 中的 @Bean

  • 导入 ImportSelector 实现类

    class MySelector implements ImportSelector {  
        String[] selectImports() {  
            // 动态返回配置类全限定名  
            return new String[]{"com.example.AutoConfig"};  
        }  
    }  
    

@EnableXxxxx 注解

  • 本质: 对 @Import 的封装。
  • 流程
    1. 定义注解(如 @EnableCaching)并标注 @Import({CacheConfig.class, CacheSelector.class})
    2. 用户使用 @EnableXxxxx 激活功能。
    3. Spring 执行 @Import 逻辑:
      • 加载配置类中的 @Bean;
      • 或执行 ImportSelector.selectImports() 动态加载配置。
  • 优势
    • 简化配置(一键开启功能);
    • 支持 @ConditionalOnXxx 条件化装配(如 @ConditionalOnClass);
    • 第三方 Starter 依赖提供此注解。

源码跟踪

核心架构:一个核心注解 + 一个启动方法

@SpringBootApplication

作用: 标注在主启动类上

本质: 复合注解,包含以下三个关键注解

  • @SpringBootConfiguration

    • 底层是 @Configuration

    • 表明启动类本身也是一个配置类,可以在其内部定义相关的 @Bean

  • @ComponentScan

    • 表示组件扫描

    • 默认会扫描启动类包以及子包下Spring能识别的组件

  • @EnableAutoConfiguration

    • 启用 Spring Boot 自动配置

    • 底层依赖于 @Import(AutoConfigurationImportSelector.class)

@EnableAutoConfiguration 工作流程

  1. 底层是一个 @Import 注解,该注解中导入了 AutoConfigurationImportSelector.class
  2. 调用 AutoConfigurationImportSelector.selectImports()
  3. 内部调用 getAutoConfigurationEntry()
  4. 通过 getCandidateConfigurations() 加载自动配置类
    • META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  5. 过滤候选配置类
    • 依据条件注解如 @ConditionalOnClass
    • 检查项目依赖和排除项
  6. 最终,将满足条件的自动配置类的全类名封装成一个字符串数组

SpringApplication.run()

作用: 启动应用,创建并初始化 Spring 容器 ApplicationContext

执行流程

  1. 初始化 Spring 容器时,处理 @EnableAutoConfiguration 导入的自动配置类
  2. 将筛选后的自动配置类加载到内存
  3. 解析自动配置类中的定义(@Bean, @Conditional 等)
  4. 创建符合条件的 Bean 并注册到 IoC 容器

完整流程总结

  1. 执行 SpringApplication.run() 启动应用
  2. 解析启动类上的 @SpringBootApplication
  3. @EnableAutoConfiguration 触发 AutoConfigurationImportSelector
  4. 加载并筛选 AutoConfiguration.imports 中的自动配置类
  5. 导入筛选后的自动配置类
  6. Spring 容器初始化时处理这些类
  7. 执行自动配置类逻辑,创建并注册符合条件的 Bean
  8. 应用启动完成,可直接使用自动配置的 Bean(如 DataSource, MVC 组件等)

郭の版本(可供参考)

1、一个注解

启动类上 SpringBootApplication 注解,它是一个复合注解,内部有三个注解组成

  1. SpringBootConfiguration,底层是一个Configuration,表示启动类也是一个配置类,可以在内部定义相关Bean
  2. ComponentScan,表示组件扫描,默认会扫描启动类包及子包下Spring能识别的组件
  3. EnableAutoConfiguration,底层是一个Import注解,该注解中导入 AutoConfigurationImportSelector类,该类中有一个 selectImports 方法会被调用,该方法最终会去加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的内容,并筛选满足starter和各种Condtional条件的全类名,封装成一个字符串数组加载到内存中

2、一个方法

启动类会通过run方法去创建与初始化Spring容器,在初始化时会把上面注解加载到内存中的字符串数组对应的内容,创建成对象注入到Spring容器,至此就完成了自动化装配

@Conditional

作用: 根据特定条件决定是否注册Bean到Spring容器,实现按需装配。

关键派生注解

@ConditionalOnClass

  • 条件: Classpath中存在指定类
  • 场景: 确保依赖库存在才配置Bean

@ConditionalOnMissingBean

  • 条件: 容器中不存在指定类型/名称的Bean
  • 场景: 提供默认Bean,允许用户自定义覆盖

@ConditionalOnProperty

  • 条件: 配置文件中存在指定属性且值匹配
  • 场景: 通过配置文件开关功能模块

位置控制

  • 类上: 控制整个配置类是否生效
  • @Bean方法上: 控制单个Bean是否注册

通过OnClass(依赖存在),OnMissingBean(用户未提供),OnProperty(配置开启)
三大条件精准控制Bean注册。

自定义 Starter

  1. 定义一个 xxx-boot-starter 负责管理依赖(导入下面的 xxx-boot-autoconfigure 依赖)
  2. 定义一个 xxx-boot-autoconfiguration
    • 完成功能代码
    • 定义一个 XxxAutoConfiguration
      • 类中完成Bean的创建和注入
    • 通过Spring自动化装配原理完成 XxxAutoConfiguration 类的初始化操作
      • META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中增加 XxxAutoConfiguration 全类名

Install 流程

先把 autoconfiguration install 了
再把 starter install 这样自定义Starter就完美可用啦!可以在其他项目中使用了~

Maven 高级

日期: 2025.7.21

代码:

我的代码:

  • D:\BH\Cloud\Web开发\Web_Project\July\boot-tlias-project09 我的正常运行原代码
  • D:\BH\Cloud\Web开发\Web_Project\July\web-boot-tlias-project0902 分模块设计代码
  • D:\BH\Cloud\Web开发\Web_Project\July\web-boot-tlias-parent 父模块代码设计

老师的(上课跟练):

  • D:\BH\Cloud\Web开发\Web_Project\July\temp 老师的代码
  • D:\BH\Cloud\Web开发\Web_Project\July\web-boot02 分模块设计代码
  • D:\BH\Cloud\Web开发\Web_Project\July\boot-tlias-parent 父模块

Maven 基础

统一目录结构

  • src\main\java 写源码
  • src\main\resources 配置文件
  • src\test\java 单元测试
  • sre\test\resources 单元测试配置文件

依赖管理

<dependencies>

  • <dependency>(GAV)

项目构建

默认生命周期: 执行后续命令,前置命令都会执行

  • clean 清理 删除targer目录
  • compile 编译
  • test 测试
  • package 打包
  • install 安装

分模块设计和开发

是什么?

  • 将项目按功能拆分成多个独立的子模块。

为什么? (核心价值)

  • 提升可管理性: 方便项目管理和维护。
  • 增强可扩展性: 易于添加新功能或修改现有功能。
  • 促进模块复用: 模块间可相互调用,实现资源共享。

三种方案

  • 按模块
  • 按层
  • 按照模块+层

关键注意事项:

  • 设计先行:必须先进行模块功能规划和设计,再进行编码。
  • 切忌后拆:不能先开发完整个项目再尝试拆分模块。

继承和聚合

继承

功能: 统一管理依赖,简化子模块配置
核心配置: 父工程中 <packaging>pom</packaging>
打包方式区别:

jar: 普通模块(Spring Boot 默认,内嵌 Tomcat)
war: 传统 Web 工程(需外部 Tomcat)
pom: 父工程/聚合工程(仅管理依赖)

实现步骤

创建父工程

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.2</version>
    <!-- 优先从仓库查找 -->
    <relativePath/> 
</parent>

<groupId>com.itheima</groupId>
<artifactId>boot-tlias-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 父工程唯一标记 -->
<packaging>pom</packaging>

子工程配置继承

<parent>
    <groupId>com.itheima</groupId>
    <artifactId>boot-tlias-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>

父工程管理公共依赖
子模块自动继承父工程 <dependencies> 中的依赖

版本锁定

核心机制: <dependencyManagement> + <properties>

依赖管理区别:

  • <dependencies>: 直接依赖,子工程自动继承
  • <dependencyManagement>: 仅锁定版本,子工程需显式声明依赖

标准操作流程

定义版本属性(父工程)

<properties>
    <lombok.version>1.18.24</lombok.version>
</properties>

锁定依赖版本(父工程)

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

子模块引入依赖(无需版本号)

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

子工程显式指定版本时,以子工程版本为准(不推荐)

聚合

功能: 一键构建多个模块
配置位置: 父工程的 pom.xml

<modules>
    <module>module-1</module>
    <module>module-2</module>
</modules>

继承与聚合对比

对比维度 聚合 继承
方向 父→子 子→父
配置 父工程中定义模块 (<modules>) 子工程中声明父工程 (<parent>)
目的 简化构建过程 统一依赖管理
配置位置 父工程 pom.xml 子工程 pom.xml
感知关系 父工程感知所有子模块 父工程不感知子模块
打包方式 pom pom
实际内容 无实际代码 (设计型模块) 无实际代码 (设计型模块)

私服

私立服务器

1.Maven私服的作用是什么 ?

解决团队内部的资源共享与资源同步问题

2.有了私服之后依赖查找的顺序是什么样的?

本地仓库 --> 私服 --> 中央仓库

纸上谈兵我不看我不写我不练 😋✌

HTML CSS

Web 开发入门

我说概念大于一切 有懂得吗?

HTML CSS JavaScript Vue Ajax

HTML

概述

  • 定义: 超文本标记语言 (HyperText Markup Language)
  • 核心组成: 标签 + 文本 + 属性
  • 注释: <!-- 注释内容 -->
  • 作用: 定义页面结构和内容布局

常用标签

标签 作用 特性
<h1>-<h6> 标题标签 独占一行,字号依次减小
<a> 超链接 href="目标URL"
target="_blank"(新窗口打开)
<span> 行内容器(无语义) 默认同行显示,常配合CSS使用

CSS 笔记

引入方式

方式 特点 写法示例
行内样式 直接写在标签的style属性中 <h1 style="color:red;">文本</h1>
内部样式 <style>标签内定义 <style> h1 { color: blue; } </style>
外部样式 通过外部.css文件引入 <link rel="stylesheet" href="style.css">

选择器

选择器类型 写法示例 特点 优先级
ID选择器 #header 选中唯一元素 (id="header") 最高 (1)
类选择器 .title 可复用 (class="title") 中等 (2)
元素选择器 h1 选中所有同类型标签 最低 (3)

优先级规则: ID > > 元素
选择范围: ID(唯一) < (多元素) ≈ 元素(多元素)

属性

颜色表示形式: 关键字、rgb、rgba、十六进制

参考: CSS 选择器参考手册

仙女降临 👼✨✨

笔记格式要求

重新梳理

请重新梳理我的知识点,去掉重复冗余内容,检查是否存在知识点错误,优化知识结构,谢谢!

个人风格

非常感谢,你的梳理很到位,但是我有自己的笔记风格,请你再加工,渲染成我的笔记风格:
我不喜欢为二级标题设置一二三这种序号,去掉序号,具体意思是,移除前面的“1.2.3.”这个序号,而不是移除“所有二三级标题”
我不喜欢中文的小括号"()",请帮我全部都替换成为英文的小括号"()".
我不喜欢中文的冒号逗号": ," 请帮我全部都替换成为英文的冒号逗号": ,"
我不喜欢---的分割线,也请全部去除。更改成一行空格最好,我喜欢用空格来分隔布局。
英文单词与中文尽量保持一个空格间隙,比如"DML数据操作"写成"DML 数据操作"
标题和加粗不要同时出现,因为标题自带加粗,所以标题和加粗不要同时渲染同一段文字。
加粗少用,用太多反而体现不出重点,满页都是加粗可不行。
二级标题三级标题尽量不要出现":"结尾,看起来不美观
Java代码注释不要采用行尾注释,尽量选择单行注释

废案

如果换行是通过结尾两个空格+换行的话,typora会渲染出软换行标记"↓",这不美观,所以每行结尾请不要添加两个空格.
如下这个场景,就会出现行尾的软换行标记,请你不要出现这种
```markdown
##### 核心注解
- `@RestControllerAdvice`  
  组合注解: `@ControllerAdvice` + `@ResponseBody`  
  作用: 声明全局异常处理器, 自动将方法返回值转为 JSON 格式响应
##### 异常捕获方法
- `@ExceptionHandler`  
  作用: 标记处理特定异常的方法  
  关键属性:  
  `value`: 指定捕获的异常类型(如 `value = Exception.class`), 可省略(默认捕获方法形参类型)
```

在如下的简单场景,完全可以使用方案二而不是方案一的格式排版:
```markdown
# 方案一
- **目标**
  分离 OSS 连接参数 (Endpoint, BucketName, AK/SK), 提升安全性与灵活性
- **适用场景** 
  多属性集中管理, 类型安全
# 方案二
- **目标**: 分离 OSS 连接参数 (Endpoint, BucketName, AK/SK), 提升安全性与灵活性
- **适用场景**: 多属性集中管理, 类型安全
```

作业格式

个人:张三.sql
小组:01.rar
班级:0704.rar

装逼指南 🕶️

SQL 自连接

面试可以这么说:

「我通常用窗口函数解决,但知道一种基于笛卡尔积的分组过滤方案,通过自连接建立员工-部门同事的映射关系,在分组维度上利用聚合函数实现极值过滤...」

错题收录 💥

local 💥 localhost

🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
2025.7.10 踩坑: localhost ≠ local
💥💥💥💥💥💥💥💥💥💥💥💥💥💥💥💥💥💥💥💥💥💥💥

名称 身份 是否有效 MySQL认不认 适用场景
localhost 官方指定本地主机名 ✅ 有效 ✔️ 亲儿子 所有系统通用
local 路人甲字符串 🙄 ❌ 无效 ✖️ 不认识 从来不能用!

多表查询字段映射处理

2025.7.15 多表查询字段映射处理

// 实体类
public class Student {
    private Integer id; //ID
    private String name; //姓名
    private String no; //序号
	...
    private String clazzName;//班级名称
}

Error

<select id="getStudentsByConditions" resultType="Student">
    select s.*, c.name  <!-- 未取别名 -->
    from student s 
    left join clazz c on s.clazz_id = c.id
</select>
  • Student 对象中 clazzName 始终为 null
  • name 属性被班级名称覆盖(学生姓名丢失)

Reason

  • 列名冲突: 两表都有 name 字段 → MyBatis 无法自动区分
  • 缺少映射: 未明确将班级名称映射到 clazzName 属性

Correct

<select id="getStudentsByConditions" resultType="Student">
    select 
        s.*,
        c.name as clazzName  <!-- 关键别名 -->
    from student s 
    left join clazz c on s.clazz_id = c.id
</select>

同名列必取别名
多表联查时,相同列名必须通过 AS 指定唯一别名
别名必须与 Java 实体属性名严格一致

好用Maven依赖

好用依赖收集 Maven

lombok

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
</dependency>

hutool

<dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.28</version>
</dependency>

PageHelper

<!--分页插件PageHelper-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
</dependency>

JWT

<!-- JWT依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<!-- JWT 如果出现异常,需要添加以下依赖 -->
<!-- java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter -->
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

AOP 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

词义明鉴 ✍

收录,专业名词,整合易混

实体 实体类 实现类

  • 实体 (Entity) = 业务对象 (如 Student, Product)
  • 实体类 (Entity Class) = 定义这个业务对象的 Java 代码文件 (Student.java)
  • 实现类 (Implementation Class) = 实现某个接口的具体逻辑的类 (XxxServiceImpl.java)

反射 映射

反射 Reflection: 在程序运行时,动态获取和操作类、方法、字段等结构信息的能力

映射 Mapping

场景
数据结构 HashMap TreeMap 键值对存储
对象转换 DTO ↔ Entity 解耦业务和数据模型
ORM映射 MyBatis 对象和数据库表的自动映射

引经据典 📚

认知涟漪

引用外部文档,认知涟漪

认知涟漪

注解收录

引用外部文档,注解收录

注解收录

posted @ 2025-07-21 21:02  Breezy_space  阅读(45)  评论(0)    收藏  举报