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
目录结构:
![]() |
|---|
- bin目录: 存放的是可执行命令
- conf目录: 存放Maven的配置文件
- lib目录: 存放Maven依赖的jar包
配置仓库
新建本地仓库
Maven安装目录 新建一个本地目录 mvn_repo (本地仓库,用来存放jar包)
![]() |
|---|
配置本地仓库
进入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环境变量
环境变量
-
在系统变量处新建一个变量
变量名:MAVEN_HOME
变量值: 设置为maven的解压安装目录 (解压安装目录即可,不需要进入bin)D:\BH\Local\Develpop_Tools\Maven\apache-maven-3.9.4 -
在
PATH中新建一个:%MAVEN_HOME%\bin -
cmd输入测试代码,验证是否成功
mvn -v
IDEA集成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++实际包含三个步骤:
- 读取
count的当前值- 将值 +1
- 写回新值到内存
若线程 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 有序性
原子性是并发安全的三大支柱之一:
- 原子性:操作不可中断。
- 可见性:一个线程修改共享变量后,其他线程能立即看到新值(通过
volatile或锁实现)。- 有序性:防止指令重排序(通过
volatile或synchronized实现)。
关键注意事项
- 复合操作需显式同步
即使单次读写是原子的(如long在 64 位 JVM 上),类似check-then-act(如if (a < 10) a++)仍需锁或原子类。- 原子类的高效性
在低竞争场景下,AtomicInteger等性能优于锁(基于 CPU 硬件的 CAS 指令)。- 数据库事务的原子性
区别于并发编程,数据库事务的原子性指多个 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 - 等待自动初始化结束即可
![]() |
![]() |
|---|
阿里云初始化器
Server URL需要从官方的修改成阿里云的- 阿里云的结构和版本 和官方会存在差异
- 以后上课都统一都用阿里云的
![]() |
![]() |
![]() |
|---|
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 注入逻辑
- 按类型匹配 Bean
- 找到多个类型时, 将变量名作为 Bean 名称尝试匹配
- 匹配失败则报错 NoUniqueBeanDefinitionException
- 出现第三步错的时候,就需要配合 @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 详解有详细说明
-
数据库
-
创建工程[导入依赖]
-
配置文件(yml)
-
创建实体(满足ORM)
-
先写Mapper接口,再写Mapper映射文件
-
测试[集成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/
工程搭建
-
创建SpringBoot工程,并引入web开发起步依赖、mybatis、mysql驱动、lombok。
-
创建数据库表,并在application.yml中配置数据库的基本信息。
-
准备基础代码结构,并引入实体类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;
}
}
一个健康的目录结构
![]() |
|---|
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分层协作全景图
三层处理
| 分层 | 技术 |
|---|---|
| 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:定义请求路径/deptsmethod:指定请求类型RequestMethod.GET
统一请求前缀:
- 在类上添加
@RequestMapping("/depts")作为所有方法的前缀 - 不同的请求方式对应的新注解 @GetMapping, @DeleteMapping, @PostMapping, @PutMapping
- 方法上仍需单独定义路径(如
@PutMapping("/{id}"))
@RestController
@RequestMapping("/depts")
public class DeptController {
@PutMapping("/{id}")
public Result updateDept(...) { ... }
}
请求参数处理
参数解析链
参数解析链: HandlerMethodArgumentResolvers
简单参数处理
基本方案三
方案一:通过原始的 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 集合
-
要求
- 集合形参前必须添加 @RequestParam 注解
- 前端参数名必须与 @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 学生插入
- 数据库表结构
CREATE TABLE student ( id INT PRIMARY KEY, name VARCHAR(50), clazz_id INT, -- 数据库使用下划线命名 graduation_date DATE );
- 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_id→clazzIdgraduation_date→graduationDate- 不需要在 SQL 中写别名
情况2:插入操作(不受影响 ❌)
<insert id="insertStudent" parameterType="Student"> INSERT INTO student (name, clazz_id, graduation_date) VALUES (#{name}, #{clazz_id}, #{graduation_date}) <!-- 错误写法! --> </insert>会发生什么:
- MyBatis 尝试调用
student.getClazz_id()方法 → 不存在- 尝试调用
student.getGraduation_date()→ 不存在- 最终抛出异常:
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
解决以下场景的映射问题:
- 字段名与属性名不一致(如
entry_date→entryDate) - 存在嵌套对象/集合(如员工包含工作经历列表)
- 需要特殊处理数据类型转换
- 多表关联查询(自动映射无法处理)
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 主表名(主键字段名); -
缺点
- 影响增、删、改效率(数据库需检查外键约束).
- 不利于分布式、集群和分库分表场景.
- 可能引发数据库死锁, 消耗性能.
-
行业现状: 在实际企业开发中 较少使用
逻辑外键
- 概念: 不在数据库层面创建物理外键约束, 仅在业务逻辑代码层面维护表之间的关联关系.
- 优点: 避免了物理外键的性能和灵活性缺点, 是目前主流的实现方式.
一对一
场景举例
- 用户 与 身份证信息
- 一夫 与 一妻 (概念性举例)
实现方式
- 在 任意一方 添加一个 逻辑外键 字段, 关联另一方的主键.
- 对该外键字段添加 唯一约束 (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参数名一致(如
begin、end),这样Spring才能自动绑定参数。 - 实体类的字段名是独立的,通常与数据库表字段映射(如
beginDate、endDate)。
// 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片段
NaviCat
NaviCat 快捷键
| 麻辣快捷键 | |
|---|---|
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
countgroup 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注解 --> 来实现事务管理。
默认情况下,只有出现RuntimeException和Error才回滚异常。
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 核心价值
- 减少重复代码(如日志、权限等通用逻辑)
- 代码无侵入(业务逻辑与增强逻辑解耦)
- 提高开发效率(聚焦核心业务开发)
- 维护方便(横切关注点集中管理)
快速入门
SpringAOP开发步骤
// 表示当前类是一个切面类
@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())
实现步骤
- 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyLog {}
- 标记方法
public class UserService {
@MyLog
public void delete() { ... }
}
- 配置切面
@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 切面的核心操作对象
- 封装了被拦截方法的所有上下文信息
- 相当于方法的 "快照"
方法调用链关系
实际应用场景
- 类名 + 方法名 → 确定具体操作(例如
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
配置优先级(高->低)
- 命令行参数
- Java系统属性
- application.properties
- application.yml
- 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的封装。 - 流程
- 定义注解(如
@EnableCaching)并标注@Import({CacheConfig.class, CacheSelector.class})。 - 用户使用
@EnableXxxxx激活功能。 - Spring 执行
@Import逻辑:- 加载配置类中的
@Bean; - 或执行
ImportSelector.selectImports()动态加载配置。
- 加载配置类中的
- 定义注解(如
- 优势
- 简化配置(一键开启功能);
- 支持
@ConditionalOnXxx条件化装配(如@ConditionalOnClass); - 第三方 Starter 依赖提供此注解。
源码跟踪
核心架构:一个核心注解 + 一个启动方法
@SpringBootApplication
作用: 标注在主启动类上
本质: 复合注解,包含以下三个关键注解
-
@SpringBootConfiguration-
底层是
@Configuration -
表明启动类本身也是一个配置类,可以在其内部定义相关的
@Bean
-
-
@ComponentScan-
表示组件扫描
-
默认会扫描启动类包以及子包下Spring能识别的组件
-
-
@EnableAutoConfiguration-
启用 Spring Boot 自动配置
-
底层依赖于
@Import(AutoConfigurationImportSelector.class)
-
@EnableAutoConfiguration 工作流程
- 底层是一个
@Import注解,该注解中导入了AutoConfigurationImportSelector.class - 调用
AutoConfigurationImportSelector.selectImports() - 内部调用
getAutoConfigurationEntry() - 通过
getCandidateConfigurations()加载自动配置类META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
- 过滤候选配置类
- 依据条件注解如
@ConditionalOnClass - 检查项目依赖和排除项
- 依据条件注解如
- 最终,将满足条件的自动配置类的全类名封装成一个字符串数组。
SpringApplication.run()
作用: 启动应用,创建并初始化 Spring 容器 ApplicationContext。
执行流程
- 初始化 Spring 容器时,处理
@EnableAutoConfiguration导入的自动配置类 - 将筛选后的自动配置类加载到内存
- 解析自动配置类中的定义(
@Bean,@Conditional等) - 创建符合条件的 Bean 并注册到 IoC 容器
完整流程总结
- 执行
SpringApplication.run()启动应用 - 解析启动类上的
@SpringBootApplication @EnableAutoConfiguration触发AutoConfigurationImportSelector- 加载并筛选
AutoConfiguration.imports中的自动配置类 - 导入筛选后的自动配置类
- Spring 容器初始化时处理这些类
- 执行自动配置类逻辑,创建并注册符合条件的 Bean
- 应用启动完成,可直接使用自动配置的 Bean(如 DataSource, MVC 组件等)
郭の版本(可供参考)
1、一个注解
启动类上 SpringBootApplication 注解,它是一个复合注解,内部有三个注解组成
- SpringBootConfiguration,底层是一个Configuration,表示启动类也是一个配置类,可以在内部定义相关Bean
- ComponentScan,表示组件扫描,默认会扫描启动类包及子包下Spring能识别的组件
- 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
- 定义一个
xxx-boot-starter负责管理依赖(导入下面的xxx-boot-autoconfigure依赖) - 定义一个
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始终为nullname属性被班级名称覆盖(学生姓名丢失)
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 | 对象和数据库表的自动映射 |
引经据典 📚
认知涟漪
引用外部文档,
认知涟漪
注解收录
引用外部文档,
注解收录











浙公网安备 33010602011771号