前端学 Java 课程笔记
本笔记面向前端开发人员,以前端概念类比 Java,速成实战导向。
不涉及深度 JVM 优化、数据库优化、分布式等进阶内容。
文章目录
- [第 0 章 介绍](#第 0 章 介绍)
- [第 1 章 Java 基础](#第 1 章 Java 基础)
-
- [1-1 安装和准备](#1-1 安装和准备)
- [1-2 Java 的基本写法](#1-2 Java 的基本写法)
- [1-3 运行 Java 程序](#1-3 运行 Java 程序)
- [1-4 包的概念和使用](#1-4 包的概念和使用)
- [1-5 修饰符](#1-5 修饰符)
- [1-6 Java 中的基本类型](#1-6 Java 中的基本类型)
-
- [1-7 类和对象](#1-7 类和对象)
-
- [Object 是所有类的源头](#Object 是所有类的源头)
- 引用类型的特性
- [1-8 字符串操作](#1-8 字符串操作)
- [1-9 Map 和枚举](#1-9 Map 和枚举)
- [1-10 数组和 List、Set](#1-10 数组和 List、Set)
-
- 标准数组
- 集合家族(Collection)
- [List 操作](#List 操作)
- Set
- [1-11 null 以及 null 的处理](#1-11 null 以及 null 的处理)
-
- [null 的概念](#null 的概念)
- [Optional(Java 8 引入)](#Optional(Java 8 引入))
- [1-12 构造函数和继承](#1-12 构造函数和继承)
- [1-13 重载和重写](#1-13 重载和重写)
-
- 重载(Overload)
- 重写(Override)
- [@Override 注解](#@Override 注解)
- [重载 vs 重写的判断](#重载 vs 重写的判断)
- [1-14 抽象类和接口的作用](#1-14 抽象类和接口的作用)
-
- [抽象类(abstract class)](#抽象类(abstract class))
- 接口(interface)
- [抽象类 vs 接口](#抽象类 vs 接口)
- [1-15 错误类型与处理](#1-15 错误类型与处理)
- [第 2 章 IO 与网络](#第 2 章 IO 与网络)
-
- [2-1 流的概念与文件读写](#2-1 流的概念与文件读写)
- [2-2 专门的文件读写类](#2-2 专门的文件读写类)
-
- [NIO 包(Java 7+,内置)](#NIO 包(Java 7+,内置))
- [IO.File 类(老版本)](#IO.File 类(老版本))
- 第三方包
- [2-3 Java 开启 HTTP 服务](#2-3 Java 开启 HTTP 服务)
- [2-4 根据 id 模糊搜索(实战)](#2-4 根据 id 模糊搜索(实战))
- [2-5 使用第三方包处理 JSON](#2-5 使用第三方包处理 JSON)
- [第 3 章 工程化与数据库](#第 3 章 工程化与数据库)
-
- [3-1 Maven 的概念与安装](#3-1 Maven 的概念与安装)
- [3-2 Maven 管理模块和构建](#3-2 Maven 管理模块和构建)
- [3-3 用 Servlet 与 Tomcat 开启服务](#3-3 用 Servlet 与 Tomcat 开启服务)
- [3-4 MySQL 安装](#3-4 MySQL 安装)
- [3-5 数据库模型](#3-5 数据库模型)
-
- 基本结构
- [表设计示例(OA 系统)](#表设计示例(OA 系统))
- [3-6 建立表和库](#3-6 建立表和库)
- [3-7 SQL 语句增删改查](#3-7 SQL 语句增删改查)
- [3-8 字段属性和约束](#3-8 字段属性和约束)
- [3-9 索引](#3-9 索引)
- [3-10 条件与连表查询](#3-10 条件与连表查询)
- [3-11 数据库函数](#3-11 数据库函数)
- [3-12 分组、排序、分页](#3-12 分组、排序、分页)
-
- [分组(GROUP BY)](#分组(GROUP BY))
- [排序(ORDER BY)](#排序(ORDER BY))
- [分页(LIMIT + OFFSET)](#分页(LIMIT + OFFSET))
- [3-13 数据库可视化软件](#3-13 数据库可视化软件)
- [第 4 章 Spring Boot 实战](#第 4 章 Spring Boot 实战)
-
- [4-1 Spring Boot 创建项目](#4-1 Spring Boot 创建项目)
- [4-2 定义第一个接口](#4-2 定义第一个接口)
-
- 核心注解
- [注解在 Spring Boot 中的意义](#注解在 Spring Boot 中的意义)
- [4-3 请求参数处理](#4-3 请求参数处理)
- [4-4 项目层级规范](#4-4 项目层级规范)
-
- 各层作用
- [Model 层细分](#Model 层细分)
- [4-5 设计一个接口的分层](#4-5 设计一个接口的分层)
-
- [目标:根据 id 查询用户信息](#目标:根据 id 查询用户信息)
- [Lombok 库](#Lombok 库)
- [4-6 Spring 依赖注入体系(IOC)](#4-6 Spring 依赖注入体系(IOC))
-
- IOC(控制反转)思想
- [加入容器的注解(告诉 Spring 管理这个类)](#加入容器的注解(告诉 Spring 管理这个类))
- 从容器取出的注解
- [@SpringBootApplication 拆解](#@SpringBootApplication 拆解)
- [4-7 操作数据库(上)](#4-7 操作数据库(上))
-
- 第一步:连接数据库
- 主流数据库操作工具对比
- [JPA 使用方式](#JPA 使用方式)
- [4-8 操作数据库(下)](#4-8 操作数据库(下))
-
- [MyBatis 使用](#MyBatis 使用)
- [MyBatis-Plus 使用](#MyBatis-Plus 使用)
- [4-9 登录与注册接口](#4-9 登录与注册接口)
- [4-10 修改密码和注销](#4-10 修改密码和注销)
- [4-11 用 MyBatis-Plus 简化增删改查](#4-11 用 MyBatis-Plus 简化增删改查)
-
- [第一步:定义 Entity](#第一步:定义 Entity)
- [第二步:定义 Mapper](#第二步:定义 Mapper)
- 常用方法
- [QueryWrapper 条件构造](#QueryWrapper 条件构造)
- [4-12 拦截器、过滤器和全局配置](#4-12 拦截器、过滤器和全局配置)
-
- 执行顺序
- 过滤器(Filter)
- 拦截器(Interceptor)
- [过滤器 vs 拦截器](#过滤器 vs 拦截器)
- [MVC 全局配置(WebMvcConfigurer)](#MVC 全局配置(WebMvcConfigurer))
- [4-13 CORS 跨域设置](#4-13 CORS 跨域设置)
-
- 跨域解决方案
- [CORS 本质](#CORS 本质)
- [4-14 Token 体系(JWT)](#4-14 Token 体系(JWT))
-
- [Token 流程](#Token 流程)
- [JWT 组成](#JWT 组成)
- 使用的库(jjwt)
- [生成 Token](#生成 Token)
- [验证 Token(拦截器中)](#验证 Token(拦截器中))
- [4-15 利用 Session 做验证码](#4-15 利用 Session 做验证码)
-
- [什么是 Session](#什么是 Session)
- [Spring Boot 中使用 Session](#Spring Boot 中使用 Session)
- [图形验证码(Kaptcha 库)](#图形验证码(Kaptcha 库))
- [Base64 操作](#Base64 操作)
- 相关类
- [4-16 用户列表查询(分页)](#4-16 用户列表查询(分页))
- [4-17 页面模块](#4-17 页面模块)
- [4-18 查询用户权限](#4-18 查询用户权限)
- [4-19 ~ 4-22 综合实战模块](#4-19 ~ 4-22 综合实战模块)
-
- [4-19 订单模块](#4-19 订单模块)
- [4-20 用户余额](#4-20 用户余额)
- [4-21 定时任务](#4-21 定时任务)
- [4-22 统计查询](#4-22 统计查询)
- [附录:前端开发者学 Java 的关键映射](#附录:前端开发者学 Java 的关键映射)
第 0 章 介绍
特点
- 面向前端开发人员:大量以前端概念类比 Java,复杂代码先写 JS 再写 Java
内容
从 0 基础 → Java 语法 → Java 生态 → Spring Boot + MyBatis 实战开发。
第 1 章 Java 基础
1-1 安装和准备
安装步骤
- 下载 JDK:Oracle 官网下载 Java 8,Windows 下载 exe 包,傻瓜式安装
- 配置环境变量:Java 安装后不会自动注册为系统命令,需手动配置
环境变量本质
- 命令行输入一个命令,本质是在环境变量 Path 中查找该命令对应的可执行程序
- 配置两步:① 新建一个环境变量(如
JAVA_HOME)指向 JDK 安装目录;② 将%JAVA_HOME%\bin添加到 Path 中
1-2 Java 的基本写法
核心原则
Java 中一切代码都基于类,方法、变量都不能在类外定义。
类的基本结构
java
// 类
// 修饰符public + 类关键字class + 自定义类名Run
public class Run {
// 方法
// 修饰符public + 修饰符static + 方法返回类型void + 方法名main + 参数类型String[] + 方法接受的参数args
public static void main(String[] args) {
// 变量:修饰符(可省略) + 类型 + 名字
final String str = "aaa";
// 属性:修饰符 + 类型 + 名字
private String sort = "bbb";
}
}
变量、属性的基本结构
-
局部变量(方法内的变量 / 常量)
java//修饰符 变量类型 变量名 = 值 final String str = "aaa"; /* * ✅**局部变量**:写在方法、代码块里面 * 修饰符:**只能写 `final`**,不能用 `private/public/protected` * 加`final`后就变成**常量**,值不能修改 * 局部变量**没有默认值**,使用前必须手动赋值 * * 变量一般不写修饰符,要写也只能写`final` */ -
属性(成员变量,写在类里面、方法外面)
java// 修饰符 变量类型 属性名 = 值; private String sort = "bbb"; /* * ✅**属性 / 成员变量**:属于类,写在类中,方法之外 * 修饰符可以多种:`private、public、protected、static、final`可以组合使用 * 加`final`后就变成**常量**,值不能修改 * 有默认初始值,不手动赋值也有默认值(String 默认 null) * * 属性修饰符可以很多种 */ ``` -
核心区别总结
局部变量(方法内) 属性(成员变量,类中) 位置方法 循环 代码块内部类里面,方法外 可用修饰符 仅 finalprivate/public/protected/static/final等默认值 ❌无,必须赋值才能用 ✅有默认值 生命周期 方法执行完就销毁 对象存在就存在 javapublic class Demo { // 👉这是【属性】,可以用private修饰 private String sort = "bbb"; public void test(){ // 👉这是【局部变量】,只能加final,不能写private final String str = "aaa"; } }
代码对比

-
Java & JS 对照完整 Demo
🔹Java Demo(文件名叫 Run.java)
规则:public 类名 = 文件名;方法必须写在类内部;强类型,参数个数类型严格校验;局部变量具备方法 + 块级作用域
java// Run.java 文件名必须是 Run public class Run { // 类属性(成员变量,方法外面) public String attr = "我是类属性"; // Java方法,属于类 public int calc(int b1, int b2) { // 方法局部变量:仅本方法内可访问 int b = 123; b -= 1; if (true) { // 块级局部变量:仅if大括号内部有效 int blockVal = 888; System.out.println("if块内:" + blockVal); } // System.out.println(blockVal); // ❌编译报错,出大括号不能访问 return b + b1 + b2; } public static void main(String[] args) { Run run = new Run(); int res = run.calc(10, 20); // ✅必须传2个int参数 System.out.println("Java方法返回结果:" + res); // run.calc(); // ❌编译报错!必须传入2个参数,Java强校验 } } // 同一个文件可以放普通class,不能加public class OtherClass { } /* *运行输出: * *if块内:888 *Java方法返回结果:152 */🔹JavaScript Demo(run.js)
无文件绑定规则;函数可以独立存在;弱类型,传参随意;
let/const块级作用域,var只有函数作用域javascript// JS独立函数,不需要依附类 function calc(b1, b2) { // 函数局部变量,仅函数内部 let b = 123; b -= 1; if (true) { // let块级作用域,只在if{}内 let blockVal = 888; console.log("if块内:", blockVal); } // console.log(blockVal); // ❌运行报错,出大括号访问不到 return b + b1 + b2; } // ✅正常传参调用 let result1 = calc(10, 20); console.log("JS正常传参返回:", result1); // ✅JS允许不传参数,不会报错,b1、b2变成 undefined,相加得到NaN let result2 = calc(); console.log("JS不传参返回:", result2); /* *运行输出: * *if块内: 888 *JS正常传参返回: 152 *if块内: 888 *JS不传参返回: NaN */
关键规则
- 一个
.java文件中,与文件同名的public类只能有一个,可以有其他非 public 类 - 写方法/变量/属性的基本模板:修饰符 → 类型 → 名字
- Java 是强类型语言,定义任何东西都要说明类型
- 修饰符通常在最开头,一般只能有一个;方法和属性可以有两个不同功能的修饰符(修饰符可以不写,当不写的时候等同于写了一个默认的修饰符)
- 类是java 的基本构成单元 一切逻辑,方法,变量都要先写一个类
1-3 运行 Java 程序
程序入口
Java 程序从 public static void main(String[] args) 方法开始执行,程序中必须有一个类包含 main 方法。
编译运行流程
源代码:xx.java
↓ 【有中文时 javac -encoding UTF-8 xx.java】
javac xx.java 【编译命令,要写 .java后缀】
↓
字节码文件:xx.class 编译产出
↓
java xx【运行命令,只写类名,❌禁止写 .class】
↓
程序执行
.java文件不能直接运行,需先用javac编译为.class字节码- 有中文时需用
-encoding指定编码 - 实际开发中借助 IDE(如 IDEA、VSCode)一键运行

1-4 包的概念和使用
什么是包
包(Package)可类比前端的模块,一个文件夹就是一个包,包名就是文件夹路径。
包的基本原则
- 同包内的类可以互相直接使用(除非修饰符限制了访问范围)
- 不同包之间需要引入 :用
package定义包,用import引入包
两个核心操作
java
// 1. 定义包(文件第一行),包名与文件夹路径一致
package com.study.demo;
// 2. 引入包
import com.study.other.Run; // 引入单个类
import com.study.other.*; // 批量引入
拓展点
- 同包可以直接用,但子包不能用父包的类
- 一个 java 文件可以有多个类,但只有一个 public 类,外部只能访问到 public 类,同包内可访问第二个类
1-5 修饰符
访问修饰符(控制访问范围,只能存在一个)
| 修饰符 | 访问范围 |
|---|---|
public |
任何其他类都可访问 |
protected |
子类和同包可访问 |
| 默认(不写) | 同包可访问 |
private |
只有类自己可访问 |
范围从大到小:public > protected > 默认 > private
非访问修饰符(可与访问修饰符叠加)
| 修饰符 | 作用 |
|---|---|
static |
静态属性/方法,只能用类名访问 |
final |
变量不能重新赋值;类不能被继承;方法不能被重写 |
abstract |
定义抽象类、抽象方法 |
synchronized |
同时只能被一个线程访问 |
transient |
标记不被序列化的字段 |
volatile |
多线程并发访问时确保可见性 |
1-6 Java 中的基本类型
类型分类
- 基本类型 :四类八种(整型、浮点、布尔、
字符) - 引用类型 :
字符串、对象、数组、枚举等
四类八种基本类型
Java 8 种基本数据类型,每一种都有对应的包装类,包装类内置静态常量:
MAX_VALUE:该类型最大值MIN_VALUE:该类型最小值
-
整型(存储整数)
类型 字节 取值范围 byte1 -128 ~ 127 short2 -32768 ~ 32767 int4 -21亿 ~ 21亿(默认数字类型) long8 -2^63 ~ 2^63-1 注意:定义 long 类型字面量,数字末尾必须加 L /l ,推荐大写
Ljavapublic class IntDemo { public static void main(String[] args) { // byte 1字节 byte b1 = 10; // byte b2 = 130; // 报错,超出byte范围 -128~127 // short 2字节 short s1 = 200; // int 4字节,整数默认类型 int i1 = 10000; // long 8字节,数字后面加L // long num = 8888888888; // ❌报错,数字默认int,超出int范围 long l1 = 9999999999L; // 1. Java 写整数字面量,**默认就是 int 类型** // 2. long 类型赋值大数,末尾不加`L`会编译报错 System.out.println("byte值:" + b1); System.out.println("short值:" + s1); System.out.println("int值:" + i1); System.out.println("long值:" + l1); // byte值:10 // short值:200 // int值:10000 // long值:9999999999 // 变量溢出:超过类型最大值,不会报错,但数值会错乱。 int maxInt = 2147483647; System.out.println(maxInt + 1); // 溢出,输出负数,输出:`-2147483648` } } -
浮点(带小数点)
类型 字节 精度 float4 6-7 位小数 double8 15-16 位小数(默认小数类型) ⚠️重点规则
- 直接写小数字面量,默认是 double 类型
- 给
float赋值,数字末尾必须加F/f,推荐大写F - 浮点数存在精度丢失问题,不要直接用
==比较两个浮点数
javapublic class FloatDemo { public static void main(String[] args) { // float 末尾加F float f1 = 3.14F; // float f2 = 3.14; //❌报错,3.14默认double,不能直接赋值给float // double,小数默认类型,可以不加后缀 double d1 = 3.1415926; double d2 = 3.1415926D; //也可以加D/d,可省略 System.out.println(f1); // 3.14 System.out.println(d1); // 3.1415926 } }包装类常量(可复制)
java//float System.out.println(Float.MAX_VALUE); //float最大正数 System.out.println(Float.MIN_VALUE); //float最小的正小数(不是负数) //double System.out.println(Double.MAX_VALUE); //double最大正数 System.out.println(Double.MIN_VALUE); //double最小的正小数精度丢失示例(高频坑)
javadouble a = 0.1 + 0.2; System.out.println(a); //输出 0.30000000000000004,不等于0.3 // ❌不能写 if(0.1+0.2 == 0.3)补充:
MIN_VALUE对 float/double,代表最小正数 ,不是最小负数;最小负数写-Float.MAX_VALUE、-Double.MAX_VALUE。 -
其他
boolean:只能true/falsechar:单个字符 ,字符一定是单引号' ',字符串一定是双引号" "javapublic class CharBoolDemo { public static void main(String[] args) { // boolean boolean flag1 = true; boolean flag2 = false; // char 单引号,只能放1个字符 char c1 = 'A'; char c2 = '中'; char c3 = '9'; char c4 = '\n'; //转义换行符 // char可以直接赋值数字,对应Unicode码 char c5 = 65; System.out.println(c5); //输出 A // String 字符串,双引号,引用类型,不是基本类型 String str = "ABC中文"; System.out.println(flag1); System.out.println(c1); System.out.println(c2); System.out.println(str); // true // A // 中 // ABC中文 } }注意
char c = 'AB';❌报错,单引号只能一个字符char c = "A";❌报错,字符必须单引号;双引号是 String 字符串- Java 中
boolean不能用 1、0 代替 ,不能写boolean b = 1;
为什么不都用最大的类型?
内存有限,能用小类型就用小类型,节省内存。
类型转换
- 自动转换 :小转大(如 byte → int),无风险自动完成
- byte、short 相加结果自动转为 int
- char 赋值给整型自动转为 ASCII 码
- 顺序:
byte → short → int → long → float → double
- 强制转换 :大转小有风险,需手动强转
(int) someDouble - 根本不兼容的类型无法转换(如对象转数字、布尔转其他)
java
public class ConvertDemo {
public static void main(String[] args) {
// ==========自动转换 小转大==========
byte b = 10;
int i = b; // byte自动转int ✅
char ch = 'A';
int num = ch; // char自动转数字,A对应65
System.out.println(num); //65
byte b1=1,b2=2;
int res = b1 + b2; // byte相加,结果自动提升为int
// ==========强制转换 大转小==========
double d = 3.99;
int a = (int) d; //强制截取整数部分,小数直接丢掉
System.out.println(a); //输出3
int big = 130;
byte bb = (byte) big;
System.out.println(bb); //超出byte范围,溢出,输出‑126
long lo = 1000L;
short sh = (short) lo;
// ==========不允许转换==========
// boolean bl = true;
// int x = (int)bl; // ❌ boolean不允许和数字转换
// `byte`、`short`、`char`互相运算,**先提升为 int 再计算**,结果一定 int
short s1 = 1;
short s2 = 2;
//short s3 = s1 + s2; //❌ s1+s2结果是int,不能直接赋值short
short s3 = (short)(s1 + s2); //✅需要强制转换
int m = (int)3.5 + 4.8; // 只把3.5转int变成3,再加4.8,结果是double 7.8
int n = (int)(3.5 + 4.8); //先求和,再整体强转,结果8
}
}
1-7 类和对象
Object 是所有类的源头
类比 JS 原型链的源头是 Object,Java 中所有类(包括 null)都是 Object 的子类。因此:
- 所有类型都可以用 Object 概括
- 所有类都有 Object 的方法,如
toString()、getClass()、equals()等
引用类型的特性
引用类型变量存储的是对象的内存地址引用,和 JS 引用类型同理:
-
两个内容相同的对象用
==判断不相等(比的是地址),判断内容相等用equals() -
一个对象赋值给两个变量,修改一个另一个也会变(指向同一地址)
new User() → 内存中创建对象(假设地址 1002)
User a = new User() → a 存的是地址 1002
User b = a → b 存的也是地址 1002
1-8 字符串操作
Java 中字符串是引用类型(对象),不能按 JS 的逻辑处理。
创建
java
String str1 = "abc";
String str2 = new String("asdasd");
常见操作
| 操作 | 方法 |
|---|---|
| 判断相等 | equals()(不能用 ==) |
| 空字符串判断 | isEmpty() 或 equals("") |
| 替换 | replace() / replaceAll() |
| 正则匹配 | matches() |
| 分割为数组 | split() |
| 查找位置 | indexOf() |
| 拼接 | +、concat()、StringBuilder、StringBuffer、String.join() |
| 格式化 | String.format()(类似模板引擎占位替换) |
大量字符串拼接推荐用
StringBuilder,性能优于+。
1-9 Map 和枚举
Map(键值对)
类比前端用对象做键值对映射。
java
// 前端
let mapObj = {};
mapObj["a"] = 123;
// Java
Map<String, Number> mapObj = new HashMap<>();
mapObj.put("a", 123);
mapObj.put("b", 456);
常见操作 :put(增/改)、remove(删)、get(查)、clear(清空)、keySet(所有 key)、values(所有值)、entrySet(键值对对象)、containsKey(是否存在 key)、forEach(遍历)
Map 的种类
HashMap:无序(最常用)LinkedHashMap:按 put 顺序排序TreeMap:按 key 升序
枚举(enum)
用于定义一组状态常量,如订单状态。
java
public enum OrderStatus {
UNFINISH("未完成", "UNFINISH"),
FINISH("完成", "FINISH"),
FAIL("失败", "FAIL");
public String name;
public String code;
OrderStatus(String name, String code) {
this.name = name;
this.code = code;
}
}
// 使用
System.out.println(OrderStatus.FAIL.name);
枚举可自由拓展方法(如转 JSON、转 Map),属性可设为私有提升安全性。
1-10 数组和 List、Set
标准数组
- 内存中连续存储 ,元素同类型、同大小、长度固定
- 前端的数组并不是真正的数组(长度可变)
- 业务开发中很少用标准数组,太固定
java
int[] arr1 = new int[4]; // 指定长度
int[] arr2 = new int[]{1, 2, 3}; // 指定元素
int[] arr3 = {1, 2, 3}; // 简写
- 取值/改值:下标操作
arr[0] - 不允许增删(长度固定)
- 遍历:普通 for、增强 for(
for :)、转 stream 后 forEach
集合家族(Collection)
因为数组太固定,业务中用集合家族,类比前端的 JS 数组。
Collection(抽象接口,不可 new)
├── List(有序可重复,类似 JS 数组)
│ ├── ArrayList(数组实现,最常用)
│ └── LinkedList(链表实现)
├── Set(元素唯一)
│ ├── HashSet
│ ├── LinkedHashSet
│ └── TreeSet
└── Queue(队列,先进先出)
橙色 = 抽象接口不可 new;蓝色 = 具体实现类可 new。
List 操作
- 创建:
new ArrayList<>() - 增删改查:
add、remove、clear、get、set - 遍历:for、增强 for、forEach、迭代器
- filter/map/sort:转 stream 后操作,转回 List 用
collect
ArrayList vs LinkedList:操作 API 一样,差异在内部存储(数组 vs 链表),大多数情况 ArrayList 更优。
Set
操作和 List 基本一致,区别是元素不能重复。
1-11 null 以及 null 的处理
null 的概念
null表示"无对象"或"空引用",类似前端的 null- null 是唯一的值,只有
null == null为 true - null 本身没有类型,但可以赋值给任何引用类型
- 常见出现场景:引用类型数组默认值、数据库查询无结果、对象初始无值
Optional(Java 8 引入)
当方法可能返回 null 时,用 Optional 包装,强制调用者处理空值。
| 方法 | 作用 |
|---|---|
Optional.of() |
包装非空值(为 null 会报错) |
Optional.ofNullable() |
包装可能为 null 的值 |
Optional.empty() |
创建空 Optional |
isPresent() |
判断是否有值 |
get() |
获取值(空则报错) |
orElse(default) |
空时返回默认值 |
ifPresent(consumer) |
有值时执行操作 |
isEmpty() |
Java 11+,判断是否为空 |
ifPresentOrElse() |
Java 9+,有值执行第一个,空执行第二个 |
核心意义:如果方法可能返回 null,用 Optional 作为返回类型,强制使用者记得判空。
1-12 构造函数和继承
构造函数
- 类比 JS 的构造函数,在
new类时执行 - Java 中构造函数一定存在,不写则有默认空构造函数
写法要点:
- 名字必须和类名相同
- 没有返回类型
- 可以有多个(重载),new 时根据参数匹配
- 修饰符可用访问修饰符,
private会导致外部无法直接 new
java
public class Dog {
public Dog(String name) { // 构造函数
// 初始化逻辑
}
}
继承(extends)
java
public class Keji extends Dog {
// 子类继承父类
}
注意点:
- 父类的私有属性被继承但子类无法直接访问
- 父类如果显式定义了有参构造函数,子类必须定义构造函数并调用
super()
1-13 重载和重写
重载(Overload)
同一个类中多个同名方法 ,但参数不同(个数/类型/顺序),调用时根据参数匹配执行哪个。这就是 Java 的多态。
java
public int add(int a, int b) { return a + b; }
public int add(int a, int b, int c) { return a + b + c; } // 重载
重写(Override)
子类定义一个与父类方法名、参数完全相同的方法,覆盖父类实现。
java
// 父类
protected int doCalc(int a, int b) { return a + b; }
// 子类
@Override
protected int doCalc(int a, int b) { return a - b; } // 重写
重写规则:
- 方法名、参数列表必须相同
- 返回类型相同或兼容
- 访问权限不能更严格(如不能从 public 变 protected)
@Override 注解
加上 @Override 明确告诉编译器这是重写,编译器会按重写规则校验,防止误写成重载。功能上无意义,是辅助说明。
重载 vs 重写的判断
- 方法名+参数完全相同 → 重写
- 方法名相同但参数不同 → 重载(即使在子类中)
1-14 抽象类和接口的作用
抽象类(abstract class)
- 自身不能被实例化,作用是给子类继承
- 可以定义有实际功能的属性、构造函数和方法
- 抽象方法(
abstract)在子类中必须实现------这是抽象类最大的意义
java
public abstract class Animal {
public abstract void speak(); // 抽象方法,子类必须实现
public void breathe() { // 普通方法,有实现
System.out.println("呼吸");
}
}
接口(interface)
- 一个类可以实现多个接口
- 接口里不能定义普通属性,只能定义静态常量
- 接口里所有方法默认都是
public abstract
java
public interface Pay {
void pay(double amount); // 默认为 public abstract
}
抽象类 vs 接口
| 对比项 | 抽象类 | 接口 |
|---|---|---|
| 多继承 | 不可以(单继承) | 可以(多实现) |
| 可写内容 | 实际方法、属性 | 抽象方法、静态常量 |
| 抽象层次 | 抽象某一个种类 | 抽象一种行为 |
| 使用场景 | 规定某一种类的基本模板 | 定义一种行为能力,不固定归属某个类 |
记忆:抽象类是"是什么",接口是"能做什么"。
1-15 错误类型与处理
异常体系
Throwable
├── Error(严重错误,必中断,无法恢复)
│ ├── OutOfMemoryError(内存不足)
│ └── StackOverflowError(栈溢出/无限递归)
└── Exception
├── 受检异常(必须 try-catch 或 throws 声明)
│ ├── IOException(IO 流异常)
│ └── SQLException(数据库操作失败)
└── 非受检异常(RuntimeException,可不处理,但不处理会中断线程)
├── NullPointerException(空指针)
├── ArrayIndexOutOfBoundsException(数组越界)
└── ArithmeticException(算术错误)
try-catch-finally
java
try {
// 可能异常的代码
} catch (IOException e) {
// 捕获 IO 异常,写日志,不要直接抛出
} catch (SQLException e) {
// 多个 catch,按类型匹配
} finally {
// 无论是否异常都执行
}
要点:
- catch 可以有多个,按错误类型匹配
- catch 中可以继续抛出,但建议写入日志而非直接抛出
- 方法如果抛出受检异常,必须用
throws声明
自定义异常
继承 Exception(受检)或 RuntimeException(非受检),写构造函数即可。
java
public class MyException extends RuntimeException {
public MyException(String msg) {
super(msg);
}
}
总结
- Error 必中断;其他异常被 try-catch 捕获且 catch 中不再抛出就不会中断
- 受检异常必须处理(try-catch 或 throws);非受检异常可选择不处理
- 业务中常用自定义异常统一处理业务错误
第 2 章 IO 与网络
2-1 流的概念与文件读写
什么是流
Java 与外部系统(磁盘、网络)进行数据交互,基本都以流的形式。比如读文件、接收前端请求、返回响应数据。
为什么要用流
如果一个文件 2G,内存只有 1G,一次性读入会内存溢出。流的思路是:一点点读取,处理完再读下一部分,类比用管子把水从一个桶导到另一个桶,而不是直接抱桶倒。
输入流 / 输出流
- 输入流 InputStream:Java 从外部接收数据(读文件、接收请求)
- 输出流 OutputStream:Java 向外部发送数据(写文件、返回响应)
主流子类
| 类 | 特点 |
|---|---|
FileInputStream / FileOutputStream |
最基础的文件流,无缓冲区 |
BufferedInputStream / BufferedOutputStream |
自带缓冲区(默认 8KB),大文件操作效率高 |
2-2 专门的文件读写类
实际接口开发中很少直接操作文件,了解即可。
NIO 包(Java 7+,内置)
两大核心类:
- Paths:文件路径相关操作
- Files:文件读写、获取文件信息
Files 常用操作:判断存在、判断目录、获取目录内容、获取文件信息、读、写、拷贝、删除、新建/删除文件夹。
IO.File 类(老版本)
老版本 Java 用的文件操作类,只能提供文件信息和路径信息,不能直接读写文件内容。现在基本不用。
第三方包
Java 自带的文件操作有局限(如不能直接拷贝文件夹、不能删除非空文件夹),常用第三方包:
org.apache.commons.io.FileUtils
2-3 Java 开启 HTTP 服务
本节课用 Java 原生方式开 HTTP 服务,仅用于理解接口本质,实际开发用 Spring Boot。
接口服务的本质
四步走:
- 开启服务:一个一直运行的程序,等待请求
- 定义路由:哪个地址对应哪段代码
- 构建响应:每个路由中,收到请求后构建响应头 + 响应体
- 发送响应:把响应体返回给前端
原生方式涉及的类
HttpServer:创建服务器对象InetSocketAddress:指定端口HttpHandler:处理请求的接口HttpExchange:封装请求和响应对象
关键注意点
- 返回内容基本都要以 byte 数组形式搭载在 OutputStream 中
- 字符串转 byte 数组用
getBytes() - 响应体长度必须设置准确(用 byte 数组的 length,字符串 length 不一定准)
2-4 根据 id 模糊搜索(实战)
目标
写一个 GET 接口,接收 query 上的 id 参数,找出对应用户并返回。
涉及知识点
- 开启 HTTP 服务(HttpServer)
- 字符串操作(解析 query 参数)
- List 查找(遍历匹配)
2-5 使用第三方包处理 JSON
目标
用第三方库 Jackson 处理对象转 JSON(上节课手动拼接 JSON 太麻烦)。
知识点
- Jackson 库 :Java 最主流的 JSON 处理库
jackson-core、jackson-databind、jackson-annotations
- 如何使用第三方包:下载 jar 包 → 添加到项目 classpath → import 使用
后续会用 Maven 管理依赖,不再手动下载 jar。
第 3 章 工程化与数据库
3-1 Maven 的概念与安装
类比前端
| 前端 | Java |
|---|---|
| npm(包管理) | Maven(包管理 + 构建) |
| webpack/vite(打包) | Maven(打包) |
Maven 核心工作
- 依赖管理 :在
pom.xml(类比 package.json)中写依赖,自动下载安装 - 项目构建:打包成 jar 包(库)或 war 包(服务)
安装
- 官网下载 zip → 解压 → 配置环境变量 →
mvn命令可用 - 也可以不全局安装,项目生成时自带 Maven Wrapper(类似前端项目本地 webpack)
必要配置
- 国内镜像:加速依赖下载(一般必配)
- 依赖存放路径 :可不配,默认
~/.m2/repository
3-2 Maven 管理模块和构建
pom.xml
Maven 的配置文件,等同于前端的 package.json + webpack.config.js。
依赖配置
xml
<dependencies>
<dependency>
<groupId>javax.servlet</groupId> <!-- 组织ID -->
<artifactId>javax.servlet-api</artifactId> <!-- 项目ID -->
<version>4.0.1</version> <!-- 版本 -->
<scope>provided</scope> <!-- 范围(类似 devDependencies) -->
</dependency>
</dependencies>
- groupId :组织标识,也决定项目代码目录(通常
src/main/java/${groupId 按点分割}) - artifactId:项目标识
- version:版本号
- scope:依赖范围(compile/test/provided/runtime)
查找依赖
- Maven 中央仓库搜索:https://central.sonatype.com
- 或直接问 AI 要 pom 坐标
Maven 常用命令
mvn compile:编译为 classmvn package:打包(jar/war)mvn clean:清理构建结果mvn install:安装到本地仓库
打包类型
pom.xml 中 <packaging> 指定:jar(库/可执行程序)或 war(Web 服务)。
3-3 用 Servlet 与 Tomcat 开启服务
本节课理解原理即可,实际开发用 Spring Boot(内置 Tomcat)。
Tomcat
- 轻量级 Web 服务器,用 Java 写的
- 把 Servlet 代码交给 Tomcat,它帮你跑起来
- 也可以跑 JSP、静态 HTML
Servlet
- Java 编写 Web 服务的规范(Java 只提供规范,需额外安装
javax.servlet-api) - Servlet 3.0+ 的写法已经和 Spring Boot 很像了
- 第一次接触到用注解(@Annotation)表达功能,Spring Boot 中会大量使用
3-4 MySQL 安装
为什么需要数据库
之前的接口数据都是一次性的,重启服务数据就重置。数据库可以:
- 持久化保存增删改查的数据
- 作为独立服务运行,Java 接口只是调用它
常见数据库
- MySQL:开源免费,功能齐全(本课程用)
- Oracle:收费,功能更强
- MongoDB:非关系型,Java 不推荐
- SQL Server:Windows 生态,.NET 集成
安装注意
- 选择 Server only 安装
- 设置好 root 用户密码
3-5 数据库模型
基本结构
MySQL 服务
├── 库1(一个项目一个库)
│ ├── 用户表
│ ├── 订单表
│ └── 页面表
└── 库2
└── ...
表设计示例(OA 系统)
- 用户表:id、username、password、nickname
- 用户详细资料表:id、avatar、location、introduction(和用户表通过 id 关联)
- 用户角色表:user_id、role_id
- 角色权限表:role_id、power
设计原则:只把核心内容放主表,扩展信息拆到关联表,通过 id 关联。
3-6 建立表和库
库操作
sql
CREATE DATABASE myoa; -- 新建库
DROP DATABASE myoa; -- 删除库
SHOW DATABASES; -- 查看所有库
USE myoa; -- 进入某个库
表操作
sql
CREATE TABLE users (
id INT,
username VARCHAR(50),
password VARCHAR(255)
);
SHOW TABLES; -- 查看所有表
DROP TABLE users; -- 删除表
ALTER TABLE users ADD COLUMN email VARCHAR(100); -- 加字段
ALTER TABLE users DROP COLUMN email; -- 删字段
ALTER TABLE users MODIFY COLUMN username VARCHAR(100); -- 改字段
MySQL 字段类型
整数类
| 类型 | 字节 | 范围 |
|---|---|---|
| TINYINT | 1 | -128~127 |
| SMALLINT | 2 | -32768~32767 |
| MEDIUMINT | 3 | -838万~838万 |
| INT | 4 | -21亿~21亿 |
| BIGINT | 8 | -263~263-1 |
浮点类:FLOAT(4字节,7位精度)、DOUBLE(8字节,15位精度)
字符类
CHAR(n):固定长度,不足补空格VARCHAR(n):可变长度,最常用TEXT/MEDIUMTEXT/LONGTEXT:大文本
日期类
| 类型 | 格式 | 范围 |
|---|---|---|
| DATE | YYYY-MM-DD | 1000-01-01 ~ 9999-12-31 |
| TIME | HH:MM:SS | -838:59:59 ~ 838:59:59 |
| DATETIME | YYYY-MM-DD HH:MM:SS | 1000~9999 |
| TIMESTAMP | YYYY-MM-DD HH:MM:SS | 1970~2038(支持自动更新、时区转换) |
| YEAR | YYYY | 1901~2155 |
DATETIME vs TIMESTAMP:
- TIMESTAMP 范围小(2038 问题),但支持自动设为当前时间、时区转换
- DATETIME 范围大,适合固定时间
- 记录创建/更新时间推荐用 TIMESTAMP + 默认值
3-7 SQL 语句增删改查
插入(INSERT)
sql
-- 简写(按字段顺序)
INSERT INTO users VALUES (1, 'admin', '123');
-- 显式指定字段(推荐,可缺省有默认值的字段)
INSERT INTO users(id, username, password) VALUES (1, 'admin', '123');
INSERT INTO users(username, password) VALUES ('admin', '123');
查询(SELECT)
sql
SELECT * FROM users; -- 查所有字段
SELECT username, password FROM users; -- 查指定字段
SELECT username FROM users WHERE id = 1; -- 条件查询
更新(UPDATE)
sql
UPDATE users SET username = 'admin2', password = '666'; -- 全表更新(危险!)
UPDATE users SET username = 'admin2' WHERE id = 1; -- 条件更新
删除(DELETE)
sql
DELETE FROM users; -- 清空表(危险!)
DELETE FROM users WHERE id = 1; -- 条件删除
关键字按英语语法理解:SELECT 字段 FROM 表 WHERE 条件。
3-8 字段属性和约束
完整字段定义
sql
CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
...
);
-- 字段名 类型 约束/属性 约束/属性...
约束
| 约束 | 作用 |
|---|---|
PRIMARY KEY |
主键,唯一标识一行,查找最快,不能为空 |
FOREIGN KEY |
外键,关联其他表的主键 |
UNIQUE |
唯一约束,值不能重复 |
NOT NULL |
非空约束 |
CHECK |
检查约束,值必须符合条件 |
主键
- 一般设为 id,主键查找性能最优
- 支持复合主键(多个字段共同作为主键)
- 特性:唯一、非空、可做其他表外键
外键
sql
CREATE TABLE usersinfo (
user_id INT NOT NULL,
...
FOREIGN KEY(user_id) REFERENCES users(id)
ON DELETE CASCADE -- 主表删除时从表自动删除
ON UPDATE CASCADE -- 主表更新时从表自动更新
);
外键定义给表(不是字段),保证关联数据的一致性。
属性
DEFAULT:默认值AUTO_INCREMENT:自增(一般用于主键,一个表只能有一个)
3-9 索引
索引的本质
索引相当于给表建立了目录,查找时先查目录定位,不用全表扫描。设为主键会自动建立主键索引。
常用索引类型
| 类型 | 创建方式 |
|---|---|
| 普通索引 | CREATE INDEX 索引名 ON 表名(字段); |
| 唯一索引 | CREATE UNIQUE INDEX 索引名 ON 表名(字段);(设 UNIQUE 约束自动创建) |
| 主键索引 | 设置主键自动创建,无法手动创建 |
sql
CREATE INDEX idx_create_time ON users(create_time);
CREATE UNIQUE INDEX idx_username ON users(username);
索引的代价
- 额外存储空间:索引本身占物理空间(B-Tree 结构)
- 降低写入性能:插入/更新时需同步更新索引树
原则:按查询频率建立尽可能少的索引,不要所有字段都加索引。
其他索引
复合索引、全文索引、空间索引、前缀索引、函数索引(了解即可)。
3-10 条件与连表查询
条件判断
比较运算
sql
WHERE name = '张三' -- 相等
WHERE name != '张三' -- 不等
WHERE age > 12 -- 大于/小于/大于等于/小于等于
模糊匹配(LIKE)
sql
WHERE username LIKE '张%' -- 以"张"开头
WHERE username LIKE '%勇' -- 以"勇"结尾
WHERE username LIKE '%勇%' -- 包含"勇"
多条件联合
sql
WHERE age > 12 AND name LIKE '%张%' -- 并且
WHERE age > 12 OR name LIKE '%张%' -- 或者
WHERE age BETWEEN 10 AND 18 -- 区间(等价于 >=10 AND <=18)
WHERE name IN ('张三', '李四', '王五') -- 在列表中
WHERE age IS NULL -- 为空
WHERE age IS NOT NULL -- 不为空
连表查询
当数据分散在多个表中,需要联合查询。
sql
SELECT u.id, u.username, c.company_name, c.location
FROM users AS u
INNER JOIN companys AS c ON u.company_id = c.company_id;
三种连接方式
| 类型 | 行为 |
|---|---|
INNER JOIN |
只返回两表匹配上的数据 |
LEFT JOIN |
以左表为主,左表全部返回,右表没匹配上的字段为 null |
RIGHT JOIN |
以右表为主,右表全部返回,左表没匹配上的字段为 null |
3-11 数据库函数
不同数据库函数有差异,常用的记住即可。
聚合函数(最常用)
| 函数 | 作用 |
|---|---|
COUNT() |
计算总条数 |
SUM() |
求和 |
AVG() |
平均值 |
MAX() |
最大值 |
MIN() |
最小值 |
sql
SELECT AVG(salary) AS avg_salary FROM users;
SELECT COUNT(*) AS total FROM users;
字符串函数
CONCAT()(拼接)、LENGTH()(长度)、UPPER()/LOWER()(大小写)、SUBSTRING()(截取)
数字函数
RAND()(随机数)、CEILING()(向上取整)、FLOOR()(向下取整)、MOD()(取余)
时间函数 / 关键字
- 推荐用标准关键字 而非 MySQL 独有函数:
CURRENT_DATE:当前日期CURRENT_TIME:当前时间CURRENT_TIMESTAMP:当前时间戳
- 关键字可作为默认值(如创建时间字段默认
CURRENT_TIMESTAMP),函数不行
ID 生成函数
UUID():生成 UUIDMD5(CURRENT_TIMESTAMP):MD5 加密时间戳作为 ID
什么时候用 SQL 函数?
- 大量数据聚合运算(求和、平均值):用 SQL,效率高于查出来再 Java 循环
- 复杂逻辑/算法:用 Java 更合适
- 字符串/数字/日期处理:优先 Java,避免 SQL 不兼容,提高可读性
3-12 分组、排序、分页
分组(GROUP BY)
按某个字段将相同值的数据合并为一组。
sql
-- 统计每个公司的平均工资
SELECT company_id, AVG(salary) AS avg_salary
FROM users
GROUP BY company_id;
- GROUP BY 后,SELECT 只能选分组字段 + 聚合函数结果
HAVING:对分组结果再筛选(WHERE 是分组前筛选,不能跟聚合函数)
sql
-- 统计平均工资大于 400 的公司
SELECT company_id, AVG(salary) AS avg_salary
FROM users
GROUP BY company_id
HAVING avg_salary > 400;
WHERE vs HAVING:
- WHERE:分组前筛选原始数据
- HAVING:分组后筛选聚合结果,可跟聚合函数
排序(ORDER BY)
sql
SELECT * FROM users ORDER BY salary DESC; -- 降序
SELECT * FROM users ORDER BY username ASC; -- 升序(默认)
分页(LIMIT + OFFSET)
sql
SELECT * FROM users LIMIT 10 OFFSET 20; -- 跳过20条,取10条(第3页)
-- MySQL 简写:LIMIT 偏移, 数量
SELECT * FROM users LIMIT 20, 10;
3-13 数据库可视化软件
| 软件 | 特点 |
|---|---|
| DBeaver | 免费,支持多种数据库 |
| MySQL Workbench | MySQL 官方工具 |
| Navicat | 收费商业软件,好用,支持多种数据库 |
第 4 章 Spring Boot 实战
4-1 Spring Boot 创建项目
什么是 Spring Boot
类比前端开发:
- Vue 提供简便开发方式和指令 → Spring Boot 提供简便开发方式和指令
- npm 管理包 → Maven 管理依赖和构建
- webpack/vite 构建 → Maven 打包
- Node.js 运行时 → Servlet + Tomcat 运行服务
Spring Boot 项目核心构成:Servlet + Tomcat(开服务)+ Spring Boot 本身(开发指令)+ Maven(包管理和构建)
项目目录结构
项目根目录
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com.study.study1 # 写代码的地方(groupId.artifactId)
│ │ │ └── Study1Application.java # 启动类(等同 Vue 的 main.js)
│ │ └── resources
│ │ ├── static/ # 静态资源(可直接访问)
│ │ ├── templates/ # HTML 模板(接口开发一般不用)
│ │ └── application.yml # 项目配置(端口、数据库等)
│ └── test/ # 单元测试
├── target/ # Maven 构建结果
└── pom.xml # Maven 配置和依赖
关键文件
application.yml/application.properties:Spring Boot 配置文件pom.xml:Maven 依赖管理XxxApplication.java:启动类,带@SpringBootApplication注解
4-2 定义第一个接口
核心注解
| 注解 | 作用 |
|---|---|
@RestController |
声明这是控制器,方法返回值自动序列化为 JSON(= @Controller + @ResponseBody) |
@GetMapping("/api1") |
定义 GET 接口 |
@PostMapping("/api2") |
定义 POST 接口 |
@RequestMapping("/user") |
不区分请求方式,一般用于类上定义统一前缀 |
java
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/hello")
public String hello() {
return "hello world";
}
@GetMapping("/json")
public Map<String, Object> getJson() {
Map<String, Object> result = new HashMap<>();
result.put("name", "张三");
return result;
}
}
注解在 Spring Boot 中的意义
Spring Boot 坊间叫"面向注解编程"。注解类比 Vue 的指令(如 v-for),Spring Boot 扫描代码时看到注解就知道要做什么,帮你自动完成底层工作。
4-3 请求参数处理
五种参数场景及注解
| 场景 | 注解 | 示例 |
|---|---|---|
| GET query 单个参数 | @RequestParam |
@RequestParam("id") String id |
| GET query 整个对象 | @ModelAttribute |
@ModelAttribute User user |
| POST body | @RequestBody |
@RequestBody User user |
| 请求头参数 | @RequestHeader |
@RequestHeader("token") String token |
| 路径参数 | @PathVariable |
/api1/{id} + @PathVariable Long id |
java
@GetMapping("/api1")
public String api1(
@RequestParam(name = "id", defaultValue = "23", required = false) String id,
@RequestHeader("token") String token
) {
return "id=" + id;
}
@PostMapping("/api2")
public String api2(@RequestBody User user) {
return user.getUsername();
}
@RequestParam 进阶 :required 控制是否必传(默认 true),defaultValue 设置默认值。
4-4 项目层级规范
类比前端拆分组件层、工具层、请求层,Java 项目也分层:
前端请求 → Controller层 → Service层 → Repository层 → 数据库
↓ ↓ ↓
DTO入参 业务逻辑 数据库操作
↓ ↓ ↓
VO出参 ← ─── Entity ← ─── 数据库数据
各层作用
| 层 | 作用 |
|---|---|
| Controller | 定义接口地址和入口方法,接收参数,返回结果 |
| Service | 业务逻辑处理 |
| Repository | 数据库增删改查操作 |
| Model | 数据结构定义 |
Model 层细分
| 类型 | 含义 |
|---|---|
| DTO | 前端发来的数据 / 层间传递的数据结构 |
| Entity | 数据库表对应的实体(从数据库取出的数据结构) |
| VO | 返回给前端展示的数据结构 |
实战中 Model 不会分得这么细,很多时候数据结构一样就共用了。
4-5 设计一个接口的分层
目标:根据 id 查询用户信息
分层实现:
- Controller 层 :
UserController,定义searchUser接口 - Service 层 :
UserService,写searchUser业务方法 - Repository 层 :
UserRepository,数据库操作 - Model 层 :
- 通用返回 VO 类(
BaseVo:success、msg、data) - 用户 Entity(数据库结构)
- 用户 VO(返回前端结构)
- 通用返回 VO 类(
Lombok 库
写 Model 类时不用手写 getter/setter,用 @Data 注解自动生成。
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
java
@Data
public class User {
private String id;
private String username;
private String password;
}
// 自动生成 getter、setter、toString、equals、hashCode
4-6 Spring 依赖注入体系(IOC)
IOC(控制反转)思想
传统方式:代码中手动 new 对象。
IOC 方式:对象的创建和管理交给 Spring 容器,开发者只定义类,需要时让 Spring 自动注入。
类比:你不用自己 new Service,Spring 帮你创建好并赋值。
加入容器的注解(告诉 Spring 管理这个类)
| 注解 | 用途 |
|---|---|
@Controller / @RestController |
控制器层 |
@Service |
Service 层 |
@Repository |
Repository 层 |
@Component |
通用组件(工具类等) |
@Configuration |
配置类 |
@Bean |
把方法返回值加入容器 |
从容器取出的注解
| 注解 | 作用 |
|---|---|
@Autowired |
按类型自动从容器取出并注入 |
@Value |
注入配置文件中的值 |
@Qualifier |
按名称指定,消除歧义 |
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // Spring 自动注入
}
@SpringBootApplication 拆解
由三个注解组成:
@Configuration:定义为配置类@EnableAutoConfiguration:启动自动配置@ComponentScan:定义扫描范围(可配置扫描/排除某些包)
4-7 操作数据库(上)
第一步:连接数据库
1. 安装 MySQL 驱动
xml
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.2.0</version>
</dependency>
2. 配置数据库连接(application.yml)
yaml
spring:
datasource:
url: "jdbc:mysql://localhost:3306/db_name?useSSL=false"
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: "adminback"
server:
port: 3000
主流数据库操作工具对比
| 工具 | 占有率 | 简便度 | 复杂查询 |
|---|---|---|---|
| JPA | 国外高 | 简单(方法名即查询) | 不太好用 |
| MyBatis | 国内高 | 需写 SQL | 最能应对复杂业务 |
| JDBC | 少 | 需写 SQL | 中等 |
| MyBatis-Plus | 配合 MyBatis | API 式查询,简单 | 不擅长复杂查询 |
本课程主要用 MyBatis。
JPA 使用方式
- 定义 Entity,加
@Entity,注明主键和表名 - Repository 继承
JpaRepository<Entity, 主键类型> - 直接写方法名即可查询,不用写 SQL
4-8 操作数据库(下)
MyBatis 使用
xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
Repository 接口中用 @Select、@Insert、@Update、@Delete 注解写 SQL。
java
@Mapper
public interface UserRepository {
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(String id);
@Select("SELECT * FROM user WHERE username LIKE CONCAT('%', #{name}, '%')")
List<User> findByUsername(String name);
}
MyBatis-Plus 使用
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
继承 BaseMapper<T>,不用写 SQL 即可完成基础增删改查。
4-9 登录与注册接口
用户表设计
| 字段 | 类型 | 说明 |
|---|---|---|
| id | 字符串 | 主键,UID 生成 |
| username | 字符串 | 唯一索引 |
| password | 字符串 | 密码 |
| create_time | Date | 创建时自动填写 |
| update_time | Date | 创建时填写,更新时更新 |
注册接口逻辑
- 接收 username、password
- 根据 username 查询数据库,判断是否已存在
- 已存在 → 返回"用户名已经存在"
- 不存在 → 插入新用户 → 返回成功
登录接口逻辑
- 接收 username、password
- 根据 username 查询用户
- 不存在 → 返回"用户不存在"
- 存在 → 判断密码是否相等
- 密码正确 → 返回用户信息(id、username)
- 密码错误 → 返回失败
统一返回格式
json
{
"success": true,
"msg": "成功",
"data": { "id": "xxx", "username": "xxx" }
}
4-10 修改密码和注销
修改密码接口
- 入参:id、oldPassword、newPassword
- 逻辑:根据 id 查用户 → 校验老密码 → 匹配则更新密码,不匹配返回失败
注销(删除)接口
- 入参:id
- 逻辑:根据 id 查用户 → 存在则删除,不存在返回失败
通用入参格式可抽取为公共 DTO 复用(如
UserIdDTO)。
4-11 用 MyBatis-Plus 简化增删改查
对于简单的增删改查,用 MyBatis-Plus 比手写 SQL 更高效。
第一步:定义 Entity
java
@TableName("user") // 对应表名
public class User {
@TableId(type = IdType.ASSIGN_UUID) // 主键,自动生成
private String id;
@TableField(value = "username")
private String username;
@TableField(fill = FieldFill.INSERT) // 插入时自动填充
private Date createTime;
}
第二步:定义 Mapper
java
public interface UserRepository extends BaseMapper<User> {
// 不用写任何方法,基础 CRUD 已继承
}
常用方法
| 操作 | 方法 |
|---|---|
| 按主键查 | selectById(id) |
| 查单条 | selectOne(queryWrapper) |
| 查列表 | selectList(queryWrapper) |
| 插入 | insert(entity) |
| 按主键更新 | updateById(entity) |
| 按条件更新 | update(entity, queryWrapper) |
| 按主键删除 | deleteById(id) |
| 按条件删除 | delete(queryWrapper) |
QueryWrapper 条件构造
java
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", "admin"); // 相等
wrapper.like("username", "张"); // 模糊查询
wrapper.gt("age", 18); // 大于
wrapper.ge("age", 18); // 大于等于
wrapper.lt("age", 60); // 小于
wrapper.le("age", 60); // 小于等于
wrapper.last("LIMIT 10"); // 追加 SQL
User user = userRepository.selectOne(wrapper);
MyBatis-Plus 不适合复杂查询(连表、多条件联合),复杂查询仍用 MyBatis 手写 SQL。
4-12 拦截器、过滤器和全局配置
类比前端的路由守卫、axios 请求/响应拦截器。
执行顺序
请求 → Servlet容器 → 过滤器 → Spring → 拦截器(前置) → Controller → 拦截器(后置) → 响应
过滤器(Filter)
Servlet 提供,最早执行,只能获取基本的请求和响应。
java
@Component
@Order(1) // 执行顺序,数字越小越优先
@WebFilter(urlPatterns = "/*") // 拦截路径
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
// 前置处理
chain.doFilter(req, res); // 通过,继续执行
// 后置处理
}
}
拦截器(Interceptor)
Spring 提供,进入 Spring 后执行,可获取 Spring 上下文。
java
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
// 控制器执行前,返回 false 则中断
return true;
}
@Override
public void postHandle(...) {
// 控制器执行后,视图渲染前
}
@Override
public void afterCompletion(...) {
// 整个请求完成后
}
}
注册拦截器:
java
@Configuration
public class GlobalMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login", "/register");
}
}
过滤器 vs 拦截器
| 对比 | 过滤器 | 拦截器 |
|---|---|---|
| 提供者 | Servlet | Spring |
| 时机 | 最早 | 进入 Spring 后 |
| 能获取 | 基本请求响应 | Spring 包装后的请求响应 + 上下文 |
MVC 全局配置(WebMvcConfigurer)
可配置:拦截器、跨域(CORS)、视图解析、静态资源、消息转换、参数解析等。
4-13 CORS 跨域设置
跨域解决方案
- 前端 dev 服务代理(仅开发环境)
- 后端设置 CORS(生产环境)
CORS 本质
在响应头中设置三个字段:
| 响应头 | 作用 |
|---|---|
Access-Control-Allow-Origin |
允许的域名(* 为所有,生产环境设为前端地址) |
Access-Control-Allow-Methods |
允许的请求方式(GET、POST 等) |
Access-Control-Allow-Headers |
允许的请求头(如 token、自定义头) |
实现方式:过滤器、拦截器、MVC 全局配置都可以做。
4-14 Token 体系(JWT)
Token 流程
- 用户登录成功 → 后端生成 token → 返回前端
- 前端存 token(localStorage)→ 后续请求放在请求头
- 后端拦截器解析 token → 验证合法性和有效期
JWT 组成
JWT 由三部分组成,用 . 分隔:
- Header(头部) :JSON,说明加密算法和类型
{"alg":"HS256","typ":"JWT"} - Payload(荷载):JSON,携带用户信息(id、username、过期时间 exp)
- Signature(签名):用密钥对前两部分加密,防止伪造
使用的库(jjwt)
xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-gson</artifactId>
<version>0.11.2</version>
</dependency>
生成 Token
java
String token = Jwts.builder()
.setSubject(userId) // 设置荷载
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 过期时间
.signWith(secretKey) // 签名
.compact();
验证 Token(拦截器中)
java
Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
String userId = claims.getSubject();
Date exp = claims.getExpiration();
验证失败(格式错误、密钥错误、过期)→ 返回 401。
4-15 利用 Session 做验证码
什么是 Session
后端与前端的临时会话,针对当前用户临时存储一些信息(不存数据库)。
工作流程:
- 用户请求验证码 → 后端生成验证码并存入 Session → 生成 sessionId → 通过 Set-Cookie 响应头返回
- 浏览器自动存 cookie(含 sessionId)
- 用户登录时带上 cookie → 后端解析 sessionId → 取出之前存的验证码比对
Spring Boot 中使用 Session
java
// 存 session
@GetMapping("/captcha")
public String captcha(HttpSession session) {
session.setAttribute("code", "1234");
return "ok";
}
// 取 session
@PostMapping("/login")
public String login(HttpSession session) {
String code = (String) session.getAttribute("code");
return code;
}
图形验证码(Kaptcha 库)
xml
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
流程:生成验证码文字 → 存 Session → 生成验证码图片 → 图片转 Base64 → 返回前端
Base64 操作
java
// 编码:byte[] → Base64 字符串
String base64 = Base64.getEncoder().encodeToString(byteArr);
// 解码:Base64 字符串 → byte[]
byte[] origin = Base64.getDecoder().decode(base64Str);
Base64 只能编码 byte\[\],文件要先转为字节输出流,字符串用
getBytes()。
相关类
- ImageIO:Java 自带图片读写库,可读取为 BufferedImage
- BufferedImage:可操作的图片对象(获取宽高、操作像素)
- ByteArrayOutputStream:内存中的输出流(不写入硬盘),用于生成图片/Excel 后直接返回前端
4-16 用户列表查询(分页)
入参
json
{
"pageNum": 1,
"pageSize": 10,
"id": "xx",
"username": "xxx",
"startTime": "2025-01-12 00:00:00",
"endTime": "2025-01-16 23:59:59"
}
出参
json
{
"success": true,
"msg": "成功",
"data": {
"total": 100,
"list": [],
"pageNum": 1,
"pageSize": 10,
"hasMore": true
}
}
分页逻辑
- 计算 offset = (pageNum - 1) * pageSize
- 查询用户列表(带条件 + LIMIT + OFFSET)
- 查询用户总数(COUNT)
- 组装 VO 返回
MyBatis XML 写复杂 SQL
当 SQL 复杂时,用单独的 XML 文件而非注解:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.repository.UserRepository">
<select id="findUserList" resultType="com.example.entity.User">
SELECT * FROM user
<where>
<if test="id != null">AND id LIKE CONCAT('%', #{id}, '%')</if>
<if test="username != null">AND username LIKE CONCAT('%', #{username}, '%')</if>
</where>
LIMIT #{pageSize} OFFSET #{offset}
</select>
</mapper>
核心标签 :<mapper>(根)、<select>(查询)、<where>(条件)、<if>(动态判断)。
4-17 页面模块
需求
后台管理系统的权限控制第一步:以树状结构查询所有页面(模块 → 顶层页面 → 子页面 → ...)。
表设计
- 模块表(modules):module_id、module_name
- 页面表(pages):page_id、page_name、page_path、parent_id(父页面 id,顶层为 null)、module_id
业务逻辑
- 查出所有模块
- 循环每个模块,查该模块下的顶层页面(parent_id IS NULL)
- 对每个顶层页面,递归查询子页面,塞入 children
- 无子页面则 children 为空数组
Model 设计
- Entity:Modules、Pages(对应数据库表,用 MyBatis-Plus)
- VO:ModulesVo(含 page 列表)、PageVo(含 children 列表,支持无限嵌套)
4-18 查询用户权限
需求
根据用户 id 查询该用户能访问哪些页面(涉及多表关联)。
新增 3 张表
- 角色表(roles):role_id、role_name(超级管理员、系统管理员、职员等)
- 用户角色表(user_role):user_id、role_id(用户属于哪个角色)
- 角色页面表(role_page):role_id、page_id(角色能访问哪些页面)
查询流程
- 用户表 + 用户角色表 + 角色表 + 角色页面表 → 四表联查,查出用户能访问的 page_id 列表
- 根据 page_id 列表查页面详情和模块信息
- 用
GROUP_CONCAT按模块分组聚合页面 id - Java 中分割 id 字符串 → 查每个页面详情 → 组装成树状结构
联查 SQL
sql
SELECT u.id, u.username, r.role_name, rp.page_id
FROM user AS u
LEFT JOIN user_role AS ur ON u.id = ur.user_id
LEFT JOIN roles AS r ON ur.role_id = r.role_id
LEFT JOIN role_page AS rp ON rp.role_id = ur.role_id
WHERE u.id = #{id}
4-19 ~ 4-22 综合实战模块
以下章节为代码实战,在前面的权限系统基础上继续扩展。
4-19 订单模块
- 新增订单表(orders):order_id、order_name、status、user_id 等
- 实现订单的增删改查接口
- 订单状态用枚举管理
- InsertOrderDTO 作为新增入参
4-20 用户余额
- 新增用户余额表(user_money)
- 下单时扣减余额(涉及事务控制)
- 余额不足时返回业务异常
4-21 定时任务
- 使用 Spring
@Scheduled注解实现定时任务 - 典型场景:定时取消超时未支付订单
- 启动类需加
@EnableScheduling开启定时任务
java
@Component
public class OrderScheduled {
@Scheduled(cron = "0 0/30 * * * ?") // 每30分钟执行
public void cancelTimeoutOrders() {
// 取消超时订单
}
}
4-22 统计查询
- 按天统计订单数量/金额
- 使用 SQL 聚合函数 + GROUP BY 日期
- CountDTO、CountByDayDTO 作为统计结果出参
- 典型 SQL:
SELECT DATE(create_time) AS day, COUNT(*) AS count FROM orders GROUP BY DATE(create_time)
附录:前端开发者学 Java 的关键映射
| 前端概念 | Java 对应 |
|---|---|
| JS 数组(可变) | List(ArrayList) |
| JS 对象(键值对) | Map(HashMap) |
| const | final |
| 模块(import/export) | 包(package/import) |
| npm + package.json | Maven + pom.xml |
| webpack/vite | Maven 构建 |
| Vue 指令(v-for 等) | Spring Boot 注解 |
| 路由守卫 / axios 拦截 | 过滤器 / 拦截器 |
| localStorage token | 同样的 token 机制 |
| Node.js 服务 | Servlet + Tomcat |
| ORM(如 Prisma) | JPA / MyBatis-Plus |
| 环境变量 .env | application.yml |
| 依赖注入(Vue provide/inject) | Spring IOC(@Autowired) |
笔记整理完毕。覆盖 Java 基础 → IO/网络 → Maven/数据库 → Spring Boot 全栈开发