GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

软件研发 --- Java

 

第一阶段:Java基础


1. 安装配置JDK、开发工具安装(IDEA)

JDK(Java Development Kit)

什么是JDK:
JDK是Java开发工具包,是Java开发的基础环境。它包含了:

  • JRE(Java Runtime Environment):Java运行时环境
  • JVM(Java Virtual Machine):Java虚拟机,负责执行字节码
  • 编译器(javac):将.java源文件编译成.class字节码文件
  • 调试工具(jdb):用于调试Java程序
  • 文档工具(javadoc):生成API文档

JDK、JRE、JVM的关系:

text
JDK(最大)
├── JRE(中间)
│   ├── JVM(最小)
│   └── 核心类库(rt.jar等)
├── 编译器(javac)
├── 调试器(jdb)
└── 其他开发工具

安装步骤:

  1. 下载JDK(推荐JDK 8或JDK 11 LTS版本)
  2. 安装到指定目录(如:C:\Program Files\Java\jdk1.8.0_xxx)
  3. 配置环境变量:
    • JAVA_HOME:指向JDK安装目录
    • Path:添加 %JAVA_HOME%\bin
    • CLASSPATH:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar
  4. 验证:命令行输入 java -version 和 javac -version

为什么需要配置环境变量:

  • JAVA_HOME:让其他工具(如IDEA、Maven、Tomcat)知道JDK安装在哪里
  • Path:让操作系统在任何目录下都能找到java和javac命令
  • CLASSPATH:告诉JVM去哪里找.class文件

IDEA(IntelliJ IDEA)

什么是IDEA:
IDEA是JetBrains公司开发的Java集成开发环境(IDE),被认为是最强大的Java IDE。

IDEA vs Eclipse:

特性IDEAEclipse
智能提示 极其强大 一般
代码重构 强大 一般
插件生态 丰富 丰富
性能 需要较多内存 相对轻量
价格 社区版免费/旗舰版收费 完全免费

IDEA核心概念:

  • Project(项目):最顶层的结构,相当于Eclipse的Workspace
  • Module(模块):一个项目可以包含多个模块,相当于Eclipse的Project
  • Package(包):组织Java类的文件夹结构
  • Class(类):Java源代码文件

IDEA常用快捷键:

text
psvm + Tab    → public static void main(String[] args) {}
sout + Tab    → System.out.println();
Ctrl + D      → 复制当前行
Ctrl + Y      → 删除当前行
Ctrl + /      → 单行注释
Ctrl + Shift + / → 多行注释
Alt + Enter   → 智能修复
Ctrl + Alt + L → 格式化代码
Shift + F6    → 重命名

第一个Java程序:

Java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

程序执行流程:

text
HelloWorld.java → javac编译 → HelloWorld.class → java执行 → JVM运行 → 输出结果
   (源文件)                      (字节码文件)

2. 数据类型、变量、常量

数据类型

Java是强类型语言,每个变量都必须声明其类型。

Java数据类型分类:

text
数据类型
├── 基本数据类型(Primitive Types)—— 8种
│   ├── 整数型
│   │   ├── byte    (1字节,-128 ~ 127)
│   │   ├── short   (2字节,-32768 ~ 32767)
│   │   ├── int     (4字节,约±21亿)【默认】
│   │   └── long    (8字节,非常大)
│   ├── 浮点型
│   │   ├── float   (4字节,单精度)
│   │   └── double  (8字节,双精度)【默认】
│   ├── 字符型
│   │   └── char    (2字节,0 ~ 65535,存储Unicode字符)
│   └── 布尔型
│       └── boolean (true / false)
└── 引用数据类型(Reference Types)
    ├── 类(Class)—— 如 String
    ├── 接口(Interface)
    └── 数组(Array)

各类型详细说明:

Java
// 整数型
byte b = 127;           // 最小的整数类型,节省空间
short s = 32767;        // 短整型,较少使用
int i = 2147483647;     // 最常用的整数类型
long l = 9223372036854775807L;  // 需要加L后缀

// 浮点型
float f = 3.14F;        // 需要加F后缀
double d = 3.141592653; // 默认浮点类型,精度更高

// 字符型
char c1 = 'A';          // 单个字符,用单引号
char c2 = 65;           // 也可以用ASCII码值
char c3 = '\u0041';     // 也可以用Unicode编码

// 布尔型
boolean flag = true;    // 只有true和false两个值

类型转换:

text
自动类型转换(隐式):小类型 → 大类型(安全,不丢失精度)
byte → short → int → long → float → double
                char ↗

强制类型转换(显式):大类型 → 小类型(可能丢失精度)
Java
// 自动类型转换
int num = 100;
double d = num;         // int → double,自动转换,d = 100.0

// 强制类型转换
double pi = 3.14;
int n = (int) pi;       // double → int,强制转换,n = 3(丢失小数部分)

// 注意:byte、short运算时自动提升为int
byte b1 = 10, b2 = 20;
// byte b3 = b1 + b2;   // 错误!b1+b2结果是int
byte b3 = (byte)(b1 + b2);  // 正确
int b4 = b1 + b2;           // 正确

变量

什么是变量:
变量是程序中用于存储数据的容器,其值可以改变。

变量的三要素:

  1. 数据类型:决定分配多少内存空间
  2. 变量名:访问这块内存的标识
  3. 值:存储在内存中的数据
Java
// 声明变量的方式
int age;            // 先声明
age = 25;           // 后赋值

int score = 100;    // 声明并赋值

int a = 1, b = 2, c = 3;  // 同时声明多个同类型变量

命名规则:

  • 由字母、数字、下划线_、美元符$组成
  • 不能以数字开头
  • 不能使用Java关键字(如class、int、public等)
  • 区分大小写
  • 建议使用驼峰命名法:studentNametotalScore

常量

什么是常量:
常量是程序运行过程中值不可改变的量。

Java
// 使用final关键字定义常量
final double PI = 3.14159265;
final int MAX_SIZE = 100;

// PI = 3.14;  // 错误!常量不能重新赋值

// 常量命名规范:全部大写,多个单词用下划线分隔
final String DATABASE_URL = "jdbc:mysql://localhost:3306/test";

字面常量:

Java
100        // 整数常量
3.14       // 浮点数常量
'A'        // 字符常量
"Hello"    // 字符串常量
true/false // 布尔常量
null       // 空常量

3. 运算符号

算术运算符

Java
int a = 10, b = 3;

System.out.println(a + b);   // 13  加法
System.out.println(a - b);   // 7   减法
System.out.println(a * b);   // 30  乘法
System.out.println(a / b);   // 3   整数除法(取整)
System.out.println(a % b);   // 1   取余(模运算)

// 注意:整数除法会截断小数
System.out.println(10 / 3);     // 3(不是3.333...)
System.out.println(10.0 / 3);   // 3.333...(有一个是浮点数就保留小数)

// 自增自减运算符
int x = 5;
x++;    // x = 6,先使用后自增
++x;    // x = 7,先自增后使用
x--;    // x = 6,先使用后自减
--x;    // x = 5,先自减后使用

// 重要区别
int m = 5;
int n1 = m++;  // n1 = 5, m = 6(先赋值,再自增)
int n2 = ++m;  // n2 = 7, m = 7(先自增,再赋值)

字符串拼接中的 +

Java
System.out.println("Hello" + "World");   // HelloWorld
System.out.println("Age: " + 25);        // Age: 25
System.out.println(1 + 2 + "abc");       // 3abc(先算1+2=3,再拼接)
System.out.println("abc" + 1 + 2);       // abc12(遇到字符串后都是拼接)

赋值运算符

Java
int a = 10;      // 基本赋值

a += 5;          // a = a + 5;  → a = 15
a -= 3;          // a = a - 3;  → a = 12
a *= 2;          // a = a * 2;  → a = 24
a /= 4;          // a = a / 4;  → a = 6
a %= 4;          // a = a % 4;  → a = 2

// 注意:复合赋值运算符会自动进行类型转换
byte b = 10;
// b = b + 5;    // 错误!b+5结果是int
b += 5;          // 正确!等价于 b = (byte)(b + 5)

比较运算符(关系运算符)

结果都是boolean类型(true或false)。

Java
int a = 10, b = 20;

System.out.println(a == b);   // false  等于
System.out.println(a != b);   // true   不等于
System.out.println(a > b);    // false  大于
System.out.println(a < b);    // true   小于
System.out.println(a >= b);   // false  大于等于
System.out.println(a <= b);   // true   小于等于

// 注意:== 和 = 的区别
// == 是比较运算符,判断是否相等
// =  是赋值运算符,将右边的值赋给左边

逻辑运算符

操作布尔值,结果也是布尔值。

Java
boolean a = true, b = false;

// 与(AND):两个都为true,结果才为true
System.out.println(a & b);    // false  (逻辑与)
System.out.println(a && b);   // false  (短路与)

// 或(OR):只要一个为true,结果就为true
System.out.println(a | b);    // true   (逻辑或)
System.out.println(a || b);   // true   (短路或)

// 非(NOT):取反
System.out.println(!a);       // false

// 异或(XOR):两个不同为true,相同为false
System.out.println(a ^ b);    // true

短路与&& vs 逻辑与&

Java
int x = 5;
// 短路与:如果第一个条件为false,第二个条件不执行
if (x > 10 && ++x > 5) { }  // x = 5(++x没有执行)

// 逻辑与:两个条件都会执行
if (x > 10 & ++x > 5) { }   // x = 6(++x被执行了)

三元运算符(条件运算符)

Java
// 格式:条件表达式 ? 值1 : 值2
// 如果条件为true,结果是值1;如果条件为false,结果是值2

int a = 10, b = 20;
int max = (a > b) ? a : b;   // max = 20

String result = (score >= 60) ? "及格" : "不及格";

// 三元运算符可以嵌套(但不推荐,影响可读性)
int x = 10, y = 20, z = 15;
int max3 = (x > y) ? (x > z ? x : z) : (y > z ? y : z);  // max3 = 20

运算符优先级(从高到低):

text
()                          括号最高
++  --  !                   一元运算符
*   /   %                   乘除取余
+   -                       加减
>   <   >=  <=              比较
==  !=                      等于不等于
&&                          短路与
||                          短路或
?:                          三元运算符
=   +=  -=  *=  /=  %=     赋值

4. 流程控制:选择结构 if switch

if 语句

程序默认是从上到下顺序执行的,选择结构可以根据条件决定执行哪段代码。

Java
// ============ 形式1:单分支 ============
if (条件) {
    // 条件为true时执行
}

// 例:
int age = 20;
if (age >= 18) {
    System.out.println("你已成年");
}

// ============ 形式2:双分支 ============
if (条件) {
    // 条件为true时执行
} else {
    // 条件为false时执行
}

// 例:
int score = 55;
if (score >= 60) {
    System.out.println("及格");
} else {
    System.out.println("不及格");
}

// ============ 形式3:多分支 ============
if (条件1) {
    // 条件1为true时执行
} else if (条件2) {
    // 条件2为true时执行
} else if (条件3) {
    // 条件3为true时执行
} else {
    // 以上条件都不满足时执行
}

// 例:
int score = 85;
if (score >= 90) {
    System.out.println("优秀");
} else if (score >= 80) {
    System.out.println("良好");  // 输出这个
} else if (score >= 60) {
    System.out.println("及格");
} else {
    System.out.println("不及格");
}

if 嵌套

Java
// 在if内部再嵌套if
int score = 85;
String gender = "男";

if (score >= 60) {
    System.out.println("考试通过");
    if (gender.equals("男")) {
        System.out.println("帅哥,恭喜你通过");
    } else {
        System.out.println("美女,恭喜你通过");
    }
} else {
    System.out.println("考试未通过");
}

// 实际应用:判断闰年
int year = 2024;
if (year % 4 == 0) {
    if (year % 100 != 0) {
        System.out.println(year + "是闰年");
    } else {
        if (year % 400 == 0) {
            System.out.println(year + "是闰年");
        } else {
            System.out.println(year + "不是闰年");
        }
    }
} else {
    System.out.println(year + "不是闰年");
}

// 简化写法
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
    System.out.println("闰年");
}

switch 语句

当有多个固定值需要判断时,switch比多个if-else更清晰。

Java
// 基本语法
switch (表达式) {
    case 值1:
        语句1;
        break;
    case 值2:
        语句2;
        break;
    case 值3:
        语句3;
        break;
    default:
        默认语句;
        break;
}

// 例:根据星期数输出
int day = 3;
switch (day) {
    case 1:
        System.out.println("星期一");
        break;
    case 2:
        System.out.println("星期二");
        break;
    case 3:
        System.out.println("星期三");  // 输出这个
        break;
    case 4:
        System.out.println("星期四");
        break;
    case 5:
        System.out.println("星期五");
        break;
    case 6:
        System.out.println("星期六");
        break;
    case 7:
        System.out.println("星期日");
        break;
    default:
        System.out.println("无效的星期");
        break;
}

switch支持的数据类型:

  • byte、short、int、char
  • String(JDK 7+)
  • 枚举类型(enum)
  • 不支持:long、float、double、boolean

break的重要性(case穿透):

Java
int day = 2;
switch (day) {
    case 1:
        System.out.println("星期一");
        // 没有break,会继续执行下面的case
    case 2:
        System.out.println("星期二");
        // 没有break,继续穿透
    case 3:
        System.out.println("星期三");
        break;  // 遇到break才停止
    case 4:
        System.out.println("星期四");
        break;
}
// 输出:星期二  星期三(穿透到break为止)

// case穿透的应用:多个case执行相同代码
switch (day) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        System.out.println("工作日");
        break;
    case 6:
    case 7:
        System.out.println("休息日");
        break;
}

switch嵌套:

Java
int year = 2024;
int month = 2;

switch (month) {
    case 1: case 3: case 5: case 7: case 8: case 10: case 12:
        System.out.println("31天");
        break;
    case 4: case 6: case 9: case 11:
        System.out.println("30天");
        break;
    case 2:
        // 嵌套判断是否闰年
        if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
            System.out.println("29天");
        } else {
            System.out.println("28天");
        }
        break;
}

if vs switch 选择:

场景推荐
判断区间范围(score >= 60) if
判断固定的等值(day == 1) switch
判断布尔条件组合 if
分支很多且都是等值判断 switch

5. 流程控制:循环结构 while do-while for

while 循环

先判断条件,再执行循环体。如果条件一开始就是false,循环体一次都不执行。

Java
// 语法
while (条件) {
    循环体;
    迭代语句;  // 改变条件的语句,防止死循环
}

// 例1:打印1~10
int i = 1;
while (i <= 10) {
    System.out.println(i);
    i++;
}

// 例2:计算1+2+3+...+100
int sum = 0;
int n = 1;
while (n <= 100) {
    sum += n;
    n++;
}
System.out.println("1到100的和:" + sum);  // 5050

// 例3:猜数字游戏
Scanner sc = new Scanner(System.in);
int target = 50;
int guess = 0;
while (guess != target) {
    System.out.print("请猜一个数:");
    guess = sc.nextInt();
    if (guess > target) {
        System.out.println("猜大了");
    } else if (guess < target) {
        System.out.println("猜小了");
    }
}
System.out.println("恭喜你猜对了!");

do-while 循环

先执行循环体,再判断条件。循环体至少执行一次。

Java
// 语法
do {
    循环体;
    迭代语句;
} while (条件);  // 注意这里有分号!

// 例1:打印1~10
int i = 1;
do {
    System.out.println(i);
    i++;
} while (i <= 10);

// 例2:do-while至少执行一次的体现
int x = 100;

// while版:条件一开始就是false,不执行
while (x < 10) {
    System.out.println("while: " + x);  // 不会执行
    x++;
}

// do-while版:先执行一次,再判断条件
do {
    System.out.println("do-while: " + x);  // 会执行一次,输出100
    x++;
} while (x < 10);

// 实际应用:输入验证(必须先输入一次)
Scanner sc = new Scanner(System.in);
int input;
do {
    System.out.print("请输入1~100之间的数:");
    input = sc.nextInt();
} while (input < 1 || input > 100);
System.out.println("你输入的是:" + input);

for 循环

最常用的循环,适合已知循环次数的场景。

Java
// 语法
for (初始化语句; 条件表达式; 迭代语句) {
    循环体;
}

// 执行顺序:
// 1. 执行初始化语句(只执行一次)
// 2. 判断条件表达式
// 3. 如果为true,执行循环体
// 4. 执行迭代语句
// 5. 回到第2步

// 例1:打印1~10
for (int i = 1; i <= 10; i++) {
    System.out.println(i);
}

// 例2:计算阶乘 5! = 1×2×3×4×5
int factorial = 1;
for (int i = 1; i <= 5; i++) {
    factorial *= i;
}
System.out.println("5! = " + factorial);  // 120

// 例3:遍历数组
int[] arr = {10, 20, 30, 40, 50};
for (int i = 0; i < arr.length; i++) {
    System.out.println("arr[" + i + "] = " + arr[i]);
}

// 例4:倒序循环
for (int i = 10; i >= 1; i--) {
    System.out.println(i);
}

// 例5:步长为2
for (int i = 0; i <= 100; i += 2) {
    System.out.println(i);  // 0, 2, 4, 6, 8, ...
}

三种循环的对比:

特性whiledo-whilefor
先判断还是先执行 先判断 先执行 先判断
最少执行次数 0次 1次 0次
适用场景 不确定循环次数 至少执行一次 已知循环次数
变量作用域 循环外 循环外 可以在循环内

6. 流程控制:多重循环(嵌套、continue、break)

循环嵌套

Java
// 外层循环控制行数,内层循环控制每行的内容

// 例1:打印矩形
// *****
// *****
// *****
for (int i = 0; i < 3; i++) {        // 3行
    for (int j = 0; j < 5; j++) {    // 每行5个*
        System.out.print("*");
    }
    System.out.println();             // 换行
}

// 例2:打印直角三角形
// *
// **
// ***
// ****
// *****
for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= i; j++) {
        System.out.print("*");
    }
    System.out.println();
}

// 例3:打印九九乘法表
// 1×1=1
// 1×2=2  2×2=4
// 1×3=3  2×3=6  3×3=9
// ...
for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= i; j++) {
        System.out.print(j + "×" + i + "=" + (i * j) + "\t");
    }
    System.out.println();
}

// 例4:打印等腰三角形
//     *
//    ***
//   *****
//  *******
// *********
int n = 5;
for (int i = 1; i <= n; i++) {
    // 打印空格
    for (int j = 1; j <= n - i; j++) {
        System.out.print(" ");
    }
    // 打印星号
    for (int j = 1; j <= 2 * i - 1; j++) {
        System.out.print("*");
    }
    System.out.println();
}

break 语句

break用于立即终止当前循环。

Java
// 例1:找到第一个能被7整除的数
for (int i = 1; i <= 100; i++) {
    if (i % 7 == 0) {
        System.out.println("第一个能被7整除的数:" + i);  // 7
        break;  // 找到后立即退出循环
    }
}

// 例2:在嵌套循环中,break只跳出当前层循环
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        if (j == 2) {
            break;  // 只跳出内层循环
        }
        System.out.println("i=" + i + ", j=" + j);
    }
}
// 输出:
// i=1, j=1
// i=2, j=1
// i=3, j=1

// 例3:使用标签跳出外层循环
outer:
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        if (j == 2) {
            break outer;  // 跳出外层循环
        }
        System.out.println("i=" + i + ", j=" + j);
    }
}
// 输出:i=1, j=1(只输出一行就跳出了整个嵌套循环)

continue 语句

continue用于跳过当次循环的剩余代码,进入下一次循环。

Java
// 例1:打印1~10中所有奇数
for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) {
        continue;  // 跳过偶数
    }
    System.out.println(i);  // 1, 3, 5, 7, 9
}

// 例2:跳过特定值
for (int i = 1; i <= 5; i++) {
    if (i == 3) {
        continue;  // 跳过3
    }
    System.out.println(i);  // 1, 2, 4, 5
}

// break vs continue 对比
System.out.println("=== break ===");
for (int i = 1; i <= 5; i++) {
    if (i == 3) break;
    System.out.print(i + " ");  // 1 2(遇到3直接终止循环)
}

System.out.println("\n=== continue ===");
for (int i = 1; i <= 5; i++) {
    if (i == 3) continue;
    System.out.print(i + " ");  // 1 2 4 5(跳过3,继续循环)
}

7. 程序调试

什么是程序调试

程序调试(Debug)是找出程序中错误(Bug)并修复的过程。

错误类型

text
程序错误
├── 语法错误(编译错误)
│   └── IDE和编译器会直接提示,如拼写错误、缺少分号
├── 运行时错误(异常)
│   └── 程序运行时才出现,如除以零、数组越界、空指针
└── 逻辑错误(最难发现)
    └── 程序能运行但结果不对,如算法错误

IDEA调试方法

1. 设置断点:

  • 在代码行号左侧点击,出现红色圆点即为断点
  • 程序运行到断点会暂停

2. 启动调试:

  • 点击Debug按钮(虫子图标)或按 Shift + F9

3. 调试操作:

text
F7  → Step Into(步入):进入方法内部
F8  → Step Over(步过):执行当前行,不进入方法内部
Shift + F8 → Step Out(步出):从当前方法跳出
F9  → Resume(恢复):继续执行到下一个断点

4. 调试窗口:

  • Variables(变量窗口):查看当前作用域内所有变量的值
  • Watches(监视窗口):添加表达式进行监控
  • Frames(调用栈):查看方法的调用层次
  • Console(控制台):查看程序输出
Java
// 调试示例:找出逻辑错误
public static int sumOfEven(int n) {
    int sum = 0;
    for (int i = 1; i <= n; i++) {  // 在这行设断点
        if (i % 2 == 0) {          // 观察i的值和条件判断
            sum += i;               // 观察sum的变化
        }
    }
    return sum;                     // 在这行设断点查看最终结果
}

5. 条件断点:

  • 右键点击断点,可以设置条件
  • 如:只有当 i == 50 时才暂停
  • 适用于循环中定位特定迭代

6. 常用调试技巧:

Java
// 技巧1:使用System.out.println输出关键变量值
System.out.println("DEBUG: i = " + i + ", sum = " + sum);

// 技巧2:使用断言(assert)
assert sum > 0 : "sum应该大于0";

// 技巧3:分段注释法
// 将代码分段注释,逐段放开,定位错误所在区域

第二阶段:Java面向对象基础 + 数组


1. 一维数组

什么是数组

数组是存储相同数据类型的固定长度的有序集合。

数组在内存中的存储:

text
栈内存                  堆内存
┌─────┐              ┌───┬───┬───┬───┬───┐
│ arr ├─────────────→│ 0 │ 0 │ 0 │ 0 │ 0 │
└─────┘              └───┴───┴───┴───┴───┘
                     [0]  [1]  [2]  [3]  [4]
  • 数组变量(arr)存储在栈中,保存的是数组在堆中的地址(引用)
  • 数组元素存储在堆中,是连续的内存空间
  • 通过索引(下标)访问元素,索引从0开始

数组的声明和创建

Java
// ============ 方式1:先声明后创建 ============
int[] arr;           // 声明(推荐写法)
int arr2[];          // 声明(C风格写法,不推荐)
arr = new int[5];    // 创建,分配5个int空间,默认值为0

// ============ 方式2:声明同时创建 ============
int[] arr3 = new int[5];

// ============ 方式3:声明并初始化 ============
int[] arr4 = new int[]{10, 20, 30, 40, 50};

// ============ 方式4:简化初始化 ============
int[] arr5 = {10, 20, 30, 40, 50};

// 各类型数组的默认值:
// int/short/byte/long → 0
// float/double → 0.0
// char → '\u0000'(空字符)
// boolean → false
// 引用类型 → null

数组的基本操作

Java
int[] arr = {10, 20, 30, 40, 50};

// 获取数组长度
System.out.println(arr.length);  // 5

// 访问元素(通过索引)
System.out.println(arr[0]);  // 10(第一个元素)
System.out.println(arr[4]);  // 50(最后一个元素)
// System.out.println(arr[5]);  // 错误!ArrayIndexOutOfBoundsException

// 修改元素
arr[2] = 100;  // 将第3个元素改为100

// 遍历数组
// 方式1:普通for循环
for (int i = 0; i < arr.length; i++) {
    System.out.println("arr[" + i + "] = " + arr[i]);
}

// 方式2:增强for循环(for-each)
for (int num : arr) {
    System.out.println(num);
}

数组的常见操作

Java
// ============ 求最大值 ============
int[] arr = {35, 12, 98, 45, 67};
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
    if (arr[i] > max) {
        max = arr[i];
    }
}
System.out.println("最大值:" + max);  // 98

// ============ 求和与平均值 ============
int sum = 0;
for (int num : arr) {
    sum += num;
}
double avg = (double) sum / arr.length;

// ============ 数组排序(冒泡排序) ============
int[] arr2 = {5, 3, 8, 1, 9, 2};
for (int i = 0; i < arr2.length - 1; i++) {
    for (int j = 0; j < arr2.length - 1 - i; j++) {
        if (arr2[j] > arr2[j + 1]) {
            int temp = arr2[j];
            arr2[j] = arr2[j + 1];
            arr2[j + 1] = temp;
        }
    }
}
// 排序后:{1, 2, 3, 5, 8, 9}

// ============ 使用Arrays工具类 ============
import java.util.Arrays;

Arrays.sort(arr);                        // 排序
System.out.println(Arrays.toString(arr)); // 输出为字符串
int index = Arrays.binarySearch(arr, 45); // 二分查找(需先排序)
int[] copy = Arrays.copyOf(arr, 10);     // 复制数组
Arrays.fill(arr, 0);                     // 填充所有元素为0

2. 二维数组

什么是二维数组

二维数组可以看作是"数组的数组",像一个表格(行和列)。

text
内存模型:
arr → [ 引用1, 引用2, 引用3 ]
         ↓        ↓        ↓
      [1,2,3]  [4,5,6]  [7,8,9]

声明和使用

Java
// ============ 声明方式 ============
// 方式1:指定行数和列数
int[][] arr = new int[3][4];  // 3行4列

// 方式2:只指定行数(列数可以不同——锯齿数组)
int[][] arr2 = new int[3][];
arr2[0] = new int[3];  // 第一行3列
arr2[1] = new int[5];  // 第二行5列
arr2[2] = new int[2];  // 第三行2列

// 方式3:直接初始化
int[][] arr3 = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// ============ 访问元素 ============
System.out.println(arr3[0][0]);  // 1(第1行第1列)
System.out.println(arr3[1][2]);  // 6(第2行第3列)
System.out.println(arr3[2][1]);  // 8(第3行第2列)

// 修改元素
arr3[1][1] = 100;

// ============ 遍历二维数组 ============
for (int i = 0; i < arr3.length; i++) {           // 遍历行
    for (int j = 0; j < arr3[i].length; j++) {    // 遍历列
        System.out.print(arr3[i][j] + "\t");
    }
    System.out.println();
}

// 增强for循环版
for (int[] row : arr3) {
    for (int num : row) {
        System.out.print(num + "\t");
    }
    System.out.println();
}

二维数组应用

Java
// 例1:求所有元素的和
int[][] scores = {
    {80, 90, 75},   // 学生1的三科成绩
    {85, 70, 95},   // 学生2
    {90, 88, 92}    // 学生3
};
int total = 0;
for (int[] row : scores) {
    for (int score : row) {
        total += score;
    }
}
System.out.println("总分:" + total);

// 例2:矩阵转置
int[][] matrix = {{1, 2, 3}, {4, 5, 6}};  // 2×3
int[][] transposed = new int[3][2];         // 3×2
for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        transposed[j][i] = matrix[i][j];
    }
}

3. 类和对象基础:方法、局部变量、成员变量、类变量

面向对象编程(OOP)概念

text
面向过程编程:关注解决问题的步骤
  → 第一步做什么,第二步做什么...

面向对象编程:关注解决问题的角色(对象)
  → 谁来做什么事

类(Class):是对象的模板/蓝图,定义了对象的属性和行为。
对象(Object):是类的实例,是实际存在的个体。

text
类比:
类   → 房屋设计图纸
对象 → 根据图纸建造的实际房屋(可以建多个)

定义类和创建对象

Java
// ============ 定义类 ============
public class Student {
    // 成员变量(属性/字段)—— 描述对象的特征
    String name;
    int age;
    String gender;
    
    // 成员方法(行为)—— 描述对象能做什么
    public void study() {
        System.out.println(name + "正在学习Java");
    }
    
    public void introduce() {
        System.out.println("我叫" + name + ",今年" + age + "岁,性别" + gender);
    }
}

// ============ 创建对象 ============
public class TestStudent {
    public static void main(String[] args) {
        // 创建对象:类名 对象名 = new 类名();
        Student stu1 = new Student();
        
        // 设置属性
        stu1.name = "张三";
        stu1.age = 20;
        stu1.gender = "男";
        
        // 调用方法
        stu1.study();       // 张三正在学习Java
        stu1.introduce();   // 我叫张三,今年20岁,性别男
        
        // 可以创建多个对象
        Student stu2 = new Student();
        stu2.name = "李四";
        stu2.age = 22;
    }
}

对象在内存中的存储:

text
栈内存                 堆内存
┌──────┐            ┌──────────────────┐
│ stu1 ├───────────→│ name = "张三"     │
└──────┘            │ age = 20          │
                    │ gender = "男"      │
┌──────┐            └──────────────────┘
│ stu2 ├───────────→┌──────────────────┐
└──────┘            │ name = "李四"     │
                    │ age = 22          │
                    │ gender = null     │
                    └──────────────────┘

方法

Java
// 方法的完整定义
访问修饰符 返回值类型 方法名(参数类型1 参数名1, 参数类型2 参数名2, ...) {
    方法体;
    return 返回值;  // 如果返回值类型是void,可以省略return
}

// ============ 无参无返回值 ============
public void sayHello() {
    System.out.println("Hello!");
}

// ============ 有参无返回值 ============
public void greet(String name) {
    System.out.println("Hello, " + name + "!");
}

// ============ 无参有返回值 ============
public int getAge() {
    return this.age;
}

// ============ 有参有返回值 ============
public int add(int a, int b) {
    return a + b;
}

// ============ 方法调用 ============
Student stu = new Student();
stu.sayHello();                  // 无参无返回
stu.greet("张三");               // 有参无返回
int age = stu.getAge();          // 无参有返回
int result = stu.add(10, 20);   // 有参有返回

构造方法

Java
public class Student {
    String name;
    int age;
    
    // 无参构造方法(如果不写任何构造方法,系统默认提供)
    public Student() {
        System.out.println("创建了一个Student对象");
    }
    
    // 有参构造方法
    public Student(String name, int age) {
        this.name = name;  // this指当前对象
        this.age = age;
    }
    
    // 注意:如果定义了有参构造,系统不再提供无参构造
    // 建议:总是同时提供无参和有参构造
}

// 使用
Student stu1 = new Student();           // 调用无参构造
Student stu2 = new Student("张三", 20); // 调用有参构造

变量的分类

Java
public class VariableDemo {
    // ============ 成员变量(实例变量) ============
    // 定义在类中,方法外
    // 属于对象,通过对象访问
    // 有默认值(int=0, String=null, boolean=false)
    // 生命周期:随对象创建而创建,随对象销毁而销毁
    String name;
    int age;
    
    // ============ 类变量(静态变量) ============
    // 使用static修饰
    // 属于类,通过类名访问(也可以通过对象访问)
    // 所有对象共享同一个值
    // 生命周期:随类的加载而创建
    static int count = 0;  // 记录创建了多少个对象
    static String schoolName = "清华大学";
    
    public VariableDemo() {
        count++;  // 每创建一个对象,计数器+1
    }
    
    public void method() {
        // ============ 局部变量 ============
        // 定义在方法内部(或代码块内)
        // 没有默认值,必须先赋值再使用
        // 生命周期:方法执行时创建,方法结束时销毁
        int localVar = 10;
        System.out.println(localVar);
        
        // 如果局部变量和成员变量同名,局部变量优先(就近原则)
        String name = "局部的name";
        System.out.println(name);       // 局部的name
        System.out.println(this.name);  // 成员的name
    }
}

// 使用类变量
System.out.println(VariableDemo.schoolName);  // 通过类名访问
System.out.println(VariableDemo.count);        // 查看创建了多少个对象

三种变量对比:

特性局部变量成员变量类变量(static)
定义位置 方法内 类中,方法外 类中,方法外,加static
默认值 无,必须初始化 有默认值 有默认值
作用范围 方法内 整个类 整个类,所有对象共享
内存位置 堆(对象中) 方法区(静态区)
生命周期 方法开始到结束 对象创建到销毁 类加载到卸载
访问方式 直接使用 通过对象 通过类名(推荐)

4. String类

String的特点

Java
// String是引用数据类型,但使用起来像基本类型
// String对象是不可变的(immutable)—— 一旦创建,内容不能改变
// String存储在字符串常量池中(方法区)

// ============ 创建String的两种方式 ============

// 方式1:字面量方式(推荐)
// 会先检查常量池中是否已有该字符串,有则直接引用,没有则创建
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2);  // true(指向常量池中同一个对象)

// 方式2:new方式
// 会在堆中创建新对象,即使常量池中已有相同字符串
String s3 = new String("Hello");
String s4 = new String("Hello");
System.out.println(s3 == s4);       // false(堆中两个不同对象)
System.out.println(s3.equals(s4));  // true(内容相同)

内存模型:

text
栈内存          堆内存                常量池(方法区)
┌────┐                              ┌─────────┐
│ s1 ├─────────────────────────────→│ "Hello" │
├────┤                              └─────────┘
│ s2 ├─────────────────────────────→  ↑ (同一个)
├────┤        ┌─────────────┐
│ s3 ├───────→│ String对象1  ├────→  "Hello"
├────┤        └─────────────┘
│ s4 ├───────→┌─────────────┐
│    │        │ String对象2  ├────→  "Hello"
└────┘        └─────────────┘

String常用方法

Java
String str = "Hello, World!";

// ============ 获取信息 ============
str.length()                    // 13(字符串长度)
str.charAt(0)                   // 'H'(获取指定索引的字符)
str.indexOf("World")            // 7(查找子串首次出现的位置)
str.lastIndexOf("l")            // 10(最后一次出现的位置)
str.isEmpty()                   // false(是否为空字符串)

// ============ 判断 ============
str.equals("Hello, World!")     // true(比较内容是否相等)
str.equalsIgnoreCase("HELLO, WORLD!")  // true(忽略大小写比较)
str.contains("World")           // true(是否包含指定子串)
str.startsWith("Hello")         // true(是否以指定前缀开头)
str.endsWith("!")                // true(是否以指定后缀结尾)

// ============ 转换 ============
str.toUpperCase()                // "HELLO, WORLD!"
str.toLowerCase()                // "hello, world!"
str.trim()                       // 去除首尾空格
str.replace("World", "Java")    // "Hello, Java!"
str.substring(7)                 // "World!"(从索引7到末尾)
str.substring(7, 12)             // "World"(从索引7到11)

// ============ 分割 ============
String csv = "张三,李四,王五";
String[] names = csv.split(",");  // ["张三", "李四", "王五"]

// ============ 拼接 ============
String s = "Hello" + " " + "World";  // 用+号拼接
String.join(",", "a", "b", "c");     // "a,b,c"

// ============ 转换与值 ============
String.valueOf(100)              // "100"(int转String)
Integer.parseInt("100")          // 100(String转int)
str.toCharArray()                // 转为字符数组

// ============ 重要:== 和 equals 的区别 ============
// == 比较的是引用(地址),equals比较的是内容
String a = "abc";
String b = "abc";
String c = new String("abc");

a == b         // true(常量池中同一个对象)
a == c         // false(一个在常量池,一个在堆中)
a.equals(c)    // true(内容相同)

String不可变性

Java
// String对象一旦创建,其内容不能被修改
// 所有看似"修改"的操作,实际上都是创建了新的String对象

String s = "Hello";
s = s + " World";  // 不是修改了原来的"Hello",而是创建了新的"Hello World"
// 原来的"Hello"在常量池中仍然存在,只是s不再指向它了

// 这就是为什么大量字符串拼接时不推荐用String + 的方式
// 因为会创建大量临时对象,影响性能
String result = "";
for (int i = 0; i < 10000; i++) {
    result += i;  // 每次都创建新对象,非常低效!
}
// 应该使用StringBuilder或StringBuffer

5. StringBuffer类

StringBuffer的特点

Java
// StringBuffer是可变的字符序列
// 线程安全(方法使用synchronized修饰)
// 适合在多线程环境中使用

// StringBuilder是StringBuffer的非线程安全版本
// 性能更高,适合在单线程环境中使用

// 性能对比:StringBuilder > StringBuffer >> String(大量拼接时)

StringBuffer常用方法

Java
// ============ 创建 ============
StringBuffer sb = new StringBuffer();          // 空的,默认容量16
StringBuffer sb2 = new StringBuffer("Hello");  // 初始内容"Hello"
StringBuffer sb3 = new StringBuffer(100);      // 指定初始容量

// ============ 增(追加) ============
sb.append("Hello");
sb.append(" ");
sb.append("World");
sb.append(100);          // 可以追加各种类型
System.out.println(sb);  // "Hello World100"

// 链式调用
sb.append("a").append("b").append("c");

// ============ 删 ============
sb.delete(5, 11);         // 删除索引5到10的字符
sb.deleteCharAt(0);       // 删除指定索引的字符

// ============ 改(修改/替换) ============
sb.replace(0, 5, "Java"); // 将索引0到4的内容替换为"Java"
sb.setCharAt(0, 'j');      // 修改指定索引的字符

// ============ 插入 ============
sb.insert(5, "Beautiful "); // 在索引5处插入

// ============ 反转 ============
sb.reverse();              // 反转字符串

// ============ 其他 ============
sb.length()                // 长度
sb.capacity()              // 容量
sb.toString()              // 转为String

// ============ 与String的转换 ============
// String → StringBuffer
String str = "Hello";
StringBuffer sbuf = new StringBuffer(str);

// StringBuffer → String
String result = sbuf.toString();

String、StringBuffer、StringBuilder对比

特性StringStringBufferStringBuilder
可变性 不可变 可变 可变
线程安全 安全(不可变) 安全(synchronized) 不安全
性能 拼接慢 中等 最快
使用场景 少量字符串操作 多线程大量拼接 单线程大量拼接
Java
// 性能对比演示
long start = System.currentTimeMillis();

// String方式(非常慢)
String s = "";
for (int i = 0; i < 100000; i++) {
    s += i;
}
System.out.println("String耗时:" + (System.currentTimeMillis() - start) + "ms");

start = System.currentTimeMillis();

// StringBuilder方式(很快)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
    sb.append(i);
}
String result = sb.toString();
System.out.println("StringBuilder耗时:" + (System.currentTimeMillis() - start) + "ms");

6. 方法传参(多参)返回值

方法参数

Java
// ============ 单个参数 ============
public void greet(String name) {
    System.out.println("Hello, " + name);
}

// ============ 多个参数 ============
public int add(int a, int b) {
    return a + b;
}

// ============ 可变参数(不定参数) ============
// 使用...表示,本质是数组
// 必须是方法的最后一个参数
// 一个方法只能有一个可变参数
public int sum(int... numbers) {
    int total = 0;
    for (int num : numbers) {
        total += num;
    }
    return total;
}

// 调用
sum(1, 2);              // 3
sum(1, 2, 3);           // 6
sum(1, 2, 3, 4, 5);     // 15

值传递 vs 引用传递

Java只有值传递!

Java
// ============ 基本数据类型:传递的是值的副本 ============
public static void change(int num) {
    num = 100;  // 只改变了副本,不影响原始变量
}

int a = 10;
change(a);
System.out.println(a);  // 10(没有改变)

// ============ 引用数据类型:传递的是地址的副本 ============
public static void change(int[] arr) {
    arr[0] = 100;  // 通过地址副本找到同一个数组,修改了数组内容
}

int[] myArr = {1, 2, 3};
change(myArr);
System.out.println(myArr[0]);  // 100(改变了!)

// ============ String特殊情况 ============
public static void change(String s) {
    s = "World";  // s指向了新的String对象,原来的s没有受影响
}

String str = "Hello";
change(str);
System.out.println(str);  // "Hello"(没有改变!因为String不可变)
text
基本类型传参内存图:
    main方法栈          change方法栈
    ┌───────┐          ┌───────┐
    │ a = 10│          │num=100│  ← 改的是副本
    └───────┘          └───────┘

引用类型传参内存图:
    main方法栈          change方法栈       堆内存
    ┌──────┐           ┌──────┐         ┌───────────┐
    │myArr ├──────┐    │ arr  ├────────→│{100, 2, 3}│
    └──────┘      └───────────────────→ └───────────┘
                        两个引用指向同一个数组

返回值

Java
// ============ 无返回值:void ============
public void printInfo() {
    System.out.println("Hello");
    // 可以使用return;提前结束方法(但不返回值)
    return;  // 可以省略
}

// ============ 有返回值 ============
public int getMax(int a, int b) {
    return (a > b) ? a : b;
}

// ============ 返回数组 ============
public int[] getScores() {
    int[] scores = {90, 85, 78};
    return scores;
}

// ============ 返回对象 ============
public Student createStudent(String name, int age) {
    Student stu = new Student();
    stu.name = name;
    stu.age = age;
    return stu;
}

// 注意:方法只能返回一个值
// 如果需要返回多个值,可以:
// 1. 返回数组
// 2. 返回对象
// 3. 使用集合

第三阶段:HTML基础


1. 常用标签

HTML概述

text
HTML(HyperText Markup Language):超文本标记语言
- 不是编程语言,是标记语言
- 用于构建网页的结构和内容
- 浏览器解析HTML文件并渲染页面

基本结构

HTML
<!DOCTYPE html>     <!-- 声明文档类型为HTML5 -->
<html lang="zh-CN"> <!-- 根元素 -->
<head>              <!-- 头部:元数据 -->
    <meta charset="UTF-8">    <!-- 字符编码 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>网页标题</title>     <!-- 浏览器标签页显示的标题 -->
</head>
<body>              <!-- 主体:页面内容 -->
    <h1>这是一个网页</h1>
</body>
</html>

常用标签详解

HTML
<!-- ============ 标题标签 h1~h6 ============ -->
<h1>一级标题(最大)</h1>
<h2>二级标题</h2>
<h3>三级标题</h3>
<h4>四级标题</h4>
<h5>五级标题</h5>
<h6>六级标题(最小)</h6>

<!-- ============ 段落和换行 ============ -->
<p>这是一个段落。段落之间有间距。</p>
<p>这是另一个段落。</p>
<br>  <!-- 换行(单标签) -->
<hr>  <!-- 水平分割线(单标签) -->

<!-- ============ 文本格式化 ============ -->
<b>加粗</b>  或  <strong>加粗(语义更强)</strong>
<i>斜体</i>  或  <em>斜体(强调)</em>
<u>下划线</u>
<del>删除线</del>  或  <s>删除线</s>
<sup>上标</sup>  如:x<sup>2</sup>
<sub>下标</sub>  如:H<sub>2</sub>O

<!-- ============ 容器标签 ============ -->
<div>块级容器(独占一行)</div>
<span>行内容器(不换行)</span>

<!-- ============ 图片标签 ============ -->
<img src="图片路径" alt="图片描述" width="200" height="150">
<!-- src: 图片源路径 -->
<!-- alt: 图片加载失败时显示的文字,也用于无障碍 -->
<!-- width/height: 宽高(可以用像素或百分比) -->

<!-- ============ 特殊字符(实体) ============ -->
&lt;     <!-- < 小于号 -->
&gt;     <!-- > 大于号 -->
&amp;    <!-- & 和号 -->
&nbsp;   <!-- 空格 -->
&copy;   <!-- © 版权 -->
&quot;   <!-- " 引号 -->

块级元素 vs 行内元素:

类型特点示例
块级元素 独占一行,可设宽高 div, h1-h6, p, ul, ol, table
行内元素 不换行,宽高由内容决定 span, a, b, i, img, input

2. 超链接

HTML
<!-- ============ 基本超链接 ============ -->
<a href="https://www.baidu.com">百度一下</a>

<!-- ============ target属性 ============ -->
<a href="page2.html" target="_self">在当前窗口打开(默认)</a>
<a href="page2.html" target="_blank">在新窗口打开</a>
<a href="page2.html" target="_parent">在父框架打开</a>
<a href="page2.html" target="_top">在顶层框架打开</a>

<!-- ============ 锚点链接(页面内跳转) ============ -->
<!-- 定义锚点 -->
<h2 id="chapter1">第一章</h2>
<p>这是第一章的内容...</p>
<h2 id="chapter2">第二章</h2>
<p>这是第二章的内容...</p>

<!-- 跳转到锚点 -->
<a href="#chapter1">跳转到第一章</a>
<a href="#chapter2">跳转到第二章</a>
<a href="#">回到顶部</a>

<!-- ============ 链接到邮箱/电话 ============ -->
<a href="mailto:example@email.com">发送邮件</a>
<a href="tel:13800138000">拨打电话</a>

<!-- ============ 下载链接 ============ -->
<a href="file.zip" download>下载文件</a>
<a href="image.png" download="新文件名.png">下载图片</a>

<!-- ============ 图片链接 ============ -->
<a href="https://www.baidu.com">
    <img src="logo.png" alt="百度Logo">
</a>

<!-- ============ 空链接(占位) ============ -->
<a href="#">链接1</a>
<a href="javascript:void(0)">链接2</a>
<a href="javascript:;">链接3</a>

3. 列表、表格

列表

HTML
<!-- ============ 无序列表 (ul) ============ -->
<ul>
    <li>苹果</li>
    <li>香蕉</li>
    <li>橘子</li>
</ul>
<!-- 显示效果:
  • 苹果
  • 香蕉
  • 橘子
-->

<!-- type属性:disc(实心圆)、circle(空心圆)、square(方块) -->
<ul type="circle">
    <li>项目1</li>
    <li>项目2</li>
</ul>

<!-- ============ 有序列表 (ol) ============ -->
<ol>
    <li>第一步</li>
    <li>第二步</li>
    <li>第三步</li>
</ol>
<!-- 显示效果:
  1. 第一步
  2. 第二步
  3. 第三步
-->

<!-- type属性:1(数字)、a(小写字母)、A(大写字母)、i(小写罗马)、I(大写罗马) -->
<ol type="A" start="3">  <!-- 从C开始 -->
    <li>项目C</li>
    <li>项目D</li>
</ol>

<!-- ============ 定义列表 (dl) ============ -->
<dl>
    <dt>HTML</dt>  <!-- 定义术语 -->
    <dd>超文本标记语言</dd>  <!-- 术语描述 -->
    <dt>CSS</dt>
    <dd>层叠样式表</dd>
    <dt>JavaScript</dt>
    <dd>脚本编程语言</dd>
</dl>

<!-- ============ 嵌套列表 ============ -->
<ul>
    <li>前端技术
        <ul>
            <li>HTML</li>
            <li>CSS</li>
            <li>JavaScript</li>
        </ul>
    </li>
    <li>后端技术
        <ul>
            <li>Java</li>
            <li>Python</li>
        </ul>
    </li>
</ul>

表格

HTML
<!-- ============ 基本表格 ============ -->
<table border="1" cellspacing="0" cellpadding="5" width="500">
    <!-- border: 边框粗细 -->
    <!-- cellspacing: 单元格之间的间距 -->
    <!-- cellpadding: 单元格内容与边框的距离 -->
    
    <caption>学生成绩表</caption>  <!-- 表格标题 -->
    
    <thead>  <!-- 表头区域 -->
        <tr>  <!-- 一行 -->
            <th>姓名</th>  <!-- 表头单元格(加粗居中) -->
            <th>语文</th>
            <th>数学</th>
            <th>英语</th>
        </tr>
    </thead>
    
    <tbody>  <!-- 表体区域 -->
        <tr>
            <td>张三</td>  <!-- 普通单元格 -->
            <td>85</td>
            <td>92</td>
            <td>78</td>
        </tr>
        <tr>
            <td>李四</td>
            <td>90</td>
            <td>88</td>
            <td>95</td>
        </tr>
    </tbody>
    
    <tfoot>  <!-- 表尾区域 -->
        <tr>
            <td>平均</td>
            <td>87.5</td>
            <td>90</td>
            <td>86.5</td>
        </tr>
    </tfoot>
</table>

<!-- ============ 合并单元格 ============ -->
<table border="1">
    <tr>
        <td colspan="3">跨3列合并(水平合并)</td>
        <!-- colspan: 向右合并n列 -->
    </tr>
    <tr>
        <td rowspan="2">跨2行合并(垂直合并)</td>
        <!-- rowspan: 向下合并n行 -->
        <td>B</td>
        <td>C</td>
    </tr>
    <tr>
        <!-- 这里少写一个td,因为上面的rowspan已经占了位置 -->
        <td>E</td>
        <td>F</td>
    </tr>
</table>

4. 内联框架、框架集

内联框架(iframe)

HTML
<!-- iframe:在当前页面中嵌入另一个页面 -->
<iframe src="https://www.baidu.com" width="800" height="600" 
        frameborder="0" scrolling="auto" name="myFrame">
    您的浏览器不支持iframe
</iframe>

<!-- 常用属性:
    src        : 嵌入的页面URL
    width      : 宽度
    height     : 高度
    frameborder: 边框(0无/1有)
    scrolling  : 滚动条(auto/yes/no)
    name       : 框架名称(配合target使用)
-->

<!-- 配合超链接使用 -->
<a href="page1.html" target="myFrame">页面1</a>
<a href="page2.html" target="myFrame">页面2</a>
<iframe name="myFrame" width="800" height="600"></iframe>
<!-- 点击链接,内容在iframe中显示 -->

<!-- 实际应用:嵌入地图、视频 -->
<iframe src="https://www.youtube.com/embed/xxxxx" allowfullscreen></iframe>

框架集(frameset)— HTML5已废弃

HTML
<!-- 框架集将浏览器窗口分成多个区域,每个区域显示不同页面 -->
<!-- 注意:frameset不能和body共存 -->

<!DOCTYPE html>
<html>
<head><title>框架集</title></head>

<!-- 上下分割 -->
<frameset rows="100px, *">
    <frame src="header.html" name="top" noresize>
    <frame src="content.html" name="main">
</frameset>

<!-- 左右分割 -->
<frameset cols="200px, *">
    <frame src="menu.html" name="left">
    <frame src="content.html" name="right">
</frameset>

<!-- 嵌套分割 -->
<frameset rows="100px, *">
    <frame src="header.html">
    <frameset cols="200px, *">
        <frame src="menu.html" name="left">
        <frame src="content.html" name="right">
    </frameset>
</frameset>

<!-- 注意:框架集在现代开发中已很少使用 -->
<!-- 替代方案:CSS布局 + iframe / SPA单页应用 -->
</html>

第四阶段:Java面向对象进阶


1. 封装

什么是封装

封装是面向对象三大特征之一(封装、继承、多态),指将对象的属性和行为隐藏在对象内部,只对外提供访问接口。

text
封装的好处:
1. 隐藏实现细节,只暴露安全的接口
2. 保护数据的完整性(通过验证)
3. 提高代码的可维护性
4. 降低耦合度

实现封装

Java
// ============ 封装前(不安全) ============
public class Student {
    String name;
    int age;
}

Student stu = new Student();
stu.age = -100;  // 不合理的值也能设置!没有任何限制

// ============ 封装后(安全) ============
public class Student {
    // 1. 将属性设为private(私有),外部无法直接访问
    private String name;
    private int age;
    
    // 2. 提供public的getter和setter方法,作为外部访问的接口
    
    // getter:获取属性值
    public String getName() {
        return name;
    }
    
    // setter:设置属性值(可以添加验证逻辑)
    public void setName(String name) {
        if (name != null && !name.isEmpty()) {
            this.name = name;
        } else {
            System.out.println("姓名不能为空");
        }
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            System.out.println("年龄不合理");
        }
    }
}

// 使用封装后的类
Student stu = new Student();
stu.setName("张三");
stu.setAge(20);
// stu.setAge(-100);  // 会提示"年龄不合理"
System.out.println(stu.getName());  // 张三
System.out.println(stu.getAge());   // 20

访问修饰符

修饰符同一类同一包子类不同包
private
默认(不写)
protected
public
Java
public class Person {
    public String name;       // 任何地方都能访问
    protected int age;        // 同包 + 子类可访问
    String gender;            // 默认:同包可访问
    private double salary;    // 只有本类可访问
}

this关键字

Java
public class Student {
    private String name;
    private int age;
    
    // this指当前对象
    public void setName(String name) {
        this.name = name;  // this.name是成员变量,name是参数
    }
    
    // this调用构造方法
    public Student() {
        this("未知", 0);  // 调用有参构造,必须是构造方法第一行
    }
    
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // this作为返回值(链式调用)
    public Student setNameChain(String name) {
        this.name = name;
        return this;
    }
    
    public Student setAgeChain(int age) {
        this.age = age;
        return this;
    }
}

// 链式调用
new Student().setNameChain("张三").setAgeChain(20);

2. 方法重载

什么是方法重载(Overload)

在同一个类中,方法名相同,但参数列表不同(参数个数、类型、顺序不同)。

Java
public class Calculator {
    
    // 方法重载:同名不同参
    
    // 两个int相加
    public int add(int a, int b) {
        return a + b;
    }
    
    // 三个int相加(参数个数不同)
    public int add(int a, int b, int c) {
        return a + b + c;
    }
    
    // 两个double相加(参数类型不同)
    public double add(double a, double b) {
        return a + b;
    }
    
    // int和double相加(参数类型顺序不同)
    public double add(int a, double b) {
        return a + b;
    }
    
    public double add(double a, int b) {
        return a + b;
    }
}

// 使用
Calculator calc = new Calculator();
calc.add(10, 20);        // 调用 add(int, int) → 30
calc.add(10, 20, 30);    // 调用 add(int, int, int) → 60
calc.add(1.5, 2.5);      // 调用 add(double, double) → 4.0
calc.add(10, 1.5);        // 调用 add(int, double) → 11.5

重载的规则:

text
✅ 构成重载的条件:
   - 方法名相同
   - 参数列表不同(个数、类型、顺序任一不同)

❌ 不构成重载(编译错误):
   - 仅返回值类型不同
   - 仅访问修饰符不同
   - 仅参数名不同
Java
// 以下不构成重载:
public int add(int a, int b) { return a + b; }
// public double add(int a, int b) { return a + b; }  // 错误!仅返回值不同
// public int add(int x, int y) { return x + y; }     // 错误!仅参数名不同

常见的重载例子:

Java
// System.out.println就是重载的典型
System.out.println(100);        // println(int)
System.out.println(3.14);       // println(double)
System.out.println("Hello");    // println(String)
System.out.println(true);       // println(boolean)
System.out.println('A');        // println(char)

3. 继承

什么是继承

继承是面向对象的第二大特征。子类继承父类的属性和方法,实现代码复用。

text
继承的概念:
  动物(父类/超类)
  ├── 狗(子类):继承了动物的特征,还有自己特有的
  ├── 猫(子类):继承了动物的特征,还有自己特有的
  └── 鸟(子类):继承了动物的特征,还有自己特有的

实现继承

Java
// ============ 父类(基类/超类) ============
public class Animal {
    private String name;
    private int age;
    
    public Animal() {}
    
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public void eat() {
        System.out.println(name + "在吃东西");
    }
    
    public void sleep() {
        System.out.println(name + "在睡觉");
    }
    
    // getter/setter省略
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

// ============ 子类 使用extends关键字继承 ============
public class Dog extends Animal {
    private String breed;  // 子类特有属性:品种
    
    public Dog() {}
    
    public Dog(String name, int age, String breed) {
        super(name, age);  // 调用父类构造方法
        this.breed = breed;
    }
    
    // 子类特有方法
    public void bark() {
        System.out.println(getName() + "在汪汪叫");
    }
}

public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }
    
    public void catchMouse() {
        System.out.println(getName() + "在抓老鼠");
    }
}

// ============ 使用 ============
Dog dog = new Dog("旺财", 3, "金毛");
dog.eat();       // 继承的方法:旺财在吃东西
dog.sleep();     // 继承的方法:旺财在睡觉
dog.bark();      // 特有的方法:旺财在汪汪叫

Cat cat = new Cat("咪咪", 2);
cat.eat();         // 继承的方法
cat.catchMouse();  // 特有的方法

继承的特点

Java
// 1. Java是单继承(一个类只能有一个直接父类)
public class A {}
public class B extends A {}
// public class C extends A, B {}  // 错误!不能多继承

// 2. 支持多层继承
public class Animal {}
public class Dog extends Animal {}
public class GoldenRetriever extends Dog {}  // 金毛 → 狗 → 动物

// 3. 所有类都直接或间接继承自Object类
// Object是所有类的根类

// 4. 子类不能继承父类的:
//    - private修饰的属性和方法
//    - 构造方法(但可以通过super调用)

// 5. super关键字
public class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);    // 调用父类构造方法,必须是第一行
    }
    
    public void show() {
        super.eat();         // 调用父类的方法
        // super.name;       // 如果name是private,不能直接访问
        System.out.println(super.getName());  // 可以通过getter访问
    }
}

// 6. 构造方法的调用顺序
// 创建子类对象时,先执行父类构造,再执行子类构造
// 如果不写super(),系统会自动调用父类无参构造

4. 多态(方法重写)

方法重写(Override)

子类重新定义父类中已有的方法,方法名、参数列表、返回值类型都相同。

Java
// 父类
public class Animal {
    public void eat() {
        System.out.println("动物在吃东西");
    }
    
    public void makeSound() {
        System.out.println("动物在叫");
    }
}

// 子类重写父类方法
public class Dog extends Animal {
    @Override  // 注解:标识这是重写的方法,编译器会检查
    public void eat() {
        System.out.println("狗在啃骨头");  // 重写了父类的eat方法
    }
    
    @Override
    public void makeSound() {
        System.out.println("汪汪汪!");  // 重写了父类的makeSound方法
    }
}

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫在吃鱼");
    }
    
    @Override
    public void makeSound() {
        System.out.println("喵喵喵~");
    }
}

重写的规则:

text
1. 方法名、参数列表必须相同
2. 返回值类型相同(或子类类型)
3. 访问权限不能比父类更严格(可以更宽松)
4. 不能重写private方法(因为子类看不到)
5. 不能重写static方法
6. 不能重写final方法

重载(Overload) vs 重写(Override):

特性重载重写
位置 同一个类中 子类和父类之间
方法名 相同 相同
参数列表 必须不同 必须相同
返回值 无要求 相同或子类型
访问权限 无要求 不能更严格

多态

多态是面向对象的第三大特征。同一个方法调用,根据对象的不同,产生不同的行为。

Java
// ============ 多态的前提条件 ============
// 1. 有继承/实现关系
// 2. 有方法重写
// 3. 父类引用指向子类对象

// ============ 多态的核心:父类引用指向子类对象 ============
Animal animal1 = new Dog();   // 编译类型是Animal,运行类型是Dog
Animal animal2 = new Cat();   // 编译类型是Animal,运行类型是Cat

animal1.eat();       // 狗在啃骨头(运行时调用Dog的eat方法)
animal2.eat();       // 猫在吃鱼(运行时调用Cat的eat方法)
animal1.makeSound(); // 汪汪汪!
animal2.makeSound(); // 喵喵喵~

// ============ 多态的实际应用 ============
// 方法参数使用父类类型,可以接收任何子类对象
public class AnimalShow {
    // 不使用多态:需要为每种动物写一个方法
    // public void showEat(Dog dog) { dog.eat(); }
    // public void showEat(Cat cat) { cat.eat(); }
    
    // 使用多态:一个方法搞定
    public void showEat(Animal animal) {
        animal.eat();  // 根据传入的实际对象调用对应的eat方法
    }
}

AnimalShow show = new AnimalShow();
show.showEat(new Dog());  // 狗在啃骨头
show.showEat(new Cat());  // 猫在吃鱼

// ============ 向上转型和向下转型 ============
// 向上转型(自动):子类 → 父类
Animal a = new Dog();  // 自动转型
// a.bark();  // 错误!编译类型是Animal,没有bark方法

// 向下转型(强制):父类 → 子类(需要instanceof判断)
if (a instanceof Dog) {
    Dog dog = (Dog) a;  // 强制转型
    dog.bark();  // 现在可以调用Dog特有的方法了
}

多态的内存原理:

text
编译时:看左边(Animal类型),决定能调用哪些方法
运行时:看右边(Dog对象),决定实际执行哪个方法

编译时检查:Animal类有eat()方法吗? → 有,通过编译
运行时执行:实际对象是Dog → 执行Dog的eat()

5. 抽象类、接口

抽象类

抽象类是不能被实例化的类,用于定义规范,由子类来实现。

Java
// 使用abstract关键字定义抽象类
public abstract class Shape {
    private String color;
    
    public Shape(String color) {
        this.color = color;
    }
    
    // 抽象方法:只有方法签名,没有方法体
    // 子类必须重写所有抽象方法
    public abstract double getArea();
    public abstract double getPerimeter();
    
    // 普通方法:抽象类中也可以有普通方法
    public void show() {
        System.out.println("颜色:" + color + ",面积:" + getArea());
    }
    
    public String getColor() { return color; }
}

// 子类必须实现所有抽象方法(除非子类也是抽象类)
public class Circle extends Shape {
    private double radius;
    
    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }
    
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public double getPerimeter() {
        return 2 * Math.PI * radius;
    }
}

public class Rectangle extends Shape {
    private double width, height;
    
    public Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double getArea() {
        return width * height;
    }
    
    @Override
    public double getPerimeter() {
        return 2 * (width + height);
    }
}

// 使用(多态)
Shape shape1 = new Circle("红色", 5);
Shape shape2 = new Rectangle("蓝色", 4, 6);
shape1.show();  // 颜色:红色,面积:78.54
shape2.show();  // 颜色:蓝色,面积:24.0

// Shape shape = new Shape("绿色");  // 错误!抽象类不能实例化

抽象类的特点:

text
1. 使用abstract修饰
2. 不能被实例化(不能new)
3. 可以有抽象方法(也可以没有)
4. 可以有普通方法、构造方法、成员变量
5. 子类必须重写所有抽象方法(除非子类也是抽象类)
6. 抽象方法不能是private、static、final

接口

接口是完全抽象的"规范",定义了一组行为标准。

Java
// 使用interface关键字定义接口
public interface Flyable {
    // 接口中的变量默认是 public static final
    int MAX_HEIGHT = 10000;  // 常量
    
    // 接口中的方法默认是 public abstract(JDK 7及之前)
    void fly();
    void land();
    
    // JDK 8+ 可以有默认方法
    default void showInfo() {
        System.out.println("这是一个可以飞行的对象");
    }
    
    // JDK 8+ 可以有静态方法
    static void staticMethod() {
        System.out.println("接口的静态方法");
    }
}

public interface Swimmable {
    void swim();
}

// 使用implements关键字实现接口
// 一个类可以实现多个接口(弥补单继承的不足)
public class Duck extends Animal implements Flyable, Swimmable {
    
    @Override
    public void fly() {
        System.out.println("鸭子在飞");
    }
    
    @Override
    public void land() {
        System.out.println("鸭子降落了");
    }
    
    @Override
    public void swim() {
        System.out.println("鸭子在游泳");
    }
}

// 接口也可以实现多态
Flyable f = new Duck();
f.fly();  // 鸭子在飞

Swimmable s = new Duck();
s.swim(); // 鸭子在游泳

抽象类 vs 接口:

特性抽象类接口
关键字 abstract class interface
继承/实现 extends(单继承) implements(多实现)
构造方法
成员变量 可以有各种变量 只能有常量(public static final)
方法 抽象方法 + 普通方法 抽象方法 + 默认方法(JDK8)
设计理念 is-a(是什么) can-do(能做什么)
使用场景 有共同的属性和部分方法 定义行为规范

6. 程序异常处理

异常体系

text
Throwable(所有异常和错误的父类)
├── Error(错误)—— 严重问题,程序无法处理
│   ├── OutOfMemoryError(内存溢出)
│   ├── StackOverflowError(栈溢出)
│   └── ...
└── Exception(异常)—— 程序可以处理
    ├── RuntimeException(运行时异常/非受检异常)
    │   ├── NullPointerException(空指针)
    │   ├── ArrayIndexOutOfBoundsException(数组越界)
    │   ├── ClassCastException(类型转换异常)
    │   ├── ArithmeticException(算术异常,如除以零)
    │   ├── NumberFormatException(数字格式异常)
    │   └── ...
    └── 受检异常(编译时异常)—— 必须处理
        ├── IOException(IO异常)
        ├── FileNotFoundException(文件未找到)
        ├── SQLException(数据库异常)
        ├── ClassNotFoundException(类未找到)
        └── ...

异常处理方式

Java
// ============ 方式1:try-catch-finally ============
try {
    // 可能发生异常的代码
    int result = 10 / 0;
    System.out.println("这行不会执行");
} catch (ArithmeticException e) {
    // 捕获并处理异常
    System.out.println("发生算术异常:" + e.getMessage());
    e.printStackTrace();  // 打印异常堆栈信息
} catch (Exception e) {
    // 可以有多个catch,从小到大排列
    System.out.println("其他异常:" + e.getMessage());
} finally {
    // 无论是否发生异常,finally中的代码都会执行
    // 通常用于释放资源(关闭连接、关闭文件等)
    System.out.println("finally块一定会执行");
}

// ============ 方式2:throws声明异常 ============
// 在方法签名上声明可能抛出的异常,交给调用者处理
public void readFile(String path) throws FileNotFoundException, IOException {
    FileInputStream fis = new FileInputStream(path);  // 可能抛出FileNotFoundException
    fis.read();  // 可能抛出IOException
    fis.close();
}

// 调用者处理
try {
    readFile("test.txt");
} catch (FileNotFoundException e) {
    System.out.println("文件不存在");
} catch (IOException e) {
    System.out.println("读取文件失败");
}

// ============ 方式3:throw手动抛出异常 ============
public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("年龄不合理:" + age);
    }
    this.age = age;
}

// ============ 自定义异常 ============
public class AgeException extends RuntimeException {
    public AgeException(String message) {
        super(message);
    }
}

public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new AgeException("年龄必须在0~150之间,当前值:" + age);
    }
    this.age = age;
}

throws vs throw:

 throwsthrow
位置 方法签名上 方法体内
作用 声明方法可能抛出的异常 创建并抛出异常对象
后面跟 异常类名 异常对象

第五阶段:HTML5 + CSS基础


1. HTML5表单

HTML
<!-- ============ form标签 ============ -->
<form action="服务器URL" method="get/post" enctype="multipart/form-data">
    <!-- action: 表单提交的目标地址 -->
    <!-- method: 提交方式(get/post) -->
    <!-- enctype: 编码类型(文件上传时使用multipart/form-data) -->
    
    <!-- ============ 文本输入 ============ -->
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username" 
           placeholder="请输入用户名" maxlength="20" required>
    
    <!-- ============ 密码输入 ============ -->
    <label for="password">密码:</label>
    <input type="password" id="password" name="password" required>
    
    <!-- ============ HTML5新增输入类型 ============ -->
    <input type="email" placeholder="邮箱">         <!-- 自动验证邮箱格式 -->
    <input type="url" placeholder="网址">            <!-- 自动验证URL -->
    <input type="tel" placeholder="手机号">           <!-- 移动端弹出数字键盘 -->
    <input type="number" min="0" max="100" step="5"> <!-- 数字输入 -->
    <input type="range" min="0" max="100">           <!-- 滑块 -->
    <input type="date">                              <!-- 日期选择器 -->
    <input type="time">                              <!-- 时间选择器 -->
    <input type="datetime-local">                    <!-- 日期时间选择器 -->
    <input type="month">                             <!-- 月份选择器 -->
    <input type="week">                              <!-- 周选择器 -->
    <input type="color">                             <!-- 颜色选择器 -->
    <input type="search" placeholder="搜索">          <!-- 搜索框 -->
    <input type="hidden" name="token" value="xxx">   <!-- 隐藏域 -->
    
    <!-- ============ 单选按钮 ============ -->
    <input type="radio" name="gender" value="male" checked> 男
    <input type="radio" name="gender" value="female"> 女
    <!-- 同name的radio为一组,只能选一个 -->
    
    <!-- ============ 复选框 ============ -->
    <input type="checkbox" name="hobby" value="reading"> 阅读
    <input type="checkbox" name="hobby" value="gaming"> 游戏
    <input type="checkbox" name="hobby" value="sports" checked> 运动
    
    <!-- ============ 下拉列表 ============ -->
    <select name="city">
        <option value="">请选择</option>
        <option value="beijing">北京</option>
        <option value="shanghai" selected>上海</option>
        <option value="guangzhou">广州</option>
    </select>
    
    <!-- ============ 多行文本 ============ -->
    <textarea name="intro" rows="5" cols="40" placeholder="自我介绍"></textarea>
    
    <!-- ============ 文件上传 ============ -->
    <input type="file" name="avatar" accept="image/*" multiple>
    <!-- accept: 限制文件类型 -->
    <!-- multiple: 允许多选 -->
    
    <!-- ============ 按钮 ============ -->
    <input type="submit" value="提交">     <!-- 提交按钮 -->
    <input type="reset" value="重置">      <!-- 重置按钮 -->
    <input type="button" value="普通按钮"> <!-- 普通按钮 -->
    <button type="submit">提交按钮</button> <!-- button标签 -->
    
    <!-- ============ HTML5表单属性 ============ -->
    <input type="text" required>           <!-- 必填 -->
    <input type="text" autofocus>          <!-- 自动获取焦点 -->
    <input type="text" autocomplete="off"> <!-- 关闭自动完成 -->
    <input type="text" pattern="[0-9]{6}"> <!-- 正则验证 -->
    <input type="text" readonly>           <!-- 只读 -->
    <input type="text" disabled>           <!-- 禁用 -->
    
    <!-- ============ datalist(数据列表) ============ -->
    <input type="text" list="browsers">
    <datalist id="browsers">
        <option value="Chrome">
        <option value="Firefox">
        <option value="Safari">
        <option value="Edge">
    </datalist>
</form>

GET vs POST:

特性GETPOST
数据位置 URL中(可见) 请求体中(不可见)
数据长度 有限制(约2KB) 无限制
安全性 低(数据在URL中暴露) 较高
书签 可以收藏 不能收藏
缓存 会被缓存 不会被缓存
使用场景 查询数据 提交数据(登录、注册等)

2. 样式表分类

CSS简介

text
CSS(Cascading Style Sheets):层叠样式表
- 用于控制HTML元素的显示样式(颜色、大小、位置等)
- 实现内容(HTML)与表现(CSS)的分离

三种引入方式

HTML
<!-- ============ 1. 行内样式(内联样式) ============ -->
<!-- 直接写在标签的style属性中 -->
<p style="color: red; font-size: 16px;">红色文字</p>
<!-- 优先级最高,但不推荐(维护困难) -->

<!-- ============ 2. 内部样式(嵌入样式) ============ -->
<!-- 写在head标签内的style标签中 -->
<head>
    <style>
        p {
            color: blue;
            font-size: 14px;
        }
        .highlight {
            background-color: yellow;
        }
    </style>
</head>
<!-- 适合单个页面的样式 -->

<!-- ============ 3. 外部样式(推荐) ============ -->
<!-- 写在单独的.css文件中,通过link标签引入 -->
<head>
    <link rel="stylesheet" href="style.css">
</head>
<!-- 适合多个页面共用样式,最推荐的方式 -->

<!-- style.css文件内容:
p {
    color: green;
    font-size: 16px;
}
-->

三种方式对比:

方式优先级复用性维护性推荐度
行内样式 最高
内部样式 单页 一般 ⭐⭐
外部样式 ⭐⭐⭐

3. 选择器(CSS3选择器)

基本选择器

CSS
/* ============ 1. 元素选择器(标签选择器) ============ */
p { color: red; }           /* 所有<p>标签 */
h1 { font-size: 24px; }     /* 所有<h1>标签 */
div { background: #f0f0f0; }

/* ============ 2. 类选择器 ============ */
.highlight { background: yellow; }  /* class="highlight"的元素 */
.btn { padding: 10px 20px; }
p.intro { font-style: italic; }     /* 同时是<p>标签且class="intro" */

/* HTML: <p class="highlight">高亮文字</p> */
/* 一个元素可以有多个类: <p class="highlight intro">多个类</p> */

/* ============ 3. ID选择器 ============ */
#header { height: 80px; }   /* id="header"的元素 */
#footer { background: #333; }
/* ID在页面中必须唯一 */

/* ============ 4. 通配符选择器 ============ */
* { margin: 0; padding: 0; }  /* 所有元素 */

/* ============ 5. 属性选择器(CSS3) ============ */
[href] { color: blue; }                     /* 有href属性的元素 */
[type="text"] { border: 1px solid #ccc; }   /* type="text"的元素 */
[class~="btn"] { }                           /* class包含"btn"(空格分隔)*/
[href^="https"] { }                          /* href以"https"开头 */
[href$=".pdf"] { }                           /* href以".pdf"结尾 */
[href*="baidu"] { }                          /* href包含"baidu" */

组合选择器

CSS
/* ============ 后代选择器(空格) ============ */
div p { color: red; }      /* div内部所有p(包括嵌套的) */

/* ============ 子元素选择器(>) ============ */
div > p { color: blue; }   /* div的直接子元素p */

/* ============ 相邻兄弟选择器(+) ============ */
h1 + p { font-size: 18px; }  /* 紧跟在h1后面的第一个p */

/* ============ 通用兄弟选择器(~) ============ */
h1 ~ p { color: gray; }   /* h1后面所有的兄弟p */

/* ============ 分组选择器(,) ============ */
h1, h2, h3 { color: navy; }  /* h1、h2、h3共用样式 */

伪类选择器

CSS
/* ============ 链接伪类 ============ */
a:link { color: blue; }      /* 未访问的链接 */
a:visited { color: purple; } /* 已访问的链接 */
a:hover { color: red; }      /* 鼠标悬停 */
a:active { color: orange; }  /* 点击时 */
/* 顺序必须是 LVHA:link → visited → hover → active */

/* ============ 结构伪类(CSS3) ============ */
li:first-child { }           /* 第一个子元素 */
li:last-child { }            /* 最后一个子元素 */
li:nth-child(3) { }          /* 第3个子元素 */
li:nth-child(2n) { }         /* 偶数子元素 */
li:nth-child(2n+1) { }       /* 奇数子元素 */
li:nth-child(odd) { }        /* 奇数(同上) */
li:nth-child(even) { }       /* 偶数 */
p:first-of-type { }          /* 同类型中的第一个 */
p:last-of-type { }           /* 同类型中的最后一个 */
p:nth-of-type(2) { }         /* 同类型中的第2个 */

/* ============ 表单伪类 ============ */
input:focus { border-color: blue; }   /* 获取焦点时 */
input:disabled { background: #eee; }  /* 禁用状态 */
input:checked { }                      /* 选中状态 */
input:required { }                     /* 必填字段 */
input:valid { }                        /* 验证通过 */
input:invalid { }                      /* 验证失败 */

/* ============ 否定伪类 ============ */
p:not(.intro) { }            /* 不是.intro类的p */

伪元素选择器

CSS
/* ============ 伪元素(CSS3用::) ============ */
p::before { content: "★ "; }     /* 在元素内容前面插入 */
p::after { content: " →"; }      /* 在元素内容后面插入 */
p::first-line { font-weight: bold; }  /* 第一行 */
p::first-letter { font-size: 2em; }   /* 第一个字母 */
::selection { background: yellow; }    /* 被选中的文本 */

选择器优先级

text
!important > 行内样式 > ID选择器 > 类选择器 > 元素选择器 > 通配符

权重计算:
  行内样式      1000
  ID选择器      100
  类/伪类/属性  10
  元素/伪元素   1
  通配符        0

例:
  #nav .list li a     → 100 + 10 + 1 + 1 = 112
  .content p          → 10 + 1 = 11
  #header             → 100

第六阶段:CSS布局


1. 基本样式

字体样式

CSS
p {
    font-family: "微软雅黑", Arial, sans-serif;  /* 字体族 */
    font-size: 16px;          /* 字体大小 */
    font-weight: bold;        /* 字体粗细:normal/bold/100~900 */
    font-style: italic;       /* 字体风格:normal/italic/oblique */
    font-variant: small-caps; /* 小型大写字母 */
    
    /* 简写 */
    font: italic bold 16px/1.5 "微软雅黑", Arial;
    /* 顺序:style weight size/line-height family */
}

文本样式

CSS
p {
    color: #333;                    /* 文字颜色 */
    text-align: center;             /* 对齐:left/right/center/justify */
    text-decoration: underline;     /* 装饰:none/underline/overline/line-through */
    text-indent: 2em;               /* 首行缩进(2个字符) */
    text-transform: uppercase;      /* 大小写:uppercase/lowercase/capitalize */
    letter-spacing: 2px;            /* 字符间距 */
    word-spacing: 5px;              /* 单词间距 */
    line-height: 1.5;               /* 行高 */
    white-space: nowrap;            /* 不换行 */
    text-overflow: ellipsis;        /* 文字溢出显示省略号 */
    overflow: hidden;               /* 配合text-overflow使用 */
    text-shadow: 2px 2px 4px #666;  /* 文字阴影 */
}

超链接样式

CSS
/* 去除默认下划线 */
a {
    text-decoration: none;
    color: #333;
}

/* 悬停效果 */
a:hover {
    color: #ff6600;
    text-decoration: underline;
}

/* 导航链接样式 */
.nav a {
    display: inline-block;
    padding: 10px 20px;
    color: white;
    background-color: #333;
    text-decoration: none;
}

.nav a:hover {
    background-color: #ff6600;
}

背景样式

CSS
div {
    background-color: #f0f0f0;                 /* 背景颜色 */
    background-image: url('bg.jpg');           /* 背景图片 */
    background-repeat: no-repeat;              /* 重复:repeat/no-repeat/repeat-x/repeat-y */
    background-position: center center;        /* 位置:left/right/center top/bottom/center 或 px */
    background-size: cover;                    /* 大小:cover/contain/100% 50%/200px 100px */
    background-attachment: fixed;              /* 固定:scroll/fixed */
    
    /* 简写 */
    background: #f0f0f0 url('bg.jpg') no-repeat center center / cover;
    
    /* CSS3: 多背景 */
    background: url('img1.png') no-repeat top left,
                url('img2.png') no-repeat bottom right,
                #f0f0f0;
    
    /* CSS3: 渐变背景 */
    background: linear-gradient(to right, #ff6600, #ff0066);    /* 线性渐变 */
    background: radial-gradient(circle, #ff6600, #ff0066);      /* 径向渐变 */
}

列表样式

CSS
ul {
    list-style-type: none;         /* 去掉列表符号 */
    /* 可选值:disc/circle/square/decimal/none */
    list-style-position: inside;   /* 符号位置:inside/outside */
    list-style-image: url('bullet.png');  /* 自定义列表图标 */
    
    /* 简写 */
    list-style: none;
}

2. 盒子模型

盒子模型概念

text
每个HTML元素都可以看作一个"盒子",由以下部分组成:

┌──────────────────────────────────────┐
│              margin(外边距)          │
│  ┌───────────────────────────────┐   │
│  │         border(边框)          │   │
│  │  ┌─────────────────────────┐  │   │
│  │  │     padding(内边距)     │  │   │
│  │  │  ┌───────────────────┐  │  │   │
│  │  │  │                   │  │  │   │
│  │  │  │   content(内容)  │  │  │   │
│  │  │  │                   │  │  │   │
│  │  │  └───────────────────┘  │  │   │
│  │  └─────────────────────────┘  │   │
│  └───────────────────────────────┘   │
└──────────────────────────────────────┘

各部分设置

CSS
div {
    /* ============ 内容区域 ============ */
    width: 200px;      /* 内容宽度 */
    height: 100px;     /* 内容高度 */
    
    /* ============ 内边距 padding ============ */
    padding-top: 10px;
    padding-right: 20px;
    padding-bottom: 10px;
    padding-left: 20px;
    
    /* 简写 */
    padding: 10px;                  /* 四边相同 */
    padding: 10px 20px;             /* 上下10,左右20 */
    padding: 10px 20px 30px;        /* 上10,左右20,下30 */
    padding: 10px 20px 30px 40px;   /* 上10,右20,下30,左40(顺时针) */
    
    /* ============ 边框 border ============ */
    border-width: 2px;
    border-style: solid;    /* solid/dashed/dotted/double/none */
    border-color: #333;
    
    /* 简写 */
    border: 2px solid #333;
    
    /* 单独设置某一边 */
    border-top: 3px dashed red;
    border-bottom: none;
    
    /* CSS3: 圆角边框 */
    border-radius: 10px;              /* 四角相同 */
    border-radius: 50%;               /* 圆形 */
    border-radius: 10px 20px 30px 40px;  /* 四角不同 */
    
    /* CSS3: 盒子阴影 */
    box-shadow: 5px 5px 10px rgba(0,0,0,0.3);
    /* 水平偏移 垂直偏移 模糊半径 颜色 */
    
    /* ============ 外边距 margin ============ */
    margin-top: 10px;
    margin-right: auto;     /* auto: 自动居中 */
    margin-bottom: 10px;
    margin-left: auto;
    
    /* 简写(与padding相同) */
    margin: 10px auto;      /* 上下10,左右自动(水平居中) */
    margin: 0 auto;         /* 最常用的水平居中方式 */
}

盒子大小计算

CSS
/* ============ 标准盒模型(默认) ============ */
/* 总宽度 = width + padding-left + padding-right + border-left + border-right + margin-left + margin-right */
/* 总高度 = height + padding-top + padding-bottom + border-top + border-bottom + margin-top + margin-bottom */

div {
    width: 200px;
    padding: 20px;
    border: 5px solid #333;
    margin: 10px;
}
/* 实际占据宽度 = 200 + 20*2 + 5*2 + 10*2 = 270px */
/* 盒子可见宽度 = 200 + 20*2 + 5*2 = 250px */

/* ============ CSS3: box-sizing ============ */
/* 改变盒子大小的计算方式 */
div {
    box-sizing: content-box;  /* 默认:width只包含content */
    box-sizing: border-box;   /* width包含content+padding+border */
}

/* border-box更符合直觉 */
div {
    box-sizing: border-box;
    width: 200px;
    padding: 20px;
    border: 5px solid #333;
}
/* 设置width=200px,盒子可见宽度就是200px */
/* content宽度 = 200 - 20*2 - 5*2 = 150px */

/* 全局设置(推荐) */
* {
    box-sizing: border-box;
}

外边距合并问题

CSS
/* 垂直方向的margin会合并(取较大值) */
.box1 { margin-bottom: 30px; }
.box2 { margin-top: 20px; }
/* 两个盒子之间的间距是30px,不是50px */

/* 解决方法:只给一个元素设置margin */

3. 页面布局(浮动)

浮动(float)

CSS
/* 浮动最初用于实现文字环绕图片效果 */
/* 现在主要用于横向排列元素 */

.left-box {
    float: left;     /* 向左浮动 */
    width: 200px;
    height: 300px;
}

.right-box {
    float: right;    /* 向右浮动 */
    width: 200px;
    height: 300px;
}

/* float的值:left / right / none(默认) */

浮动的特点:

text
1. 脱离标准文档流(不再占据原来的位置)
2. 浮动元素会向左或向右移动,直到碰到父元素边界或其他浮动元素
3. 浮动元素不会超出父元素的padding区域
4. 多个浮动元素会在同一行排列(空间不够会换行)
5. 浮动元素变成"块级"(可以设宽高)

清除浮动

问题:父元素没有设高度时,浮动子元素不会撑开父元素(高度塌陷)

CSS
/* ============ 方法1:clear属性 ============ */
.clearfix {
    clear: both;  /* 清除两侧浮动的影响 */
    /* clear: left / right / both / none */
}

/* ============ 方法2:父元素overflow ============ */
.parent {
    overflow: hidden;  /* 触发BFC,自动包裹浮动子元素 */
}

/* ============ 方法3:伪元素清除(推荐) ============ */
.clearfix::after {
    content: "";
    display: block;
    clear: both;
}
/* 给父元素添加class="clearfix"即可 */

/* ============ 方法4:双伪元素清除 ============ */
.clearfix::before,
.clearfix::after {
    content: "";
    display: table;
}
.clearfix::after {
    clear: both;
}

常见布局

CSS
/* ============ 两列布局 ============ */
.left {
    float: left;
    width: 200px;
}
.right {
    margin-left: 220px;  /* 左边距 = 左栏宽度 + 间距 */
}

/* ============ 三列布局 ============ */
.left {
    float: left;
    width: 200px;
}
.right {
    float: right;
    width: 200px;
}
.center {
    margin: 0 220px;  /* 两边留出空间 */
}

4. 定位机制

position属性

CSS
/* ============ 1. 静态定位 static(默认) ============ */
div {
    position: static;
    /* 默认值,元素按标准文档流排列 */
    /* top/right/bottom/left无效 */
}

/* ============ 2. 相对定位 relative ============ */
div {
    position: relative;
    top: 20px;      /* 相对于原位置向下移动20px */
    left: 30px;     /* 相对于原位置向右移动30px */
}
/* 特点:
   - 相对于元素原来的位置进行偏移
   - 不脱离文档流(原位置仍然保留)
   - 常用作子元素绝对定位的参照
*/

/* ============ 3. 绝对定位 absolute ============ */
div {
    position: absolute;
    top: 50px;
    right: 20px;
}
/* 特点:
   - 相对于最近的已定位(非static)祖先元素定位
   - 如果没有已定位的祖先,则相对于body
   - 脱离文档流(不占据原位置)
   - 常用的套路:"子绝父相"(子元素absolute,父元素relative)
*/

/* "子绝父相"示例 */
.parent {
    position: relative;  /* 作为参照 */
    width: 500px;
    height: 300px;
}
.child {
    position: absolute;  /* 相对于parent定位 */
    top: 10px;
    right: 10px;
    /* 在parent的右上角 */
}

/* ============ 4. 固定定位 fixed ============ */
div {
    position: fixed;
    bottom: 20px;
    right: 20px;
}
/* 特点:
   - 相对于浏览器窗口定位
   - 脱离文档流
   - 页面滚动时位置不变
   - 常用于:固定导航栏、回到顶部按钮、悬浮广告
*/

/* 固定导航栏 */
.navbar {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 60px;
    background: #333;
    z-index: 999;
}

/* ============ 5. 粘性定位 sticky(CSS3) ============ */
div {
    position: sticky;
    top: 0;
}
/* 特点:
   - 在到达指定位置前是relative,到达后变成fixed
   - 常用于:滚动时固定的表头
*/

z-index(层叠顺序)

CSS
/* 当定位元素重叠时,z-index决定谁在上面 */
.box1 {
    position: absolute;
    z-index: 1;    /* 较小的在下面 */
}
.box2 {
    position: absolute;
    z-index: 10;   /* 较大的在上面 */
}
/* z-index只对定位元素有效(position不是static) */
/* 值可以是正数、负数、0 */

居中技巧

CSS
/* ============ 水平居中 ============ */
/* 块级元素 */
.center-block {
    width: 200px;
    margin: 0 auto;
}

/* 行内元素/文字 */
.center-text {
    text-align: center;
}

/* ============ 垂直居中 ============ */
/* 单行文字 */
.center-line {
    height: 50px;
    line-height: 50px;  /* line-height = height */
}

/* ============ 水平垂直居中(定位方式) ============ */
.parent {
    position: relative;
}
.child {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

/* ============ 水平垂直居中(Flex方式,最推荐) ============ */
.parent {
    display: flex;
    justify-content: center;  /* 水平居中 */
    align-items: center;      /* 垂直居中 */
}

第七阶段:MySQL数据库


1. MySQL安装与管理(SQLyog)

MySQL简介

text
MySQL是最流行的开源关系型数据库管理系统(RDBMS)
- 关系型:数据以表格形式存储,表之间通过关系(外键)连接
- SQL:Structured Query Language(结构化查询语言)
- 特点:开源免费、性能高、跨平台、社区活跃

安装与配置

text
安装MySQL:
1. 下载MySQL Server(推荐5.7或8.0版本)
2. 安装时设置root密码
3. 选择字符集:UTF-8(utf8mb4)
4. 配置端口:默认3306
5. 将MySQL添加到系统服务(自动启动)

管理工具SQLyog:
- 图形化的MySQL管理工具
- 可以直观地创建数据库、表
- 可以直接执行SQL语句
- 可以导入导出数据

基本操作

SQL
-- 连接MySQL
mysql -u root -p

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

-- 创建数据库
CREATE DATABASE school CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

-- 使用数据库
USE school;

-- 查看当前数据库的所有表
SHOW TABLES;

-- 删除数据库
DROP DATABASE school;

2. 数据库约束

SQL
-- 约束用于保证数据的完整性和正确性

-- ============ 1. 主键约束 PRIMARY KEY ============
-- 唯一标识表中的每一行,不能为NULL,不能重复
CREATE TABLE student (
    id INT PRIMARY KEY AUTO_INCREMENT,  -- 主键 + 自增
    name VARCHAR(20)
);

-- ============ 2. 非空约束 NOT NULL ============
-- 该列不能为NULL
name VARCHAR(20) NOT NULL,

-- ============ 3. 唯一约束 UNIQUE ============
-- 该列的值不能重复(但可以有多个NULL)
email VARCHAR(50) UNIQUE,

-- ============ 4. 默认约束 DEFAULT ============
-- 未指定值时使用默认值
gender VARCHAR(2) DEFAULT '男',

-- ============ 5. 外键约束 FOREIGN KEY ============
-- 建立表之间的关联关系,保证引用完整性
CREATE TABLE score (
    id INT PRIMARY KEY AUTO_INCREMENT,
    student_id INT,
    course VARCHAR(20),
    grade DECIMAL(5,2),
    FOREIGN KEY (student_id) REFERENCES student(id)
);

-- ============ 6. 检查约束 CHECK(MySQL 8.0.16+支持) ============
age INT CHECK(age >= 0 AND age <= 150),

-- ============ 综合示例 ============
CREATE TABLE employee (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL,
    email VARCHAR(50) UNIQUE,
    age INT CHECK(age >= 18 AND age <= 65),
    gender CHAR(1) DEFAULT '男',
    dept_id INT,
    hire_date DATE NOT NULL,
    salary DECIMAL(10,2) DEFAULT 0,
    FOREIGN KEY (dept_id) REFERENCES department(id)
);

3. DDL、DML、DQL、DCL语句

DDL(Data Definition Language)数据定义语言

SQL
-- 操作数据库结构(库、表、字段)

-- ============ 创建表 ============
CREATE TABLE student (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '学生ID',
    name VARCHAR(20) NOT NULL COMMENT '姓名',
    age INT COMMENT '年龄',
    gender CHAR(1) DEFAULT '男' COMMENT '性别',
    email VARCHAR(50) UNIQUE COMMENT '邮箱',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表';

-- MySQL常用数据类型:
-- 整数:TINYINT, SMALLINT, INT, BIGINT
-- 小数:FLOAT, DOUBLE, DECIMAL(M,D)
-- 字符串:CHAR(定长), VARCHAR(变长), TEXT(大文本)
-- 日期:DATE, TIME, DATETIME, TIMESTAMP
-- 二进制:BLOB

-- ============ 修改表 ============
-- 添加列
ALTER TABLE student ADD phone VARCHAR(20);
ALTER TABLE student ADD address VARCHAR(100) AFTER email;  -- 指定位置

-- 修改列类型
ALTER TABLE student MODIFY name VARCHAR(50) NOT NULL;

-- 修改列名
ALTER TABLE student CHANGE phone telephone VARCHAR(20);

-- 删除列
ALTER TABLE student DROP telephone;

-- 重命名表
ALTER TABLE student RENAME TO stu;
RENAME TABLE stu TO student;

-- ============ 删除表 ============
DROP TABLE student;              -- 直接删除
DROP TABLE IF EXISTS student;    -- 存在才删除

-- ============ 清空表 ============
TRUNCATE TABLE student;  -- 删除所有数据,重置自增ID,不可回滚

DML(Data Manipulation Language)数据操作语言

SQL
-- 操作表中的数据(增删改)

-- ============ 插入数据 INSERT ============
-- 方式1:指定列名
INSERT INTO student (name, age, gender, email)
VALUES ('张三', 20, '男', 'zhangsan@email.com');

-- 方式2:所有列(不推荐,列顺序改变会出问题)
INSERT INTO student
VALUES (NULL, '李四', 22, '女', 'lisi@email.com', NOW());

-- 方式3:批量插入
INSERT INTO student (name, age, gender) VALUES
('王五', 21, '男'),
('赵六', 23, '女'),
('钱七', 20, '男');

-- ============ 更新数据 UPDATE ============
-- 修改指定条件的数据
UPDATE student SET age = 25 WHERE name = '张三';

-- 修改多个字段
UPDATE student SET age = 26, email = 'new@email.com' WHERE id = 1;

-- 不加WHERE条件会修改所有行!(危险操作)
UPDATE student SET age = 20;  -- 所有学生年龄都变成20

-- ============ 删除数据 DELETE ============
-- 删除指定条件的数据
DELETE FROM student WHERE id = 5;

-- 删除所有数据(可回滚)
DELETE FROM student;

-- TRUNCATE vs DELETE:
-- TRUNCATE: 速度快,重置自增ID,不能回滚,不触发触发器
-- DELETE:   可以条件删除,可以回滚,触发触发器

DQL(Data Query Language)数据查询语言

SQL
-- ============ 基础查询 ============
SELECT * FROM student;                          -- 查询所有列
SELECT name, age FROM student;                  -- 查询指定列
SELECT name AS '姓名', age AS '年龄' FROM student; -- 别名
SELECT DISTINCT gender FROM student;            -- 去重

-- ============ 条件查询 WHERE ============
SELECT * FROM student WHERE age > 20;
SELECT * FROM student WHERE age >= 20 AND gender = '男';
SELECT * FROM student WHERE age = 20 OR age = 22;
SELECT * FROM student WHERE age IN (20, 22, 25);
SELECT * FROM student WHERE age BETWEEN 20 AND 25;
SELECT * FROM student WHERE email IS NULL;
SELECT * FROM student WHERE email IS NOT NULL;

-- ============ 排序 ORDER BY ============
SELECT * FROM student ORDER BY age ASC;          -- 升序(默认)
SELECT * FROM student ORDER BY age DESC;         -- 降序
SELECT * FROM student ORDER BY age DESC, name ASC; -- 多字段排序

-- ============ 限制结果 LIMIT ============
SELECT * FROM student LIMIT 5;                   -- 前5条
SELECT * FROM student LIMIT 5, 10;               -- 从第6条开始取10条(分页)
-- LIMIT offset, count
-- 第1页: LIMIT 0, 10
-- 第2页: LIMIT 10, 10
-- 第n页: LIMIT (n-1)*pageSize, pageSize

详细见后面的查询部分。

DCL(Data Control Language)数据控制语言

SQL
-- 管理用户权限

-- ============ 用户管理 ============
-- 创建用户
CREATE USER 'zhangsan'@'localhost' IDENTIFIED BY '123456';
CREATE USER 'zhangsan'@'%' IDENTIFIED BY '123456';  -- %表示任何主机

-- 删除用户
DROP USER 'zhangsan'@'localhost';

-- 修改密码
ALTER USER 'zhangsan'@'localhost' IDENTIFIED BY 'newpassword';

-- ============ 权限管理 ============
-- 授权
GRANT SELECT, INSERT ON school.* TO 'zhangsan'@'localhost';
GRANT ALL PRIVILEGES ON school.* TO 'zhangsan'@'localhost';  -- 所有权限

-- 撤销权限
REVOKE INSERT ON school.* FROM 'zhangsan'@'localhost';

-- 查看权限
SHOW GRANTS FOR 'zhangsan'@'localhost';

-- 刷新权限
FLUSH PRIVILEGES;

4. 各种查询

模糊查询

SQL
-- LIKE + 通配符
-- %:匹配任意多个字符
-- _:匹配一个字符

SELECT * FROM student WHERE name LIKE '张%';    -- 张开头的
SELECT * FROM student WHERE name LIKE '%三';    -- 三结尾的
SELECT * FROM student WHERE name LIKE '%小%';   -- 包含小的
SELECT * FROM student WHERE name LIKE '张_';    -- 张+一个字符(张三、张四)
SELECT * FROM student WHERE name LIKE '张__';   -- 张+两个字符(张三丰)

连表查询(多表查询)

SQL
-- 准备数据
-- student表:id, name, class_id
-- class表:id, class_name

-- ============ 内连接(INNER JOIN)============
-- 只返回两个表中匹配的记录
SELECT s.name, c.class_name
FROM student s
INNER JOIN class c ON s.class_id = c.id;

-- 等价写法(隐式内连接)
SELECT s.name, c.class_name
FROM student s, class c
WHERE s.class_id = c.id;

-- ============ 左连接(LEFT JOIN)============
-- 返回左表所有记录 + 右表匹配的记录(不匹配的显示NULL)
SELECT s.name, c.class_name
FROM student s
LEFT JOIN class c ON s.class_id = c.id;

-- ============ 右连接(RIGHT JOIN)============
-- 返回右表所有记录 + 左表匹配的记录
SELECT s.name, c.class_name
FROM student s
RIGHT JOIN class c ON s.class_id = c.id;

-- ============ 自连接 ============
-- 表和自己连接
SELECT e.name AS '员工', m.name AS '上级'
FROM employee e
LEFT JOIN employee m ON e.manager_id = m.id;

子查询

SQL
-- 子查询:SQL语句中嵌套另一个SELECT语句

-- ============ 单行子查询 ============
-- 查询比张三年龄大的所有学生
SELECT * FROM student
WHERE age > (SELECT age FROM student WHERE name = '张三');

-- ============ 多行子查询 ============
-- IN:在子查询结果中
SELECT * FROM student
WHERE class_id IN (SELECT id FROM class WHERE class_name LIKE '%一%');

-- ANY/SOME:任意一个
SELECT * FROM student
WHERE age > ANY (SELECT age FROM student WHERE gender = '女');

-- ALL:所有
SELECT * FROM student
WHERE age > ALL (SELECT age FROM student WHERE gender = '女');

-- ============ EXISTS子查询 ============
SELECT * FROM student s
WHERE EXISTS (SELECT 1 FROM score sc WHERE sc.student_id = s.id);

-- ============ 子查询作为临时表 ============
SELECT t.name, t.avg_score
FROM (
    SELECT s.name, AVG(sc.grade) AS avg_score
    FROM student s
    JOIN score sc ON s.id = sc.student_id
    GROUP BY s.id
) t
WHERE t.avg_score > 80;

分组查询

SQL
-- GROUP BY + 聚合函数

-- ============ 聚合函数 ============
SELECT COUNT(*) FROM student;                    -- 总数
SELECT COUNT(email) FROM student;                -- 非NULL数量
SELECT SUM(age) FROM student;                    -- 求和
SELECT AVG(age) FROM student;                    -- 平均值
SELECT MAX(age) FROM student;                    -- 最大值
SELECT MIN(age) FROM student;                    -- 最小值

-- ============ 分组查询 ============
-- 按性别统计人数
SELECT gender, COUNT(*) AS '人数'
FROM student
GROUP BY gender;

-- 按班级统计平均成绩
SELECT class_id, AVG(grade) AS '平均分'
FROM score
GROUP BY class_id;

-- ============ HAVING(分组后的条件过滤) ============
-- 查询平均分大于80的班级
SELECT class_id, AVG(grade) AS avg_grade
FROM score
GROUP BY class_id
HAVING avg_grade > 80;

-- WHERE vs HAVING:
-- WHERE:分组前过滤,不能使用聚合函数
-- HAVING:分组后过滤,可以使用聚合函数

SQL执行顺序

SQL
-- 书写顺序:
SELECT → FROM → JOIN → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT

-- 执行顺序:
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

-- 理解执行顺序很重要!
-- 1. FROM/JOIN: 确定数据来源(哪些表)
-- 2. WHERE: 过滤行
-- 3. GROUP BY: 分组
-- 4. HAVING: 过滤分组
-- 5. SELECT: 选择列(此时才能用别名)
-- 6. ORDER BY: 排序(可以用别名)
-- 7. LIMIT: 限制结果数量

5. 数据库函数

SQL
-- ============ 字符串函数 ============
SELECT CONCAT('Hello', ' ', 'World');     -- 拼接:Hello World
SELECT LENGTH('Hello');                    -- 长度:5
SELECT UPPER('hello');                     -- 大写:HELLO
SELECT LOWER('HELLO');                     -- 小写:hello
SELECT SUBSTRING('Hello World', 7, 5);    -- 截取:World(从第7个开始取5个)
SELECT TRIM('  Hello  ');                  -- 去空格:Hello
SELECT REPLACE('Hello', 'l', 'L');         -- 替换:HeLLo
SELECT REVERSE('Hello');                   -- 反转:olleH
SELECT LPAD('5', 3, '0');                  -- 左填充:005
SELECT RPAD('5', 3, '0');                  -- 右填充:500

-- ============ 数值函数 ============
SELECT ABS(-10);          -- 绝对值:10
SELECT CEIL(3.2);         -- 向上取整:4
SELECT FLOOR(3.8);        -- 向下取整:3
SELECT ROUND(3.1415, 2);  -- 四舍五入:3.14
SELECT MOD(10, 3);        -- 取余:1
SELECT RAND();            -- 随机数:0~1之间

-- ============ 日期函数 ============
SELECT NOW();             -- 当前日期时间:2024-01-15 10:30:00
SELECT CURDATE();         -- 当前日期:2024-01-15
SELECT CURTIME();         -- 当前时间:10:30:00
SELECT YEAR(NOW());       -- 年份
SELECT MONTH(NOW());      -- 月份
SELECT DAY(NOW());        -- 日
SELECT DATEDIFF('2024-12-31', '2024-01-01');  -- 日期差:365
SELECT DATE_ADD(NOW(), INTERVAL 30 DAY);       -- 加30天
SELECT DATE_FORMAT(NOW(), '%Y年%m月%d日');      -- 格式化

-- ============ 流程控制函数 ============
SELECT IF(score >= 60, '及格', '不及格');

SELECT IFNULL(email, '未设置');  -- 如果email为NULL,返回'未设置'

SELECT CASE
    WHEN score >= 90 THEN '优秀'
    WHEN score >= 80 THEN '良好'
    WHEN score >= 60 THEN '及格'
    ELSE '不及格'
END AS '等级'
FROM score;

6. 数据库事务

事务概念

SQL
-- 事务(Transaction):一组操作要么全部成功,要么全部失败
-- 经典案例:银行转账
-- A给B转100元:
-- 1. A账户 -100
-- 2. B账户 +100
-- 如果第1步成功,第2步失败?→ A少了100,B没收到 → 数据不一致!
-- 事务保证这两步要么都成功,要么都回滚

ACID特性

text
A - Atomicity(原子性)
    事务中的操作要么全部执行,要么全部不执行

C - Consistency(一致性)
    事务执行前后,数据保持一致(如转账前后总金额不变)

I - Isolation(隔离性)
    多个事务并发执行时,互不干扰

D - Durability(持久性)
    事务提交后,数据永久保存,即使系统故障也不丢失

事务操作

SQL
-- ============ 手动控制事务 ============
-- 开始事务
START TRANSACTION;  -- 或 BEGIN;

-- 执行操作
UPDATE account SET balance = balance - 100 WHERE name = 'A';
UPDATE account SET balance = balance + 100 WHERE name = 'B';

-- 提交事务(所有操作生效)
COMMIT;

-- 或 回滚事务(所有操作撤销)
ROLLBACK;

-- ============ 自动提交 ============
-- MySQL默认自动提交(每条SQL都是一个事务)
-- 查看自动提交状态
SELECT @@autocommit;  -- 1=开启,0=关闭

-- 关闭自动提交
SET autocommit = 0;

-- ============ 保存点 ============
START TRANSACTION;
INSERT INTO student VALUES (1, '张三', 20);
SAVEPOINT sp1;  -- 设置保存点
INSERT INTO student VALUES (2, '李四', 22);
ROLLBACK TO sp1;  -- 回滚到保存点(只撤销李四的插入)
COMMIT;

事务隔离级别

SQL
-- 并发问题:
-- 1. 脏读:读到其他事务未提交的数据
-- 2. 不可重复读:同一事务中两次读取结果不同(其他事务修改了数据)
-- 3. 幻读:同一事务中两次查询结果条数不同(其他事务插入了数据)

-- 隔离级别(从低到高):
-- READ UNCOMMITTED(读未提交):都不能防止
-- READ COMMITTED(读已提交):防止脏读
-- REPEATABLE READ(可重复读):防止脏读和不可重复读(MySQL默认)
-- SERIALIZABLE(序列化):全部防止(性能最差)

-- 查看隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

7. 数据库视图

SQL
-- 视图(View):虚拟的表,是SQL查询结果的命名引用
-- 不存储数据,每次使用时都执行底层SQL
-- 作用:简化复杂查询、控制数据访问权限

-- ============ 创建视图 ============
CREATE VIEW v_student_score AS
SELECT s.name, s.age, c.class_name, sc.grade
FROM student s
JOIN class c ON s.class_id = c.id
JOIN score sc ON s.id = sc.student_id;

-- ============ 使用视图(像表一样查询) ============
SELECT * FROM v_student_score WHERE grade > 80;

-- ============ 修改视图 ============
CREATE OR REPLACE VIEW v_student_score AS
SELECT s.name, AVG(sc.grade) AS avg_grade
FROM student s
JOIN score sc ON s.id = sc.student_id
GROUP BY s.id;

-- ============ 删除视图 ============
DROP VIEW v_student_score;
DROP VIEW IF EXISTS v_student_score;

-- ============ 查看视图 ============
SHOW CREATE VIEW v_student_score;

8. 数据库索引

SQL
-- 索引(Index):类似于书的目录,加速查询
-- 原理:通常使用B+树数据结构

-- 优点:大幅提高查询速度
-- 缺点:占用存储空间,降低增删改的速度

-- ============ 创建索引 ============
-- 普通索引
CREATE INDEX idx_name ON student(name);

-- 唯一索引
CREATE UNIQUE INDEX idx_email ON student(email);

-- 复合索引(多列索引)
CREATE INDEX idx_name_age ON student(name, age);

-- 建表时创建索引
CREATE TABLE student (
    id INT PRIMARY KEY,  -- 主键自动创建索引
    name VARCHAR(20),
    email VARCHAR(50),
    INDEX idx_name(name),
    UNIQUE INDEX idx_email(email)
);

-- ============ 查看索引 ============
SHOW INDEX FROM student;

-- ============ 删除索引 ============
DROP INDEX idx_name ON student;

-- ============ 哪些列适合建索引 ============
-- ✅ 主键(自动创建)
-- ✅ 外键
-- ✅ WHERE条件频繁使用的列
-- ✅ ORDER BY、GROUP BY使用的列
-- ✅ 数据量大且区分度高的列
-- ❌ 频繁更新的列
-- ❌ 数据量小的表
-- ❌ 大量重复值的列(如性别)

-- ============ EXPLAIN 分析查询计划 ============
EXPLAIN SELECT * FROM student WHERE name = '张三';
-- type列:ALL(全表扫描) > index > range > ref > eq_ref > const
-- key列:实际使用的索引
-- rows列:预估扫描行数

9. 数据库备份恢复

SQL
-- ============ 命令行备份 ============
-- 备份整个数据库
mysqldump -u root -p school > school_backup.sql

-- 备份指定表
mysqldump -u root -p school student score > tables_backup.sql

-- 备份所有数据库
mysqldump -u root -p --all-databases > all_backup.sql

-- ============ 命令行恢复 ============
-- 方式1:命令行
mysql -u root -p school < school_backup.sql

-- 方式2:登录MySQL后
mysql> USE school;
mysql> SOURCE /path/to/school_backup.sql;

-- ============ SQLyog中操作 ============
-- 备份:右键数据库 → 备份/导出 → 导出为SQL
-- 恢复:右键数据库 → 导入 → 执行SQL文件

第八阶段:Java集合框架


1. List(ArrayList、LinkedList)

集合框架概览

text
Java集合框架
├── Collection(单列集合)
│   ├── List(有序、可重复)
│   │   ├── ArrayList(数组实现)
│   │   ├── LinkedList(链表实现)
│   │   └── Vector(线程安全,已过时)
│   └── Set(无序、不可重复)
│       ├── HashSet(哈希表实现)
│       ├── LinkedHashSet(有序HashSet)
│       └── TreeSet(红黑树,有序)
└── Map(双列集合,键值对)
    ├── HashMap(哈希表实现)
    ├── LinkedHashMap(有序HashMap)
    ├── TreeMap(红黑树,有序)
    └── Hashtable(线程安全,已过时)

ArrayList

Java
// ArrayList基于动态数组实现
// 特点:查询快(通过索引O(1)),增删慢(需要移动元素O(n))

import java.util.ArrayList;

// ============ 创建 ============
ArrayList<String> list = new ArrayList<>();  // 泛型指定元素类型

// ============ 增 ============
list.add("张三");           // 添加到末尾
list.add("李四");
list.add("王五");
list.add(1, "赵六");        // 插入到索引1的位置

// ============ 删 ============
list.remove("李四");         // 按元素删除(删除第一个匹配的)
list.remove(0);             // 按索引删除
list.clear();               // 清空

// ============ 改 ============
list.set(0, "新张三");       // 修改索引0的元素

// ============ 查 ============
String name = list.get(0);   // 获取索引0的元素
int size = list.size();      // 获取长度
boolean has = list.contains("张三");  // 是否包含
int idx = list.indexOf("张三");       // 查找索引(-1表示不存在)
boolean empty = list.isEmpty();       // 是否为空

// ============ 遍历 ============
// 方式1:普通for
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 方式2:增强for
for (String s : list) {
    System.out.println(s);
}

// 方式3:迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

// 方式4:forEach(Java 8+)
list.forEach(s -> System.out.println(s));

ArrayList底层原理:

text
初始容量:10
扩容机制:当数组满时,创建新数组(原容量的1.5倍),将旧数组复制过去

添加元素:
[张三][李四][王五][ ][ ][ ][ ][ ][ ][ ]  ← 初始容量10
                  ↑ 新元素添加到这里

插入元素到索引1:
[张三][赵六][李四][王五][ ][ ][ ][ ][ ][ ]
       ↑ 李四和王五需要向后移动 → 所以插入慢

LinkedList

Java
// LinkedList基于双向链表实现
// 特点:增删快(O(1)),查询慢(需要遍历O(n))

import java.util.LinkedList;

LinkedList<String> list = new LinkedList<>();

// 基本操作和ArrayList相同(因为都实现了List接口)
list.add("A");
list.add("B");
list.get(0);
list.remove(0);

// LinkedList特有方法(作为双端队列/栈使用)
list.addFirst("First");      // 添加到头部
list.addLast("Last");        // 添加到尾部
list.getFirst();             // 获取第一个
list.getLast();              // 获取最后一个
list.removeFirst();          // 删除第一个
list.removeLast();           // 删除最后一个
list.push("Push");           // 压栈(添加到头部)
list.pop();                  // 弹栈(删除并返回第一个)
list.peek();                 // 查看栈顶(不删除)

LinkedList底层原理:

text
双向链表:每个节点包含 [前驱指针|数据|后继指针]

null ← [A] ⇄ [B] ⇄ [C] ⇄ [D] → null
        ↑                     ↑
       first                 last

插入/删除:只需要修改前后节点的指针,不需要移动其他元素
查询:需要从head或tail开始遍历

ArrayList vs LinkedList:

特性ArrayListLinkedList
底层 动态数组 双向链表
查询 快(O(1)) 慢(O(n))
增删 慢(O(n)) 快(O(1))
内存 连续空间 不连续,每个节点有额外指针
适用场景 频繁查询 频繁增删

2. Set

Java
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.TreeSet;

// ============ HashSet ============
// 无序、不可重复、允许null
// 底层:哈希表(HashMap)
HashSet<String> set = new HashSet<>();
set.add("张三");
set.add("李四");
set.add("王五");
set.add("张三");  // 重复元素不会被添加
set.add(null);    // 可以添加null

System.out.println(set);  // [null, 李四, 张三, 王五](顺序不确定)
System.out.println(set.size());        // 4
System.out.println(set.contains("张三")); // true
set.remove("李四");

// 遍历
for (String s : set) {
    System.out.println(s);
}

// ============ LinkedHashSet ============
// 有序(插入顺序)、不可重复
// 底层:哈希表 + 链表
LinkedHashSet<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("C");
linkedSet.add("A");
linkedSet.add("B");
System.out.println(linkedSet);  // [C, A, B](保持插入顺序)

// ============ TreeSet ============
// 有序(自然排序或自定义排序)、不可重复
// 底层:红黑树
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(30);
treeSet.add(10);
treeSet.add(20);
System.out.println(treeSet);  // [10, 20, 30](自动排序)

// 自定义对象需要实现Comparable接口或传入Comparator
TreeSet<Student> stuSet = new TreeSet<>((s1, s2) -> s1.getAge() - s2.getAge());

HashSet去重原理:

text
1. 计算对象的hashCode
2. 根据hashCode确定存储位置
3. 如果位置为空,直接存入
4. 如果位置不为空,调用equals比较
5. equals为true → 重复,不存入
6. equals为false → 不重复,链表/红黑树存入

所以自定义对象要放入HashSet,必须重写hashCode()和equals()方法!

3. Map

Java
import java.util.HashMap;
import java.util.Map;

// Map存储键值对(key-value),key不重复,value可以重复

// ============ 基本操作 ============
Map<String, Integer> map = new HashMap<>();

// 添加/修改
map.put("张三", 85);
map.put("李四", 92);
map.put("王五", 78);
map.put("张三", 90);  // key重复,覆盖旧值

// 获取
int score = map.get("张三");              // 90
int score2 = map.getOrDefault("赵六", 0); // key不存在返回默认值0

// 删除
map.remove("王五");

// 判断
map.containsKey("张三");      // true
map.containsValue(90);        // true
map.isEmpty();                 // false
map.size();                    // 2

// ============ 遍历 ============
// 方式1:keySet遍历
for (String key : map.keySet()) {
    System.out.println(key + " = " + map.get(key));
}

// 方式2:entrySet遍历(推荐,效率更高)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " = " + entry.getValue());
}

// 方式3:forEach(Java 8+)
map.forEach((key, value) -> System.out.println(key + " = " + value));

// 方式4:values遍历(只需要值)
for (Integer value : map.values()) {
    System.out.println(value);
}

HashMap底层原理:

text
JDK 8:数组 + 链表 + 红黑树

1. 计算key的hashCode
2. 通过hash值确定数组位置
3. 如果该位置为空,直接存入
4. 如果不为空(哈希冲突),用链表连接
5. 当链表长度 > 8 且数组长度 >= 64 时,链表转为红黑树
6. 数组默认大小16,负载因子0.75,超过12个元素就扩容(2倍)

4. Collections工具类

Java
import java.util.Collections;
import java.util.ArrayList;

ArrayList<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9));

// ============ 排序 ============
Collections.sort(list);              // 升序排序
Collections.sort(list, (a, b) -> b - a);  // 降序排序
Collections.reverse(list);          // 反转

// ============ 查找 ============
int max = Collections.max(list);    // 最大值
int min = Collections.min(list);    // 最小值
int freq = Collections.frequency(list, 1);  // 元素出现次数

// ============ 修改 ============
Collections.shuffle(list);          // 随机打乱
Collections.fill(list, 0);          // 全部填充为0
Collections.swap(list, 0, 1);       // 交换两个位置的元素
Collections.replaceAll(list, 1, 10); // 将所有1替换为10

// ============ 其他 ============
Collections.unmodifiableList(list);  // 创建不可修改的列表
Collections.synchronizedList(list);  // 创建线程安全的列表
Collections.singletonList("only");   // 创建只有一个元素的列表
Collections.emptyList();             // 创建空列表

5. Iterator和增强型for循环

Java
// ============ Iterator(迭代器) ============
// 所有Collection都可以使用Iterator遍历

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));

Iterator<String> it = list.iterator();
while (it.hasNext()) {           // 是否有下一个元素
    String element = it.next();  // 获取下一个元素
    System.out.println(element);
    
    if (element.equals("B")) {
        it.remove();  // 安全删除当前元素(Iterator的remove方法)
    }
}
// 注意:遍历时不能用list.remove()删除元素(会抛ConcurrentModificationException)
// 必须用it.remove()

// ============ 增强型for循环(for-each) ============
// 简化了Iterator的写法
// 适用于:数组、实现了Iterable接口的集合

// 遍历数组
int[] arr = {1, 2, 3, 4, 5};
for (int num : arr) {
    System.out.println(num);
}

// 遍历集合
List<String> names = Arrays.asList("张三", "李四", "王五");
for (String name : names) {
    System.out.println(name);
}

// 遍历Map
Map<String, Integer> map = new HashMap<>();
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// 注意:增强for循环中不能修改集合结构(不能添加/删除元素)
// 如果需要删除,使用Iterator

6. 泛型

Java
// 泛型(Generics):参数化类型,在编译时检查类型安全

// ============ 没有泛型的问题 ============
ArrayList list = new ArrayList();  // 没有泛型
list.add("Hello");
list.add(100);        // 可以添加任何类型
list.add(new Dog());  // 不安全!
String s = (String) list.get(1);  // 运行时ClassCastException!

// ============ 使用泛型 ============
ArrayList<String> list = new ArrayList<>();  // 限定只能存String
list.add("Hello");
// list.add(100);  // 编译错误!类型安全
String s = list.get(0);  // 不需要强制转换

// ============ 泛型类 ============
public class Box<T> {  // T是类型参数
    private T content;
    
    public void setContent(T content) {
        this.content = content;
    }
    
    public T getContent() {
        return content;
    }
}

Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String s = stringBox.getContent();  // 不需要强转

Box<Integer> intBox = new Box<>();
intBox.setContent(100);
int n = intBox.getContent();

// ============ 泛型方法 ============
public <T> void printArray(T[] array) {
    for (T element : array) {
        System.out.println(element);
    }
}

// ============ 泛型接口 ============
public interface Comparable<T> {
    int compareTo(T other);
}

public class Student implements Comparable<Student> {
    @Override
    public int compareTo(Student other) {
        return this.age - other.age;
    }
}

// ============ 通配符 ============
// ? 表示未知类型
public void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

// 上界通配符:只能读取,不能添加
public void processNumbers(List<? extends Number> list) {
    // 可以读取为Number类型
    Number n = list.get(0);
    // list.add(1);  // 编译错误!
}

// 下界通配符:可以添加,读取只能用Object
public void addIntegers(List<? super Integer> list) {
    list.add(1);   // 可以添加Integer
    list.add(2);
    Object obj = list.get(0);  // 只能用Object接收
}

泛型擦除:

text
Java泛型是"伪泛型",只在编译时检查类型
编译后泛型信息被擦除,运行时不存在泛型

ArrayList<String> 和 ArrayList<Integer> 在运行时都是 ArrayList

第九阶段:JDBC


1. JDBC基础

JDBC概念

text
JDBC(Java Database Connectivity):Java数据库连接
- Java程序访问数据库的标准接口
- Sun公司定义了一套接口规范(java.sql包)
- 各数据库厂商提供具体的实现(驱动jar包)

程序 ←→ JDBC接口 ←→ MySQL驱动 ←→ MySQL数据库
                  ←→ Oracle驱动 ←→ Oracle数据库

JDBC核心类

text
DriverManager  → 管理数据库驱动,获取数据库连接
Connection     → 数据库连接对象
Statement      → 执行SQL语句(有SQL注入风险)
PreparedStatement → 预编译SQL语句(推荐,防SQL注入)
ResultSet      → 查询结果集

JDBC基本步骤

Java
import java.sql.*;

public class JDBCDemo {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        try {
            // 1. 注册驱动(MySQL 8.0+)
            Class.forName("com.mysql.cj.jdbc.Driver");
            // MySQL 5.x: com.mysql.jdbc.Driver
            
            // 2. 获取连接
            String url = "jdbc:mysql://localhost:3306/school?useSSL=false&serverTimezone=UTC&characterEncoding=utf8";
            String username = "root";
            String password = "123456";
            conn = DriverManager.getConnection(url, username, password);
            
            // 3. 创建PreparedStatement(预编译SQL)
            String sql = "SELECT * FROM student WHERE age > ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, 20);  // 设置第1个参数为20
            
            // 4. 执行查询
            rs = pstmt.executeQuery();
            
            // 5. 处理结果集
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                System.out.println(id + " " + name + " " + age);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6. 关闭资源(后开先关)
            try {
                if (rs != null) rs.close();
                if (pstmt != null) pstmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

2. JDBC实现建表操作

Java
public static void createTable() {
    Connection conn = null;
    Statement stmt = null;
    
    try {
        Class.forName("com.mysql.cj.jdbc.Driver");
        conn = DriverManager.getConnection(
            "jdbc:mysql://localhost:3306/school", "root", "123456");
        
        stmt = conn.createStatement();
        
        String sql = "CREATE TABLE IF NOT EXISTS employee (" +
                     "id INT PRIMARY KEY AUTO_INCREMENT, " +
                     "name VARCHAR(20) NOT NULL, " +
                     "age INT, " +
                     "salary DECIMAL(10,2), " +
                     "dept VARCHAR(20)" +
                     ")";
        
        stmt.executeUpdate(sql);  // executeUpdate用于DDL和DML
        System.out.println("表创建成功");
        
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 关闭资源...
    }
}

3. JDBC实现增删改查(批量、分页)

Java
// ============ 增:INSERT ============
public static void insert() throws Exception {
    Connection conn = getConnection();
    String sql = "INSERT INTO employee (name, age, salary, dept) VALUES (?, ?, ?, ?)";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    
    pstmt.setString(1, "张三");
    pstmt.setInt(2, 25);
    pstmt.setDouble(3, 8000.00);
    pstmt.setString(4, "技术部");
    
    int rows = pstmt.executeUpdate();  // 返回受影响的行数
    System.out.println("插入了" + rows + "行");
    
    close(conn, pstmt, null);
}

// ============ 批量增:Batch Insert ============
public static void batchInsert() throws Exception {
    Connection conn = getConnection();
    conn.setAutoCommit(false);  // 关闭自动提交,提高性能
    
    String sql = "INSERT INTO employee (name, age, salary, dept) VALUES (?, ?, ?, ?)";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    
    for (int i = 0; i < 1000; i++) {
        pstmt.setString(1, "员工" + i);
        pstmt.setInt(2, 20 + i % 30);
        pstmt.setDouble(3, 5000 + Math.random() * 10000);
        pstmt.setString(4, "部门" + (i % 5));
        pstmt.addBatch();  // 添加到批处理
        
        if (i % 100 == 0) {
            pstmt.executeBatch();  // 每100条执行一次
            pstmt.clearBatch();
        }
    }
    pstmt.executeBatch();  // 执行剩余的
    conn.commit();         // 提交事务
    
    close(conn, pstmt, null);
}

// ============ 删:DELETE ============
public static void delete(int id) throws Exception {
    Connection conn = getConnection();
    String sql = "DELETE FROM employee WHERE id = ?";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setInt(1, id);
    
    int rows = pstmt.executeUpdate();
    System.out.println("删除了" + rows + "行");
    
    close(conn, pstmt, null);
}

// ============ 批量删 ============
public static void batchDelete(int[] ids) throws Exception {
    Connection conn = getConnection();
    String sql = "DELETE FROM employee WHERE id = ?";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    
    for (int id : ids) {
        pstmt.setInt(1, id);
        pstmt.addBatch();
    }
    pstmt.executeBatch();
    
    close(conn, pstmt, null);
}

// ============ 改:UPDATE ============
public static void update(int id, double newSalary) throws Exception {
    Connection conn = getConnection();
    String sql = "UPDATE employee SET salary = ? WHERE id = ?";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setDouble(1, newSalary);
    pstmt.setInt(2, id);
    
    int rows = pstmt.executeUpdate();
    System.out.println("更新了" + rows + "行");
    
    close(conn, pstmt, null);
}

// ============ 查:SELECT ============
public static void query() throws Exception {
    Connection conn = getConnection();
    String sql = "SELECT * FROM employee WHERE dept = ?";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "技术部");
    
    ResultSet rs = pstmt.executeQuery();
    while (rs.next()) {
        System.out.println(
            rs.getInt("id") + " | " +
            rs.getString("name") + " | " +
            rs.getInt("age") + " | " +
            rs.getDouble("salary") + " | " +
            rs.getString("dept")
        );
    }
    
    close(conn, pstmt, rs);
}

// ============ 分页查询 ============
public static void queryByPage(int pageNum, int pageSize) throws Exception {
    Connection conn = getConnection();
    String sql = "SELECT * FROM employee LIMIT ?, ?";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setInt(1, (pageNum - 1) * pageSize);  // offset
    pstmt.setInt(2, pageSize);                    // count
    
    ResultSet rs = pstmt.executeQuery();
    while (rs.next()) {
        // 处理数据...
    }
    
    // 查询总数
    String countSql = "SELECT COUNT(*) FROM employee";
    PreparedStatement countPstmt = conn.prepareStatement(countSql);
    ResultSet countRs = countPstmt.executeQuery();
    if (countRs.next()) {
        int total = countRs.getInt(1);
        int totalPages = (int) Math.ceil((double) total / pageSize);
        System.out.println("总共" + total + "条," + totalPages + "页");
    }
    
    close(conn, pstmt, rs);
}

详细剖析:JDBC对事务支持 及后续所有课程内容


一、事务处理:JDBC对事务支持

1.1 事务的基本概念

事务(Transaction)是数据库操作的最小工作单元,是一组不可分割的操作序列。

事务的四大特性(ACID)

text
A - Atomicity(原子性):事务中的所有操作要么全部成功,要么全部失败回滚
C - Consistency(一致性):事务执行前后,数据库从一个一致性状态变到另一个一致性状态
I - Isolation(隔离性):多个事务并发执行时,彼此互不干扰
D - Durability(持久性):事务一旦提交,对数据库的改变是永久的

经典案例:银行转账

text
张三 → 李四 转账 1000元

操作1:张三账户 -1000
操作2:李四账户 +1000

如果操作1成功,操作2失败 → 钱凭空消失了!
所以必须保证:要么两个操作都成功,要么都失败

1.2 JDBC默认的事务行为

Java
// 默认情况下,JDBC是自动提交模式
// 每执行一条SQL语句,就自动提交一次事务

Connection conn = DriverManager.getConnection(url, user, password);
// conn.getAutoCommit() == true  (默认为true,自动提交)

Statement stmt = conn.createStatement();
stmt.executeUpdate("UPDATE account SET balance = balance - 1000 WHERE name = '张三'");
// ↑ 这条SQL执行完毕后,立即自动提交,无法回滚

stmt.executeUpdate("UPDATE account SET balance = balance + 1000 WHERE name = '李四'");
// ↑ 如果这条SQL执行失败,上面的操作已经提交了,无法撤销!

1.3 JDBC手动控制事务

核心API

Java
// Connection 接口中的事务相关方法:

conn.setAutoCommit(false);  // 关闭自动提交,开启手动事务
conn.commit();              // 手动提交事务
conn.rollback();            // 回滚事务
conn.setSavepoint();        // 设置保存点
conn.rollback(savepoint);   // 回滚到保存点
conn.setTransactionIsolation(level);  // 设置事务隔离级别

完整的事务控制代码

Java
import java.sql.*;

public class TransactionDemo {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt1 = null;
        PreparedStatement pstmt2 = null;
        
        try {
            // 1. 获取连接
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/bank_db?useSSL=false&serverTimezone=UTC",
                "root", 
                "123456"
            );
            
            // ★★★ 2. 关闭自动提交(开启事务)★★★
            conn.setAutoCommit(false);
            
            // 3. 执行业务操作
            // 操作1:张三扣款
            String sql1 = "UPDATE account SET balance = balance - ? WHERE name = ?";
            pstmt1 = conn.prepareStatement(sql1);
            pstmt1.setDouble(1, 1000);
            pstmt1.setString(2, "张三");
            int rows1 = pstmt1.executeUpdate();
            
            // 模拟异常(除以零异常)
            // int x = 1 / 0;  // 取消注释可测试回滚
            
            // 操作2:李四收款
            String sql2 = "UPDATE account SET balance = balance + ? WHERE name = ?";
            pstmt2 = conn.prepareStatement(sql2);
            pstmt2.setDouble(1, 1000);
            pstmt2.setString(2, "李四");
            int rows2 = pstmt2.executeUpdate();
            
            // 4. 验证业务逻辑
            if (rows1 > 0 && rows2 > 0) {
                // ★★★ 5. 两个操作都成功,提交事务 ★★★
                conn.commit();
                System.out.println("转账成功!");
            } else {
                // 有操作未影响到数据,回滚
                conn.rollback();
                System.out.println("转账失败,已回滚!");
            }
            
        } catch (Exception e) {
            // ★★★ 6. 出现异常,回滚事务 ★★★
            try {
                if (conn != null) {
                    conn.rollback();
                    System.out.println("发生异常,事务已回滚!");
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
            
        } finally {
            // 7. 释放资源
            try {
                if (pstmt1 != null) pstmt1.close();
                if (pstmt2 != null) pstmt2.close();
                // 恢复自动提交(如果连接要归还连接池)
                if (conn != null) {
                    conn.setAutoCommit(true);
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

1.4 事务控制流程图

text
开始
conn.setAutoCommit(false)  ← 关闭自动提交
┌─────────────────┐
│  执行SQL操作1    │
│  执行SQL操作2    │
│  执行SQL操作3    │
│  ...            │
└────────┬────────┘
    是否有异常?
    ┌────┴────┐
    │         │
   无异常    有异常
    │         │
    ▼         ▼
conn.commit()  conn.rollback()
    │         │
    ▼         ▼
 提交成功    回滚成功
    │         │
    └────┬────┘
    释放资源(finally)

1.5 Savepoint(保存点)

保存点允许你回滚到事务中的某个特定点,而不是回滚整个事务。

Java
Connection conn = null;
Savepoint sp = null;

try {
    conn = getConnection();
    conn.setAutoCommit(false);
    
    // 操作1:插入订单
    String sql1 = "INSERT INTO orders(order_no, amount) VALUES(?, ?)";
    PreparedStatement pstmt1 = conn.prepareStatement(sql1);
    pstmt1.setString(1, "ORD001");
    pstmt1.setDouble(2, 5000);
    pstmt1.executeUpdate();
    
    // ★★★ 设置保存点 ★★★
    sp = conn.setSavepoint("afterOrder");
    
    // 操作2:插入订单详情(可能失败)
    try {
        String sql2 = "INSERT INTO order_detail(order_no, product, qty) VALUES(?, ?, ?)";
        PreparedStatement pstmt2 = conn.prepareStatement(sql2);
        pstmt2.setString(1, "ORD001");
        pstmt2.setString(2, "iPhone");
        pstmt2.setInt(3, -1);  // 假设数量不能为负数,触发异常
        pstmt2.executeUpdate();
    } catch (Exception e) {
        // ★★★ 回滚到保存点(只撤销操作2,保留操作1)★★★
        conn.rollback(sp);
        System.out.println("订单详情插入失败,已回滚到保存点");
        
        // 可以在这里执行替代操作
        String sql3 = "INSERT INTO order_detail(order_no, product, qty) VALUES(?, ?, ?)";
        PreparedStatement pstmt3 = conn.prepareStatement(sql3);
        pstmt3.setString(1, "ORD001");
        pstmt3.setString(2, "iPhone");
        pstmt3.setInt(3, 1);  // 使用默认数量
        pstmt3.executeUpdate();
    }
    
    conn.commit();
    
} catch (Exception e) {
    if (conn != null) conn.rollback();
    e.printStackTrace();
}

1.6 事务隔离级别

并发问题

text
┌──────────────┬───────────────────────────────────────────────────┐
│  并发问题     │  说明                                             │
├──────────────┼───────────────────────────────────────────────────┤
│  脏读        │  事务A读到了事务B未提交的数据                       │
│  (Dirty Read) │  如果事务B回滚,事务A读到的就是无效数据              │
├──────────────┼───────────────────────────────────────────────────┤
│  不可重复读   │  事务A两次读取同一数据,期间事务B修改并提交了该数据    │
│  (Non-       │  导致事务A两次读取结果不一致                        │
│  Repeatable) │                                                   │
├──────────────┼───────────────────────────────────────────────────┤
│  幻读        │  事务A按条件查询,期间事务B插入了符合条件的新数据      │
│  (Phantom)   │  导致事务A再次查询时多出了"幻影"记录               │
└──────────────┴───────────────────────────────────────────────────┘

四种隔离级别

Java
// JDBC中设置隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);  // 1
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);    // 2
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);   // 4(MySQL默认)
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);      // 8
text
┌─────────────────────────┬────────┬──────────────┬────────┐
│   隔离级别               │  脏读  │  不可重复读    │  幻读  │
├─────────────────────────┼────────┼──────────────┼────────┤
│ READ_UNCOMMITTED(读未提交)│   ✓   │      ✓       │   ✓   │
│ READ_COMMITTED(读已提交)  │   ✗   │      ✓       │   ✓   │
│ REPEATABLE_READ(可重复读) │   ✗   │      ✗       │   ✓   │
│ SERIALIZABLE(串行化)      │   ✗   │      ✗       │   ✗   │
└─────────────────────────┴────────┴──────────────┴────────┘

✓ = 可能发生    ✗ = 不会发生

隔离级别越高 → 数据越安全 → 性能越差
隔离级别越低 → 数据越不安全 → 性能越好

1.7 实际开发中的事务模板(最佳实践)

Java
/**
 * JDBC事务操作模板方法
 */
public boolean transferMoney(String fromUser, String toUser, double amount) {
    Connection conn = null;
    try {
        conn = DBUtil.getConnection();
        
        // 1. 开启事务
        conn.setAutoCommit(false);
        
        // 2. 业务操作(可能是多个DAO操作)
        AccountDao accountDao = new AccountDao();
        
        // 检查余额
        double balance = accountDao.getBalance(conn, fromUser);
        if (balance < amount) {
            throw new RuntimeException("余额不足!");
        }
        
        // 扣款
        accountDao.decreaseBalance(conn, fromUser, amount);
        
        // 加款
        accountDao.increaseBalance(conn, toUser, amount);
        
        // 记录流水
        TransactionLogDao logDao = new TransactionLogDao();
        logDao.addLog(conn, fromUser, toUser, amount);
        
        // 3. 提交事务
        conn.commit();
        return true;
        
    } catch (Exception e) {
        // 4. 回滚事务
        DBUtil.rollback(conn);
        e.printStackTrace();
        return false;
        
    } finally {
        // 5. 恢复并关闭
        DBUtil.close(conn);
    }
}

二、封装BaseDao

2.1 为什么需要BaseDao

在实际开发中,每个DAO类都需要:获取连接 → 创建Statement → 执行SQL → 处理结果集 → 关闭资源。大量重复代码!

text
没有BaseDao时:

UserDao:
  getConnection() → prepareStatement() → executeQuery() → close()
  getConnection() → prepareStatement() → executeUpdate() → close()
  
ProductDao:
  getConnection() → prepareStatement() → executeQuery() → close()
  getConnection() → prepareStatement() → executeUpdate() → close()

OrderDao:
  getConnection() → prepareStatement() → executeQuery() → close()
  getConnection() → prepareStatement() → executeUpdate() → close()

↑ 大量重复代码!

2.2 BaseDao完整实现

Java
import java.sql.*;
import java.util.*;

/**
 * 数据访问层基类
 * 封装了通用的JDBC操作
 */
public class BaseDao {
    
    // 数据库连接参数
    private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    private static final String URL = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC&characterEncoding=utf-8";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";
    
    // 静态代码块,加载驱动(只执行一次)
    static {
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("数据库驱动加载失败!", e);
        }
    }
    
    /**
     * 获取数据库连接
     */
    protected Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USER, PASSWORD);
    }
    
    /**
     * 通用的增删改方法
     * @param sql    SQL语句(带?占位符)
     * @param params 参数数组
     * @return 受影响的行数
     */
    protected int executeUpdate(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            
            // 设置参数
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    pstmt.setObject(i + 1, params[i]);
                }
            }
            
            return pstmt.executeUpdate();
            
        } catch (SQLException e) {
            throw new RuntimeException("执行更新操作失败:" + sql, e);
        } finally {
            close(null, pstmt, conn);
        }
    }
    
    /**
     * 带事务的通用增删改方法
     * @param conn   外部传入的连接(事务控制用)
     * @param sql    SQL语句
     * @param params 参数
     * @return 受影响的行数
     */
    protected int executeUpdate(Connection conn, String sql, Object... params) {
        PreparedStatement pstmt = null;
        
        try {
            pstmt = conn.prepareStatement(sql);
            
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    pstmt.setObject(i + 1, params[i]);
                }
            }
            
            return pstmt.executeUpdate();
            
        } catch (SQLException e) {
            throw new RuntimeException("执行更新操作失败:" + sql, e);
        } finally {
            close(null, pstmt, null); // 注意:不关闭conn,由外部控制
        }
    }
    
    /**
     * 批量执行增删改
     * @param sql       SQL语句
     * @param paramList 参数列表(每个Object[]对应一条SQL的参数)
     * @return 每条SQL受影响的行数数组
     */
    protected int[] executeBatch(String sql, List<Object[]> paramList) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        
        try {
            conn = getConnection();
            conn.setAutoCommit(false); // 批量操作使用事务
            pstmt = conn.prepareStatement(sql);
            
            for (Object[] params : paramList) {
                if (params != null) {
                    for (int i = 0; i < params.length; i++) {
                        pstmt.setObject(i + 1, params[i]);
                    }
                }
                pstmt.addBatch();
            }
            
            int[] results = pstmt.executeBatch();
            conn.commit();
            return results;
            
        } catch (SQLException e) {
            rollback(conn);
            throw new RuntimeException("批量操作失败:" + sql, e);
        } finally {
            close(null, pstmt, conn);
        }
    }
    
    /**
     * 查询返回单个值(如:count、max、sum等)
     */
    protected Object queryScalar(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    pstmt.setObject(i + 1, params[i]);
                }
            }
            
            rs = pstmt.executeQuery();
            if (rs.next()) {
                return rs.getObject(1);
            }
            return null;
            
        } catch (SQLException e) {
            throw new RuntimeException("查询标量值失败:" + sql, e);
        } finally {
            close(rs, pstmt, conn);
        }
    }
    
    /**
     * 查询返回List<Map<String, Object>>
     * 每个Map代表一行数据,key是列名,value是列值
     */
    protected List<Map<String, Object>> queryForList(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    pstmt.setObject(i + 1, params[i]);
                }
            }
            
            rs = pstmt.executeQuery();
            
            // 获取结果集元数据(列信息)
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            
            List<Map<String, Object>> list = new ArrayList<>();
            
            while (rs.next()) {
                Map<String, Object> row = new LinkedHashMap<>();
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnLabel(i); // 获取列别名
                    Object value = rs.getObject(i);
                    row.put(columnName, value);
                }
                list.add(row);
            }
            
            return list;
            
        } catch (SQLException e) {
            throw new RuntimeException("查询列表失败:" + sql, e);
        } finally {
            close(rs, pstmt, conn);
        }
    }
    
    /**
     * 查询返回单个Map(查询一条记录)
     */
    protected Map<String, Object> queryForMap(String sql, Object... params) {
        List<Map<String, Object>> list = queryForList(sql, params);
        return list.isEmpty() ? null : list.get(0);
    }
    
    /**
     * 分页查询
     * @param sql      原始SQL(不含LIMIT)
     * @param pageNum  当前页码(从1开始)
     * @param pageSize 每页条数
     * @param params   查询参数
     * @return 分页结果
     */
    protected Map<String, Object> queryForPage(String sql, int pageNum, int pageSize, Object... params) {
        Map<String, Object> result = new HashMap<>();
        
        // 1. 查询总记录数
        String countSql = "SELECT COUNT(*) FROM (" + sql + ") temp";
        Object countObj = queryScalar(countSql, params);
        long totalCount = countObj != null ? ((Number) countObj).longValue() : 0;
        
        // 2. 计算总页数
        long totalPages = (totalCount + pageSize - 1) / pageSize;
        
        // 3. 计算偏移量
        int offset = (pageNum - 1) * pageSize;
        
        // 4. 拼接分页SQL
        String pageSql = sql + " LIMIT " + offset + ", " + pageSize;
        List<Map<String, Object>> data = queryForList(pageSql, params);
        
        // 5. 封装结果
        result.put("data", data);           // 当前页数据
        result.put("pageNum", pageNum);      // 当前页码
        result.put("pageSize", pageSize);    // 每页条数
        result.put("totalCount", totalCount); // 总记录数
        result.put("totalPages", totalPages); // 总页数
        
        return result;
    }
    
    /**
     * 事务回滚
     */
    protected void rollback(Connection conn) {
        if (conn != null) {
            try {
                conn.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 关闭资源
     */
    protected void close(ResultSet rs, Statement stmt, Connection conn) {
        try {
            if (rs != null) rs.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (stmt != null) stmt.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

2.3 使用BaseDao的具体DAO类

Java
/**
 * 用户数据访问类
 */
public class UserDao extends BaseDao {
    
    // 添加用户
    public int addUser(String name, int age, String email) {
        String sql = "INSERT INTO user(name, age, email) VALUES(?, ?, ?)";
        return executeUpdate(sql, name, age, email);
    }
    
    // 删除用户
    public int deleteUser(int id) {
        String sql = "DELETE FROM user WHERE id = ?";
        return executeUpdate(sql, id);
    }
    
    // 修改用户
    public int updateUser(int id, String name, int age, String email) {
        String sql = "UPDATE user SET name = ?, age = ?, email = ? WHERE id = ?";
        return executeUpdate(sql, name, age, email, id);
    }
    
    // 根据ID查询
    public Map<String, Object> findById(int id) {
        String sql = "SELECT * FROM user WHERE id = ?";
        return queryForMap(sql, id);
    }
    
    // 查询所有
    public List<Map<String, Object>> findAll() {
        String sql = "SELECT * FROM user ORDER BY id";
        return queryForList(sql);
    }
    
    // 模糊查询
    public List<Map<String, Object>> findByName(String name) {
        String sql = "SELECT * FROM user WHERE name LIKE ?";
        return queryForList(sql, "%" + name + "%");
    }
    
    // 分页查询
    public Map<String, Object> findByPage(int pageNum, int pageSize) {
        String sql = "SELECT * FROM user ORDER BY id";
        return queryForPage(sql, pageNum, pageSize);
    }
    
    // 查询总数
    public int getTotalCount() {
        String sql = "SELECT COUNT(*) FROM user";
        Object result = queryScalar(sql);
        return result != null ? ((Number) result).intValue() : 0;
    }
    
    // 批量添加
    public int[] batchAddUsers(List<Object[]> users) {
        String sql = "INSERT INTO user(name, age, email) VALUES(?, ?, ?)";
        return executeBatch(sql, users);
    }
}

2.4 测试代码

Java
public class UserDaoTest {
    public static void main(String[] args) {
        UserDao userDao = new UserDao();
        
        // 1. 添加
        int rows = userDao.addUser("张三", 25, "zhangsan@qq.com");
        System.out.println("添加了 " + rows + " 条记录");
        
        // 2. 查询所有
        List<Map<String, Object>> users = userDao.findAll();
        for (Map<String, Object> user : users) {
            System.out.println(user);
            // 输出:{id=1, name=张三, age=25, email=zhangsan@qq.com}
        }
        
        // 3. 分页查询
        Map<String, Object> page = userDao.findByPage(1, 10);
        System.out.println("总记录数:" + page.get("totalCount"));
        System.out.println("总页数:" + page.get("totalPages"));
        List<Map<String, Object>> data = (List<Map<String, Object>>) page.get("data");
        for (Map<String, Object> row : data) {
            System.out.println(row);
        }
        
        // 4. 批量添加
        List<Object[]> batchUsers = new ArrayList<>();
        batchUsers.add(new Object[]{"李四", 30, "lisi@qq.com"});
        batchUsers.add(new Object[]{"王五", 28, "wangwu@qq.com"});
        batchUsers.add(new Object[]{"赵六", 35, "zhaoliu@qq.com"});
        userDao.batchAddUsers(batchUsers);
    }
}

三、请求响应机制

3.1 HTTP协议基础

text
客户端(浏览器)                         服务器(Tomcat)
    │                                      │
    │   ──── HTTP请求(Request) ────→       │
    │         GET /index.html              │
    │         POST /login                  │
    │                                      │
    │   ←── HTTP响应(Response) ────        │
    │         200 OK                       │
    │         <html>...</html>             │
    │                                      │

3.2 HTTP请求的组成

text
┌─────────────────────────────────────────────────┐
│  请求行:GET /user/list?page=1 HTTP/1.1         │
├─────────────────────────────────────────────────┤
│  请求头:                                       │
│    Host: localhost:8080                         │
│    User-Agent: Mozilla/5.0                     │
│    Content-Type: application/x-www-form-...    │
│    Accept: text/html                           │
│    Cookie: JSESSIONID=xxx                      │
├─────────────────────────────────────────────────┤
│  空行                                          │
├─────────────────────────────────────────────────┤
│  请求体(POST请求才有):                        │
│    username=admin&password=123456               │
└─────────────────────────────────────────────────┘

3.3 HTTP响应的组成

text
┌─────────────────────────────────────────────────┐
│  响应行:HTTP/1.1 200 OK                        │
├─────────────────────────────────────────────────┤
│  响应头:                                       │
│    Content-Type: text/html;charset=utf-8       │
│    Content-Length: 1024                         │
│    Set-Cookie: JSESSIONID=xxx                  │
├─────────────────────────────────────────────────┤
│  空行                                          │
├─────────────────────────────────────────────────┤
│  响应体:                                       │
│    <html><body>Hello World</body></html>       │
└─────────────────────────────────────────────────┘

3.4 常见HTTP状态码

text
200 - OK(请求成功)
301 - 永久重定向
302 - 临时重定向
304 - 未修改(使用缓存)
400 - 请求语法错误
403 - 禁止访问
404 - 资源未找到
405 - 请求方法不允许
500 - 服务器内部错误

3.5 GET与POST的区别

text
┌──────────┬────────────────────────┬────────────────────────┐
│          │        GET             │        POST            │
├──────────┼────────────────────────┼────────────────────────┤
│ 参数位置  │ URL后面(?key=value)   │ 请求体中                │
│ 数据大小  │ 有限制(约2KB)         │ 理论无限制              │
│ 安全性    │ 参数暴露在URL中         │ 参数在请求体中,相对安全  │
│ 缓存     │ 可以被缓存              │ 不会被缓存              │
│ 书签     │ 可以保存为书签           │ 不可以                  │
│ 用途     │ 获取数据                │ 提交数据                │
└──────────┴────────────────────────┴────────────────────────┘

四、Tomcat配置

4.1 Tomcat目录结构

text
apache-tomcat-9.x/
├── bin/              ← 启动/关闭脚本
│   ├── startup.bat   ← Windows启动
│   ├── startup.sh    ← Linux启动
│   ├── shutdown.bat  ← Windows关闭
│   └── shutdown.sh   ← Linux关闭
├── conf/             ← 配置文件
│   ├── server.xml    ← 服务器核心配置(端口等)
│   ├── web.xml       ← 全局Web应用配置
│   └── context.xml   ← 上下文配置
├── lib/              ← 服务器依赖的jar包
├── logs/             ← 日志文件
├── temp/             ← 临时文件
├── webapps/          ← ★ Web应用部署目录
│   ├── ROOT/         ← 默认根应用
│   └── examples/     ← 示例应用
└── work/             ← 编译后的文件(JSP编译等)

4.2 核心配置(server.xml)

XML
<!-- 修改端口号(默认8080) -->
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443"
           URIEncoding="UTF-8" />

<!-- 配置虚拟主机 -->
<Host name="localhost" appBase="webapps"
      unpackWARs="true" autoDeploy="true">
    
    <!-- 配置项目的上下文路径 -->
    <Context path="/myapp" docBase="D:/projects/myapp" reloadable="true" />
</Host>

4.3 IDEA中配置Tomcat

text
1. 菜单:Run → Edit Configurations → + → Tomcat Server → Local

2. Server标签页:
   - Application Server:选择Tomcat安装目录
   - HTTP Port:8080
   - JMX Port:1099

3. Deployment标签页:
   - 点击 + → Artifact → 选择项目的 war exploded
   - Application context:/myapp(访问路径)

4. 启动:点击绿色三角按钮

五、Web应用部署

5.1 Web项目目录结构

text
mywebapp/
├── src/                    ← Java源代码
│   ├── com.example.servlet/
│   ├── com.example.dao/
│   ├── com.example.entity/
│   └── com.example.util/
├── web/                    ← Web资源根目录
│   ├── WEB-INF/            ← ★ 受保护目录(浏览器不能直接访问)
│   │   ├── web.xml         ← Web应用配置文件
│   │   ├── classes/        ← 编译后的.class文件
│   │   └── lib/            ← 项目依赖的jar包
│   ├── index.html          ← 首页
│   ├── css/                ← CSS文件
│   ├── js/                 ← JavaScript文件
│   └── images/             ← 图片资源

5.2 web.xml配置

XML
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <!-- 欢迎页面 -->
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    
    <!-- Servlet配置 -->
    <servlet>
        <servlet-name>UserServlet</servlet-name>
        <servlet-class>com.example.servlet.UserServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>UserServlet</servlet-name>
        <url-pattern>/user</url-pattern>
    </servlet-mapping>
    
    <!-- 过滤器配置 -->
    <filter>
        <filter-name>EncodingFilter</filter-name>
        <filter-class>com.example.filter.EncodingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>EncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
</web-app>

六、MVC设计模式

6.1 MVC概念

text
┌──────────────────────────────────────────────────────────┐
│                    MVC 设计模式                           │
│                                                          │
│  M - Model(模型层)                                      │
│      负责数据处理、业务逻辑                                │
│      对应:JavaBean + DAO + Service                       │
│                                                          │
│  V - View(视图层)                                       │
│      负责数据展示、用户界面                                │
│      对应:HTML / JSP / Vue前端页面                        │
│                                                          │
│  C - Controller(控制层)                                  │
│      负责接收请求、调用模型、返回视图                       │
│      对应:Servlet                                        │
└──────────────────────────────────────────────────────────┘

6.2 MVC工作流程

text
用户(浏览器)
    │ ① 发送请求(点击、提交表单等)
Controller (Servlet)
    │ ② 接收请求,解析参数
    │ ③ 调用Model处理业务
Model (Service → DAO → 数据库)
    │ ④ 处理业务逻辑,返回数据
Controller (Servlet)
    │ ⑤ 将数据传给视图 或 直接返回JSON
View (HTML/前端页面)
    │ ⑥ 渲染数据,展示给用户
用户(浏览器)

6.3 三层架构 vs MVC

text
┌────────────────┐    ┌────────────────┐
│   三层架构      │    │     MVC        │
├────────────────┤    ├────────────────┤
│   表现层        │ ←→ │  View + Ctrl   │
│ (Servlet+HTML)  │    │                │
├────────────────┤    ├────────────────┤
│   业务逻辑层    │ ←→ │    Model       │
│   (Service)     │    │  (Service)     │
├────────────────┤    │                │
│   数据访问层    │ ←→ │    Model       │
│   (DAO)        │    │   (DAO)        │
└────────────────┘    └────────────────┘

6.4 项目分层示例

text
src/
├── com.example.entity/         ← 实体类(JavaBean)
│   └── User.java
├── com.example.dao/            ← 数据访问层
│   ├── BaseDao.java
│   └── UserDao.java
├── com.example.service/        ← 业务逻辑层
│   ├── UserService.java
│   └── UserServiceImpl.java
├── com.example.servlet/        ← 控制层
│   └── UserServlet.java
├── com.example.filter/         ← 过滤器
│   └── EncodingFilter.java
└── com.example.util/           ← 工具类
    └── DBUtil.java

七、Servlet与Filter

7.1 Servlet基本概念

Servlet是运行在服务器端的Java程序,用于接收和处理HTTP请求,并生成响应。

Java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

/**
 * Servlet基础示例
 */
public class HelloServlet extends HttpServlet {
    
    /**
     * 处理GET请求
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        // 设置响应内容类型和编码
        response.setContentType("text/html;charset=UTF-8");
        
        // 获取输出流
        PrintWriter out = response.getWriter();
        out.println("<h1>Hello Servlet!</h1>");
        out.println("<p>当前时间:" + new java.util.Date() + "</p>");
    }
    
    /**
     * 处理POST请求
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        doGet(request, response);  // 通常POST和GET共用逻辑
    }
}

7.2 Servlet生命周期

text
┌─────────────────────────────────────────────────────────────┐
│                  Servlet 生命周期                            │
│                                                             │
│  ① 加载和实例化                                              │
│     Tomcat启动或第一次请求时,创建Servlet实例                  │
│     ↓                                                       │
│  ② init() 初始化                                            │
│     只执行一次,用于初始化资源                                │
│     ↓                                                       │
│  ③ service() → doGet() / doPost()  服务                     │
│     每次请求都会调用,处理客户端请求                           │
│     ↓                                                       │
│  ④ destroy() 销毁                                           │
│     服务器关闭时执行一次,释放资源                            │
│                                                             │
│  时间线:                                                   │
│  ───────┬───────┬──────────────────────────┬───────         │
│         init   service(多次)              destroy           │
│                                                             │
│  注意:Servlet是单例的!所有请求共享同一个Servlet实例          │
└─────────────────────────────────────────────────────────────┘
Java
public class LifecycleServlet extends HttpServlet {
    
    // ① 构造方法(实例化)
    public LifecycleServlet() {
        System.out.println("1. Servlet 构造方法被调用(实例化)");
    }
    
    // ② 初始化(只执行一次)
    @Override
    public void init() throws ServletException {
        System.out.println("2. init() 初始化方法被调用");
        // 可以在这里初始化数据库连接池等资源
    }
    
    // ③ 处理请求(每次请求都执行)
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        System.out.println("3. doGet() 处理请求");
    }
    
    // ④ 销毁(服务器关闭时执行一次)
    @Override
    public void destroy() {
        System.out.println("4. destroy() 销毁方法被调用");
        // 释放资源
    }
}

7.3 HttpServletRequest 常用方法

Java
protected void doGet(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException {
    
    // ===== 获取请求参数 =====
    String username = request.getParameter("username");        // 获取单个参数
    String[] hobbies = request.getParameterValues("hobby");    // 获取多值参数(复选框)
    Map<String, String[]> paramMap = request.getParameterMap(); // 获取所有参数
    
    // ===== 获取请求信息 =====
    String method = request.getMethod();              // GET 或 POST
    String uri = request.getRequestURI();             // /myapp/user
    String url = request.getRequestURL().toString();   // http://localhost:8080/myapp/user
    String queryString = request.getQueryString();     // page=1&size=10
    String contextPath = request.getContextPath();     // /myapp
    String remoteAddr = request.getRemoteAddr();       // 客户端IP
    
    // ===== 获取请求头 =====
    String userAgent = request.getHeader("User-Agent");
    String contentType = request.getContentType();
    
    // ===== 设置请求编码(处理中文乱码)=====
    request.setCharacterEncoding("UTF-8");
    
    // ===== 域对象(存储数据)=====
    request.setAttribute("user", userObj);             // 存储数据
    Object user = request.getAttribute("user");         // 获取数据
    request.removeAttribute("user");                    // 移除数据
    
    // ===== 请求转发 =====
    request.getRequestDispatcher("/WEB-INF/page.html")
           .forward(request, response);
    
    // ===== Session相关 =====
    HttpSession session = request.getSession();
    session.setAttribute("loginUser", user);
    session.getAttribute("loginUser");
    session.invalidate();  // 销毁session
}

7.4 HttpServletResponse 常用方法

Java
protected void doGet(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException {
    
    // ===== 设置响应编码 =====
    response.setContentType("text/html;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");
    
    // ===== 输出响应内容 =====
    PrintWriter out = response.getWriter();  // 字符输出流
    out.println("<h1>Hello</h1>");
    
    // 或者使用字节输出流(用于文件下载等)
    // OutputStream os = response.getOutputStream();
    
    // ===== 重定向 =====
    response.sendRedirect("http://www.baidu.com");
    response.sendRedirect(request.getContextPath() + "/login.html");
    
    // ===== 设置响应头 =====
    response.setHeader("Content-Disposition", "attachment;filename=test.txt"); // 文件下载
    response.setHeader("Cache-Control", "no-cache"); // 禁止缓存
    
    // ===== 设置状态码 =====
    response.setStatus(200);  // 正常
    response.sendError(404, "页面未找到");
    
    // ===== 返回JSON数据(前后端分离常用)=====
    response.setContentType("application/json;charset=UTF-8");
    PrintWriter writer = response.getWriter();
    writer.write("{\"code\":200, \"msg\":\"success\", \"data\":[]}");
}

7.5 转发 vs 重定向

text
┌──────────────┬──────────────────────┬──────────────────────┐
│              │    转发(forward)      │    重定向(redirect)   │
├──────────────┼──────────────────────┼──────────────────────┤
│ 浏览器地址栏  │ 不变                  │ 变化(显示新地址)     │
│ 请求次数      │ 1次                  │ 2次                   │
│ 是否共享数据  │ 共享request域的数据    │ 不共享                │
│ 能否访问      │ 可以访问WEB-INF下资源  │ 不能                  │
│ WEB-INF      │                      │                      │
│ 跳转范围      │ 只能在本项目内         │ 可以跳转到任意URL      │
│ 谁来跳转      │ 服务器内部跳转         │ 浏览器重新发起请求     │
└──────────────┴──────────────────────┴──────────────────────┘

转发:
浏览器 ──请求──→ ServletA ──转发──→ ServletB ──响应──→ 浏览器
                (request域数据共享)

重定向:
浏览器 ──请求1──→ ServletA ──响应302──→ 浏览器 ──请求2──→ ServletB

7.6 Filter(过滤器)

Java
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;

/**
 * 编码过滤器(解决中文乱码)
 */
public class EncodingFilter implements Filter {
    
    private String encoding = "UTF-8";
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String enc = filterConfig.getInitParameter("encoding");
        if (enc != null) {
            this.encoding = enc;
        }
        System.out.println("EncodingFilter 初始化,编码:" + encoding);
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        
        // 请求到达Servlet之前的处理
        request.setCharacterEncoding(encoding);
        response.setCharacterEncoding(encoding);
        response.setContentType("text/html;charset=" + encoding);
        
        System.out.println("请求到达Servlet之前...");
        
        // ★★★ 放行:让请求继续到达Servlet ★★★
        chain.doFilter(request, response);
        
        System.out.println("Servlet处理完毕之后...");
        // 响应返回浏览器之前的处理
    }
    
    @Override
    public void destroy() {
        System.out.println("EncodingFilter 销毁");
    }
}
text
Filter执行流程:

浏览器 ──请求──→ Filter1 ──→ Filter2 ──→ Servlet
                  │            │           │
                  │            │           │(处理请求)
                  │            │           │
浏览器 ←─响应── Filter1 ←── Filter2 ←── Servlet

Filter链:按web.xml中配置的顺序依次执行

7.7 完整的用户登录案例

实体类

Java
// User.java
public class User {
    private int id;
    private String username;
    private String password;
    private String realName;
    private String email;
    
    // 构造方法
    public User() {}
    
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
    
    // getter/setter省略...
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    public String getRealName() { return realName; }
    public void setRealName(String realName) { this.realName = realName; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

DAO层

Java
// UserDao.java
public class UserDao extends BaseDao {
    
    /**
     * 根据用户名和密码查询用户(登录验证)
     */
    public User findByUsernameAndPassword(String username, String password) {
        String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
        Map<String, Object> map = queryForMap(sql, username, password);
        
        if (map == null) return null;
        
        User user = new User();
        user.setId((Integer) map.get("id"));
        user.setUsername((String) map.get("username"));
        user.setPassword((String) map.get("password"));
        user.setRealName((String) map.get("real_name"));
        user.setEmail((String) map.get("email"));
        
        return user;
    }
    
    /**
     * 分页查询用户列表
     */
    public List<User> findByPage(int pageNum, int pageSize) {
        String sql = "SELECT * FROM user ORDER BY id LIMIT ?, ?";
        int offset = (pageNum - 1) * pageSize;
        List<Map<String, Object>> list = queryForList(sql, offset, pageSize);
        
        List<User> users = new ArrayList<>();
        for (Map<String, Object> map : list) {
            User user = new User();
            user.setId((Integer) map.get("id"));
            user.setUsername((String) map.get("username"));
            user.setRealName((String) map.get("real_name"));
            user.setEmail((String) map.get("email"));
            users.add(user);
        }
        return users;
    }
}

Servlet层

Java
// LoginServlet.java
import javax.servlet.annotation.WebServlet;

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    
    private UserDao userDao = new UserDao();
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        request.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=UTF-8");
        
        // 1. 获取参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        
        // 2. 调用DAO验证
        User user = userDao.findByUsernameAndPassword(username, password);
        
        // 3. 返回JSON结果
        PrintWriter out = response.getWriter();
        
        if (user != null) {
            // 登录成功,保存到Session
            HttpSession session = request.getSession();
            session.setAttribute("loginUser", user);
            
            out.write("{\"code\":200, \"msg\":\"登录成功\", \"data\":{\"username\":\"" 
                      + user.getUsername() + "\"}}");
        } else {
            out.write("{\"code\":401, \"msg\":\"用户名或密码错误\"}");
        }
    }
}

八、$.ajax()方法数据交互

8.1 jQuery Ajax基础

JavaScript
// 最基本的ajax请求
$.ajax({
    url: '/login',           // 请求地址
    type: 'POST',            // 请求方式
    data: {                  // 发送的数据
        username: 'admin',
        password: '123456'
    },
    dataType: 'json',        // 期望服务器返回的数据类型
    success: function(result) {   // 成功回调
        console.log(result);
        if (result.code === 200) {
            alert('登录成功!');
            window.location.href = '/index.html';
        } else {
            alert(result.msg);
        }
    },
    error: function(xhr, status, error) {  // 失败回调
        console.log('请求失败:' + error);
        alert('服务器异常,请稍后重试');
    }
});

8.2 完整的登录页面

HTML
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
    <script src="js/jquery-3.6.0.min.js"></script>
</head>
<body>
    <h2>用户登录</h2>
    <form id="loginForm">
        <p>用户名:<input type="text" name="username" id="username"></p>
        <p>密  码:<input type="password" name="password" id="password"></p>
        <p><button type="button" id="btnLogin">登录</button></p>
    </form>
    
    <script>
        $('#btnLogin').click(function() {
            var username = $('#username').val().trim();
            var password = $('#password').val().trim();
            
            // 前端验证
            if (!username) {
                alert('请输入用户名');
                return;
            }
            if (!password) {
                alert('请输入密码');
                return;
            }
            
            // 发送Ajax请求
            $.ajax({
                url: '/login',
                type: 'POST',
                data: {
                    username: username,
                    password: password
                },
                dataType: 'json',
                success: function(result) {
                    if (result.code === 200) {
                        alert('欢迎回来,' + result.data.username);
                        window.location.href = '/index.html';
                    } else {
                        alert(result.msg);
                    }
                },
                error: function() {
                    alert('服务器繁忙,请稍后重试');
                }
            });
        });
    </script>
</body>
</html>

8.3 CRUD完整示例(用户管理)

JavaScript
// ===== 查询用户列表(分页)=====
function loadUsers(pageNum) {
    $.ajax({
        url: '/user',
        type: 'GET',
        data: { action: 'list', page: pageNum, size: 10 },
        dataType: 'json',
        success: function(result) {
            if (result.code === 200) {
                var html = '';
                var users = result.data.list;
                for (var i = 0; i < users.length; i++) {
                    html += '<tr>';
                    html += '<td>' + users[i].id + '</td>';
                    html += '<td>' + users[i].username + '</td>';
                    html += '<td>' + users[i].realName + '</td>';
                    html += '<td>' + users[i].email + '</td>';
                    html += '<td>';
                    html += '<button onclick="editUser(' + users[i].id + ')">编辑</button> ';
                    html += '<button onclick="deleteUser(' + users[i].id + ')">删除</button>';
                    html += '</td>';
                    html += '</tr>';
                }
                $('#userTableBody').html(html);
                
                // 渲染分页
                renderPagination(result.data.pageNum, result.data.totalPages);
            }
        }
    });
}

// ===== 添加用户 =====
function addUser() {
    var data = {
        action: 'add',
        username: $('#addUsername').val(),
        password: $('#addPassword').val(),
        realName: $('#addRealName').val(),
        email: $('#addEmail').val()
    };
    
    $.ajax({
        url: '/user',
        type: 'POST',
        data: data,
        dataType: 'json',
        success: function(result) {
            if (result.code === 200) {
                alert('添加成功!');
                loadUsers(1);  // 刷新列表
                $('#addModal').hide();  // 关闭弹窗
            } else {
                alert(result.msg);
            }
        }
    });
}

// ===== 删除用户 =====
function deleteUser(id) {
    if (!confirm('确定要删除吗?')) return;
    
    $.ajax({
        url: '/user',
        type: 'POST',
        data: { action: 'delete', id: id },
        dataType: 'json',
        success: function(result) {
            if (result.code === 200) {
                alert('删除成功!');
                loadUsers(1);
            } else {
                alert(result.msg);
            }
        }
    });
}

// ===== 修改用户 =====
function updateUser() {
    var data = {
        action: 'update',
        id: $('#editId').val(),
        username: $('#editUsername').val(),
        realName: $('#editRealName').val(),
        email: $('#editEmail').val()
    };
    
    $.ajax({
        url: '/user',
        type: 'POST',
        data: data,
        dataType: 'json',
        success: function(result) {
            if (result.code === 200) {
                alert('修改成功!');
                loadUsers(1);
                $('#editModal').hide();
            } else {
                alert(result.msg);
            }
        }
    });
}

8.4 对应的UserServlet

Java
@WebServlet("/user")
public class UserServlet extends HttpServlet {
    
    private UserDao userDao = new UserDao();
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        request.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=UTF-8");
        
        String action = request.getParameter("action");
        PrintWriter out = response.getWriter();
        
        if ("list".equals(action)) {
            // 分页查询
            int page = Integer.parseInt(request.getParameter("page"));
            int size = Integer.parseInt(request.getParameter("size"));
            
            List<User> users = userDao.findByPage(page, size);
            int total = userDao.getTotalCount();
            int totalPages = (total + size - 1) / size;
            
            // 手动拼接JSON(实际项目用JSON库如Gson、Jackson)
            StringBuilder json = new StringBuilder();
            json.append("{\"code\":200,\"data\":{");
            json.append("\"pageNum\":").append(page).append(",");
            json.append("\"totalPages\":").append(totalPages).append(",");
            json.append("\"list\":[");
            
            for (int i = 0; i < users.size(); i++) {
                User u = users.get(i);
                if (i > 0) json.append(",");
                json.append("{\"id\":").append(u.getId());
                json.append(",\"username\":\"").append(u.getUsername()).append("\"");
                json.append(",\"realName\":\"").append(u.getRealName()).append("\"");
                json.append(",\"email\":\"").append(u.getEmail()).append("\"}");
            }
            
            json.append("]}}");
            out.write(json.toString());
        }
    }
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        request.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=UTF-8");
        
        String action = request.getParameter("action");
        PrintWriter out = response.getWriter();
        
        switch (action) {
            case "add":
                handleAdd(request, out);
                break;
            case "delete":
                handleDelete(request, out);
                break;
            case "update":
                handleUpdate(request, out);
                break;
            default:
                out.write("{\"code\":400,\"msg\":\"未知操作\"}");
        }
    }
    
    private void handleAdd(HttpServletRequest request, PrintWriter out) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String realName = request.getParameter("realName");
        String email = request.getParameter("email");
        
        int rows = userDao.addUser(username, password, realName, email);
        
        if (rows > 0) {
            out.write("{\"code\":200,\"msg\":\"添加成功\"}");
        } else {
            out.write("{\"code\":500,\"msg\":\"添加失败\"}");
        }
    }
    
    private void handleDelete(HttpServletRequest request, PrintWriter out) {
        int id = Integer.parseInt(request.getParameter("id"));
        int rows = userDao.deleteUser(id);
        
        if (rows > 0) {
            out.write("{\"code\":200,\"msg\":\"删除成功\"}");
        } else {
            out.write("{\"code\":500,\"msg\":\"删除失败\"}");
        }
    }
    
    private void handleUpdate(HttpServletRequest request, PrintWriter out) {
        int id = Integer.parseInt(request.getParameter("id"));
        String username = request.getParameter("username");
        String realName = request.getParameter("realName");
        String email = request.getParameter("email");
        
        int rows = userDao.updateUser(id, username, realName, email);
        
        if (rows > 0) {
            out.write("{\"code\":200,\"msg\":\"修改成功\"}");
        } else {
            out.write("{\"code\":500,\"msg\":\"修改失败\"}");
        }
    }
}

九、JavaScript(完整部分)

9.1 数据类型与类型转换

JavaScript
// ===== 基本数据类型 =====
var num = 100;           // Number(数字)
var str = "Hello";       // String(字符串)
var bool = true;         // Boolean(布尔)
var undef;               // undefined(未定义)
var n = null;            // null(空)

// ===== typeof 检测类型 =====
console.log(typeof num);    // "number"
console.log(typeof str);    // "string"
console.log(typeof bool);   // "boolean"
console.log(typeof undef);  // "undefined"
console.log(typeof n);      // "object"(这是JS的一个历史bug)
console.log(typeof [1,2]);  // "object"
console.log(typeof {});     // "object"

// ===== 类型转换 =====

// 1. 转为Number
var a = Number("123");        // 123
var b = Number("123abc");     // NaN(Not a Number)
var c = Number(true);         // 1
var d = Number(false);        // 0
var e = Number(null);         // 0
var f = Number(undefined);    // NaN

var g = parseInt("123abc");   // 123(解析整数,遇到非数字停止)
var h = parseInt("abc123");   // NaN
var i = parseFloat("3.14");   // 3.14
var j = parseInt("0xFF", 16); // 255(指定进制)

// 2. 转为String
var s1 = String(123);         // "123"
var s2 = String(true);        // "true"
var s3 = (123).toString();    // "123"
var s4 = 123 + "";            // "123"(隐式转换)

// 3. 转为Boolean
var b1 = Boolean(0);          // false
var b2 = Boolean("");         // false
var b3 = Boolean(null);       // false
var b4 = Boolean(undefined);  // false
var b5 = Boolean(NaN);        // false
// 以上5种为false,其他所有值都是true
var b6 = Boolean("hello");   // true
var b7 = Boolean(1);          // true
var b8 = Boolean([]);         // true(空数组也是true!)
var b9 = Boolean({});         // true(空对象也是true!)

// ===== == 与 === 的区别 =====
console.log(1 == "1");    // true(会自动类型转换)
console.log(1 === "1");   // false(严格比较,不做类型转换)
console.log(null == undefined);   // true
console.log(null === undefined);  // false

9.2 变量、alert、console、运算符

JavaScript
// ===== 变量声明 =====
var x = 10;        // var声明(函数作用域,可重复声明)
let y = 20;        // let声明(块级作用域,不可重复声明)ES6
const z = 30;      // const声明(常量,不可修改)ES6

// ===== 输出方式 =====
alert("弹窗提示");              // 弹出对话框
console.log("控制台输出");       // 控制台输出(最常用)
console.warn("警告信息");        // 黄色警告
console.error("错误信息");       // 红色错误
document.write("页面输出");      // 直接写到页面上

// ===== 算术运算符 =====
console.log(10 / 3);    // 3.3333...(JS中没有整数除法)
console.log(10 % 3);    // 1(取余)
console.log("10" - 5);  // 5(减法会自动转数字)
console.log("10" + 5);  // "105"(加法遇到字符串变拼接!)

// ===== 特殊比较 =====
console.log(NaN == NaN);     // false(NaN不等于任何值,包括自身)
console.log(isNaN(NaN));     // true(用isNaN来判断)
console.log(isNaN("abc"));   // true
console.log(isNaN(123));     // false

9.3 控制语句

JavaScript
// ===== 选择结构 =====
// if-else
var score = 85;
if (score >= 90) {
    console.log("优秀");
} else if (score >= 80) {
    console.log("良好");
} else if (score >= 60) {
    console.log("及格");
} else {
    console.log("不及格");
}

// switch
var day = new Date().getDay(); // 0-6
switch (day) {
    case 0: console.log("星期日"); break;
    case 1: console.log("星期一"); break;
    case 6: console.log("星期六"); break;
    default: console.log("工作日");
}

// 三元运算符
var age = 18;
var status = age >= 18 ? "成年" : "未成年";

// ===== 循环结构 =====
// for循环
for (var i = 0; i < 10; i++) {
    console.log(i);
}

// while循环
var count = 0;
while (count < 5) {
    console.log(count);
    count++;
}

// do-while循环(至少执行一次)
var num = 10;
do {
    console.log(num);
    num--;
} while (num > 0);

// for...in(遍历对象属性)
var person = {name: "张三", age: 25, city: "北京"};
for (var key in person) {
    console.log(key + ": " + person[key]);
}

// for...of(遍历可迭代对象)ES6
var arr = [10, 20, 30];
for (var value of arr) {
    console.log(value);
}

9.4 数组与字符串

JavaScript
// ===== 数组 =====
var arr1 = [1, 2, 3, 4, 5];
var arr2 = new Array(3);        // 创建长度为3的空数组
var arr3 = new Array(1, 2, 3);  // 创建包含1,2,3的数组

// 数组常用方法
arr1.push(6);           // 末尾添加 → [1,2,3,4,5,6]
arr1.pop();              // 末尾删除 → [1,2,3,4,5]
arr1.unshift(0);         // 开头添加 → [0,1,2,3,4,5]
arr1.shift();            // 开头删除 → [1,2,3,4,5]
arr1.splice(2, 1);       // 从索引2删除1个 → [1,2,4,5]
arr1.splice(2, 0, 3);    // 从索引2插入3 → [1,2,3,4,5]
arr1.indexOf(3);         // 查找元素位置 → 2
arr1.includes(3);        // 是否包含 → true
arr1.join("-");          // 拼接为字符串 → "1-2-3-4-5"
arr1.reverse();          // 反转
arr1.sort();             // 排序

// 数组遍历
arr1.forEach(function(item, index) {
    console.log(index + ": " + item);
});

// map(映射,生成新数组)
var doubled = arr1.map(function(item) {
    return item * 2;
});

// filter(过滤)
var evens = arr1.filter(function(item) {
    return item % 2 === 0;
});

// ===== 字符串 =====
var str = "Hello World";
str.length;              // 11
str.charAt(0);           // "H"
str.indexOf("World");    // 6
str.lastIndexOf("l");    // 9
str.substring(0, 5);     // "Hello"
str.slice(0, 5);         // "Hello"
str.slice(-5);           // "World"
str.toUpperCase();       // "HELLO WORLD"
str.toLowerCase();       // "hello world"
str.trim();              // 去除前后空格
str.split(" ");          // ["Hello", "World"]
str.replace("World", "JS");  // "Hello JS"
str.startsWith("Hello"); // true
str.endsWith("World");   // true
str.includes("llo");     // true

9.5 Chrome程序调试

text
1. 打开开发者工具:F12 或 Ctrl+Shift+I

2. Console面板:
   - 查看console.log输出
   - 直接执行JS代码
   - 查看错误信息

3. Sources面板(调试):
   - 找到JS文件
   - 点击行号设置断点
   - F5刷新页面触发断点
   - F10:逐过程执行(不进入函数)
   - F11:逐语句执行(进入函数)
   - Shift+F11:跳出函数
   - F8:继续执行到下一个断点
   - 右侧Watch:监视变量值
   - 右侧Scope:查看作用域中的变量

4. Network面板:
   - 查看网络请求
   - 查看请求/响应头
   - 查看请求/响应数据
   - 查看请求耗时

5. Elements面板:
   - 查看/修改DOM结构
   - 查看/修改CSS样式

9.6 系统函数

JavaScript
// ===== 对话框函数 =====
alert("提示信息");                        // 弹出提示框
var result = confirm("确定删除吗?");      // 确认框,返回true/false
var input = prompt("请输入姓名:", "张三"); // 输入框,返回输入值或null

// ===== 类型转换函数 =====
parseInt("123");        // 123
parseFloat("3.14");     // 3.14
Number("123");          // 123
String(123);            // "123"
Boolean(0);             // false
isNaN("abc");           // true
isFinite(100);          // true
isFinite(Infinity);     // false

// ===== 编码函数 =====
encodeURI("https://www.example.com/搜索");            // 编码URL(不编码特殊字符)
decodeURI("https://www.example.com/%E6%90%9C%E7%B4%A2"); // 解码URL
encodeURIComponent("name=张三&age=25");               // 编码组件(编码所有特殊字符)
decodeURIComponent("name%3D%E5%BC%A0%E4%B8%89");

// ===== eval 函数 =====
eval("1 + 2");    // 3(将字符串作为代码执行,不推荐使用)

// ===== Math对象(不是函数,是对象)=====
Math.PI;                // 3.141592653589793
Math.abs(-10);          // 10(绝对值)
Math.ceil(3.1);         // 4(向上取整)
Math.floor(3.9);        // 3(向下取整)
Math.round(3.5);        // 4(四舍五入)
Math.max(1, 2, 3);      // 3
Math.min(1, 2, 3);      // 1
Math.random();           // 0~1之间的随机小数
Math.floor(Math.random() * 100); // 0~99之间的随机整数
Math.pow(2, 10);         // 1024(2的10次方)
Math.sqrt(16);           // 4(平方根)

9.7 自定义函数

JavaScript
// ===== 函数声明方式 =====

// 1. function声明(函数提升,可以在声明前调用)
function add(a, b) {
    return a + b;
}

// 2. 函数表达式(不会提升)
var subtract = function(a, b) {
    return a - b;
};

// 3. 箭头函数(ES6)
var multiply = (a, b) => a * b;
var square = x => x * x;  // 单参数可省略括号
var greet = () => console.log("Hello");  // 无参数

// ===== 参数 =====
function test(a, b) {
    console.log(a, b);
}
test(1);          // a=1, b=undefined(JS不强制参数个数匹配)
test(1, 2, 3);    // a=1, b=2, 3被忽略

// 默认参数(ES6)
function greet(name = "游客") {
    console.log("你好," + name);
}
greet();        // "你好,游客"
greet("张三");  // "你好,张三"

// 剩余参数(ES6)
function sum(...numbers) {
    return numbers.reduce((acc, cur) => acc + cur, 0);
}
sum(1, 2, 3, 4, 5);  // 15

// arguments对象(非箭头函数中可用)
function showArgs() {
    console.log(arguments);        // 类数组对象
    console.log(arguments.length); // 参数个数
    for (var i = 0; i < arguments.length; i++) {
        console.log(arguments[i]);
    }
}

// ===== 返回值 =====
function divide(a, b) {
    if (b === 0) {
        return null;  // 提前返回
    }
    return a / b;
}

// 返回多个值(通过对象/数组)
function getMinMax(arr) {
    return {
        min: Math.min(...arr),
        max: Math.max(...arr)
    };
}
var result = getMinMax([3, 1, 5, 2, 4]);
console.log(result.min, result.max);  // 1 5

// ===== 回调函数 =====
function calculate(a, b, operation) {
    return operation(a, b);
}
var result = calculate(10, 5, function(x, y) {
    return x + y;
});
console.log(result);  // 15

9.8 变量的作用域

JavaScript
// ===== var 的作用域(函数作用域)=====
function test() {
    var x = 10;  // 函数内部声明,外部不可访问
    if (true) {
        var y = 20;  // var没有块级作用域!
    }
    console.log(y);  // 20(可以访问!)
}
// console.log(x);  // 报错:x is not defined

// ===== let 的作用域(块级作用域)=====
function test2() {
    let a = 10;
    if (true) {
        let b = 20;  // 块级作用域
        console.log(a);  // 10
    }
    // console.log(b);  // 报错:b is not defined
}

// ===== 全局变量 vs 局部变量 =====
var globalVar = "我是全局变量";  // 全局

function demo() {
    var localVar = "我是局部变量";
    
    // 没有用var/let/const声明的变量自动成为全局变量(严格模式下报错)
    implicitGlobal = "我也是全局变量";
}
demo();
console.log(implicitGlobal);  // 可以访问
// console.log(localVar);     // 报错

// ===== 闭包 =====
function outer() {
    var count = 0;
    
    return function() {
        count++;
        console.log(count);
    };
}

var counter = outer();
counter();  // 1
counter();  // 2
counter();  // 3
// count变量被内部函数"记住"了,不会被垃圾回收

9.9 定时函数

JavaScript
// ===== setTimeout(延迟执行,只执行一次)=====
var timer1 = setTimeout(function() {
    console.log("3秒后执行");
}, 3000);

// 清除延迟
clearTimeout(timer1);

// ===== setInterval(定时执行,重复执行)=====
var timer2 = setInterval(function() {
    console.log("每隔1秒执行一次:" + new Date().toLocaleTimeString());
}, 1000);

// 清除定时
clearInterval(timer2);

// ===== 实际应用:倒计时 =====
function countdown(seconds) {
    var remaining = seconds;
    
    var timer = setInterval(function() {
        if (remaining <= 0) {
            clearInterval(timer);
            console.log("时间到!");
            return;
        }
        console.log("剩余时间:" + remaining + "秒");
        remaining--;
    }, 1000);
}
countdown(10);

// ===== 实际应用:按钮倒计时(防止重复提交)=====
function startCountdown(btn) {
    var seconds = 60;
    btn.disabled = true;
    
    var timer = setInterval(function() {
        if (seconds <= 0) {
            clearInterval(timer);
            btn.disabled = false;
            btn.innerText = "获取验证码";
            return;
        }
        btn.innerText = seconds + "秒后重试";
        seconds--;
    }, 1000);
}

// ===== 实际应用:实时时钟 =====
setInterval(function() {
    var now = new Date();
    document.getElementById('clock').innerText = 
        now.getFullYear() + '-' + 
        (now.getMonth() + 1) + '-' + 
        now.getDate() + ' ' + 
        now.getHours() + ':' + 
        now.getMinutes() + ':' + 
        now.getSeconds();
}, 1000);

9.10 Date对象

JavaScript
// ===== 创建日期对象 =====
var now = new Date();                       // 当前时间
var d1 = new Date(2024, 0, 15);             // 2024年1月15日(月份从0开始!)
var d2 = new Date(2024, 0, 15, 10, 30, 0);  // 2024年1月15日 10:30:00
var d3 = new Date("2024-01-15");             // 字符串解析
var d4 = new Date(1705276800000);            // 时间戳

// ===== 获取日期信息 =====
now.getFullYear();    // 年(4位)
now.getMonth();       // 月(0-11,需要+1)
now.getDate();        // 日(1-31)
now.getDay();         // 星期几(0-6,0是周日)
now.getHours();       // 时(0-23)
now.getMinutes();     // 分(0-59)
now.getSeconds();     // 秒(0-59)
now.getMilliseconds(); // 毫秒
now.getTime();        // 时间戳(毫秒)

// ===== 设置日期信息 =====
now.setFullYear(2025);
now.setMonth(5);       // 设为6月
now.setDate(1);

// ===== 格式化日期 =====
function formatDate(date) {
    var year = date.getFullYear();
    var month = String(date.getMonth() + 1).padStart(2, '0');
    var day = String(date.getDate()).padStart(2, '0');
    var hours = String(date.getHours()).padStart(2, '0');
    var minutes = String(date.getMinutes()).padStart(2, '0');
    var seconds = String(date.getSeconds()).padStart(2, '0');
    
    return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
}
console.log(formatDate(new Date()));  // "2024-01-15 10:30:00"

// ===== 日期计算 =====
// 计算两个日期之间的天数
function daysBetween(date1, date2) {
    var diff = Math.abs(date2.getTime() - date1.getTime());
    return Math.floor(diff / (1000 * 60 * 60 * 24));
}

var start = new Date("2024-01-01");
var end = new Date("2024-12-31");
console.log(daysBetween(start, end) + "天");  // 365天

9.11 JSON数据类型

JavaScript
// ===== JSON格式 =====
// JSON = JavaScript Object Notation(JavaScript对象表示法)

// JSON对象
var person = {
    "name": "张三",
    "age": 25,
    "isStudent": false,
    "hobbies": ["篮球", "编程", "游戏"],
    "address": {
        "city": "北京",
        "street": "中关村大街"
    },
    "spouse": null
};

// ===== JSON与字符串的转换 =====

// 1. JS对象 → JSON字符串
var jsonStr = JSON.stringify(person);
console.log(jsonStr);
// '{"name":"张三","age":25,"isStudent":false,...}'

// 格式化输出
var prettyJson = JSON.stringify(person, null, 2);  // 缩进2空格
console.log(prettyJson);

// 2. JSON字符串 → JS对象
var jsonString = '{"name":"李四","age":30}';
var obj = JSON.parse(jsonString);
console.log(obj.name);  // "李四"
console.log(obj.age);   // 30

// ===== 在Ajax中使用JSON =====
// 发送JSON数据给服务器
$.ajax({
    url: '/api/user',
    type: 'POST',
    contentType: 'application/json',  // 告诉服务器发送的是JSON
    data: JSON.stringify({
        username: "admin",
        password: "123456"
    }),
    dataType: 'json',  // 期望返回JSON
    success: function(result) {
        // result已经被自动解析为JS对象
        console.log(result.code);
        console.log(result.data);
    }
});

// ===== JSON数组 =====
var users = [
    {"id": 1, "name": "张三", "age": 25},
    {"id": 2, "name": "李四", "age": 30},
    {"id": 3, "name": "王五", "age": 28}
];

// 遍历JSON数组
for (var i = 0; i < users.length; i++) {
    console.log(users[i].name + " - " + users[i].age);
}

// 或使用forEach
users.forEach(function(user) {
    console.log(user.name);
});

// ===== 深拷贝(利用JSON) =====
var original = {name: "张三", scores: [90, 85, 95]};
var clone = JSON.parse(JSON.stringify(original));  // 深拷贝
clone.scores.push(100);
console.log(original.scores);  // [90, 85, 95](原对象不受影响)

十、jQuery

10.1 jQuery选择器

JavaScript
// ===== 基本选择器 =====
$('#myId');           // ID选择器
$('.myClass');        // 类选择器
$('div');             // 标签选择器
$('*');               // 通配选择器
$('#myId, .myClass'); // 并集选择器

// ===== 层次选择器 =====
$('div p');           // 后代选择器(所有后代中的p)
$('div > p');         // 子代选择器(直接子元素p)
$('div + p');         // 相邻兄弟选择器(紧接在div后面的p)
$('div ~ p');         // 一般兄弟选择器(div之后所有同级的p)

// ===== 属性选择器 =====
$('[name]');               // 有name属性的元素
$('[name="username"]');    // name属性等于username的元素
$('[name^="user"]');       // name属性以user开头
$('[name$="name"]');       // name属性以name结尾
$('[name*="ser"]');        // name属性包含ser
$('[name!="password"]');   // name属性不等于password

// ===== 过滤选择器 =====
$('li:first');             // 第一个li
$('li:last');              // 最后一个li
$('li:eq(2)');             // 第3个li(索引从0开始)
$('li:gt(2)');             // 索引大于2的li
$('li:lt(2)');             // 索引小于2的li
$('li:even');              // 偶数索引的li(0, 2, 4...)
$('li:odd');               // 奇数索引的li(1, 3, 5...)
$('li:not(.active)');      // 不含active类的li
$(':contains("文本")');     // 包含指定文本的元素
$(':empty');                // 空元素
$(':has(p)');               // 包含p元素的元素

// ===== 可见性过滤选择器 =====
$(':visible');             // 可见元素
$(':hidden');              // 隐藏元素

// ===== 表单选择器 =====
$(':input');               // 所有表单元素
$(':text');                // 文本输入框
$(':password');            // 密码输入框
$(':checkbox');            // 复选框
$(':radio');               // 单选按钮
$(':checked');             // 选中的复选框/单选按钮
$(':selected');            // 选中的下拉选项
$(':disabled');            // 禁用的元素
$(':enabled');             // 可用的元素

10.2 jQuery事件

JavaScript
// ===== 常用事件 =====
$('#btn').click(function() {
    console.log("点击事件");
});

$('#input').focus(function() {
    console.log("获得焦点");
});

$('#input').blur(function() {
    console.log("失去焦点");
});

$('#input').change(function() {
    console.log("值改变了:" + $(this).val());
});

$('#input').keyup(function(e) {
    console.log("按键弹起:" + e.keyCode);
    if (e.keyCode === 13) {  // 回车键
        console.log("按了回车");
    }
});

$('div').mouseenter(function() {
    console.log("鼠标移入");
});

$('div').mouseleave(function() {
    console.log("鼠标移出");
});

// hover(mouseenter + mouseleave的简写)
$('div').hover(
    function() { console.log("移入"); },
    function() { console.log("移出"); }
);

// ===== 事件绑定 on =====
$('#btn').on('click', function() {
    console.log("on绑定的点击事件");
});

// 事件委托(动态元素也能触发)
$('ul').on('click', 'li', function() {
    console.log($(this).text());
});

// 解绑事件
$('#btn').off('click');

// 只执行一次
$('#btn').one('click', function() {
    console.log("只执行一次");
});

// ===== 阻止默认行为和冒泡 =====
$('a').click(function(e) {
    e.preventDefault();      // 阻止默认行为(不跳转)
    e.stopPropagation();     // 阻止事件冒泡
});

// ===== 页面加载完成 =====
$(document).ready(function() {
    console.log("DOM加载完成");
});
// 简写
$(function() {
    console.log("DOM加载完成");
});

10.3 jQuery操作DOM

JavaScript
// ===== 内容操作 =====
$('#div1').html();                      // 获取HTML内容
$('#div1').html('<p>新内容</p>');         // 设置HTML内容
$('#div1').text();                      // 获取纯文本
$('#div1').text('纯文本');              // 设置纯文本
$('#input1').val();                     // 获取表单值
$('#input1').val('新值');               // 设置表单值

// ===== 属性操作 =====
$('#img1').attr('src');                 // 获取属性
$('#img1').attr('src', 'new.jpg');      // 设置属性
$('#img1').attr({'src': 'new.jpg', 'alt': '图片'});  // 设置多个属性
$('#img1').removeAttr('alt');           // 移除属性

$('#cb').prop('checked');               // 获取属性(用于checked, disabled等)
$('#cb').prop('checked', true);         // 设置属性

// ===== CSS操作 =====
$('div').css('color');                  // 获取CSS属性
$('div').css('color', 'red');           // 设置CSS属性
$('div').css({                          // 设置多个CSS属性
    'color': 'red',
    'font-size': '16px',
    'background-color': '#f0f0f0'
});

$('div').addClass('active');            // 添加类
$('div').removeClass('active');         // 移除类
$('div').toggleClass('active');         // 切换类
$('div').hasClass('active');            // 是否有某个类

// ===== 节点操作 =====
// 创建元素
var $newLi = $('<li>新项目</li>');

// 添加子元素
$('ul').append($newLi);                 // 末尾追加
$('ul').prepend($newLi);                // 开头添加
$newLi.appendTo($('ul'));               // 追加到

// 添加兄弟元素
$('li:first').after($newLi);            // 在后面插入
$('li:first').before($newLi);           // 在前面插入

// 删除
$('li:last').remove();                  // 删除元素(包括事件)
$('ul').empty();                        // 清空子元素

// 替换
$('li:eq(1)').replaceWith('<li>替换的内容</li>');

// 克隆
var $clone = $('li:first').clone(true);  // true表示连事件一起克隆

// ===== 遍历 =====
$('li').each(function(index, element) {
    console.log(index + ": " + $(element).text());
});

// ===== 显示/隐藏/动画 =====
$('div').show();           // 显示
$('div').hide();           // 隐藏
$('div').toggle();         // 切换显示/隐藏

$('div').fadeIn(1000);     // 淡入
$('div').fadeOut(1000);    // 淡出
$('div').fadeToggle(1000); // 切换淡入/淡出

$('div').slideDown(500);   // 下滑显示
$('div').slideUp(500);     // 上滑隐藏
$('div').slideToggle(500); // 切换

// 自定义动画
$('div').animate({
    width: '300px',
    height: '200px',
    opacity: 0.5
}, 1000, function() {
    console.log("动画完成");
});

十一、Vue.js

11.1 Vue-cli搭建项目

Bash
# 安装Node.js(去官网下载)
node -v    # 检查Node版本
npm -v     # 检查npm版本

# 安装Vue CLI
npm install -g @vue/cli

# 创建项目
vue create my-project

# 进入项目目录
cd my-project

# 启动开发服务器
npm run serve

# 打包项目
npm run build

项目目录结构

text
my-project/
├── node_modules/       ← 依赖包
├── public/             ← 静态资源
│   ├── favicon.ico
│   └── index.html      ← 入口HTML
├── src/                ← 源代码
│   ├── assets/         ← 静态资源(图片、样式等)
│   ├── components/     ← 组件
│   ├── views/          ← 页面级组件
│   ├── router/         ← 路由配置
│   ├── store/          ← 状态管理
│   ├── App.vue         ← 根组件
│   └── main.js         ← 入口文件
├── package.json        ← 项目配置/依赖
└── vue.config.js       ← Vue配置文件

11.2 Vue基础

HTML
<!-- App.vue 基本结构 -->
<template>
  <div id="app">
    <h1>{{ message }}</h1>
    <p>{{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  
  // 数据
  data() {
    return {
      message: 'Hello Vue!',
      count: 0
    }
  },
  
  // 方法
  methods: {
    increment() {
      this.count++
    }
  },
  
  // 计算属性
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  
  // 监听器
  watch: {
    count(newVal, oldVal) {
      console.log(`count从${oldVal}变为${newVal}`)
    }
  }
}
</script>

<style>
h1 {
  color: #42b983;
}
</style>

11.3 Vue.js指令

HTML
<template>
  <div>
    <!-- ===== 插值 ===== -->
    <p>{{ message }}</p>              <!-- 文本插值 -->
    <p v-text="message"></p>         <!-- 等同于{{ }} -->
    <p v-html="htmlContent"></p>     <!-- 渲染HTML -->
    
    <!-- ===== v-bind(属性绑定)===== -->
    <img v-bind:src="imgUrl" />      <!-- 完整写法 -->
    <img :src="imgUrl" />            <!-- 简写 -->
    <div :class="{ active: isActive, error: hasError }"></div>  <!-- 动态class -->
    <div :class="[baseClass, activeClass]"></div>
    <div :style="{ color: textColor, fontSize: size + 'px' }"></div>  <!-- 动态style -->
    
    <!-- ===== v-on(事件绑定)===== -->
    <button v-on:click="handleClick">点击</button>   <!-- 完整写法 -->
    <button @click="handleClick">点击</button>        <!-- 简写 -->
    <button @click="handleClick($event)">带事件对象</button>
    <input @keyup.enter="submit" />                   <!-- 按键修饰符 -->
    <button @click.prevent="handleClick">阻止默认</button>  <!-- 事件修饰符 -->
    <button @click.stop="handleClick">阻止冒泡</button>
    
    <!-- ===== v-model(双向绑定)===== -->
    <input v-model="username" />           <!-- 文本框 -->
    <textarea v-model="content"></textarea> <!-- 文本域 -->
    <select v-model="selected">            <!-- 下拉框 -->
      <option value="">请选择</option>
      <option value="1">选项1</option>
      <option value="2">选项2</option>
    </select>
    <input type="checkbox" v-model="checked" />  <!-- 复选框 -->
    <input type="radio" v-model="picked" value="A" />  <!-- 单选 -->
    
    <!-- v-model修饰符 -->
    <input v-model.trim="username" />    <!-- 去除首尾空格 -->
    <input v-model.number="age" />       <!-- 转为数字 -->
    <input v-model.lazy="message" />     <!-- 在change事件触发时更新 -->
    
    <!-- ===== v-if / v-else-if / v-else(条件渲染)===== -->
    <div v-if="score >= 90">优秀</div>
    <div v-else-if="score >= 60">及格</div>
    <div v-else>不及格</div>
    
    <!-- ===== v-show(显示/隐藏)===== -->
    <div v-show="isVisible">显示或隐藏</div>
    <!-- v-if:直接从DOM中移除/添加元素
         v-show:通过display:none控制,元素始终在DOM中 -->
    
    <!-- ===== v-for(列表渲染)===== -->
    <ul>
      <li v-for="(item, index) in userList" :key="item.id">
        {{ index + 1 }}. {{ item.name }} - {{ item.age }}岁
      </li>
    </ul>
    
    <!-- 遍历对象 -->
    <div v-for="(value, key, index) in userObj" :key="key">
      {{ key }}: {{ value }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello',
      htmlContent: '<span style="color:red">红色文本</span>',
      imgUrl: '/images/logo.png',
      isActive: true,
      hasError: false,
      textColor: 'blue',
      size: 16,
      username: '',
      content: '',
      selected: '',
      checked: false,
      picked: '',
      score: 85,
      isVisible: true,
      userList: [
        { id: 1, name: '张三', age: 25 },
        { id: 2, name: '李四', age: 30 },
        { id: 3, name: '王五', age: 28 }
      ],
      userObj: { name: '张三', age: 25, city: '北京' }
    }
  },
  methods: {
    handleClick(event) {
      console.log('按钮被点击', event)
    },
    submit() {
      console.log('提交表单')
    }
  }
}
</script>

11.4 Axios应用

Bash
# 安装axios
npm install axios
JavaScript
// src/utils/request.js —— 封装axios

import axios from 'axios'

// 创建axios实例
const service = axios.create({
  baseURL: 'http://localhost:8080',  // 后端地址
  timeout: 10000                     // 请求超时时间
})

// ===== 请求拦截器 =====
service.interceptors.request.use(
  config => {
    // 在发送请求之前做些什么
    const token = localStorage.getItem('token')
    if (token) {
      config.headers['Authorization'] = 'Bearer ' + token
    }
    console.log('请求发出:', config.url)
    return config
  },
  error => {
    console.log('请求错误:', error)
    return Promise.reject(error)
  }
)

// ===== 响应拦截器 =====
service.interceptors.response.use(
  response => {
    const res = response.data
    
    // 根据后端返回的code判断
    if (res.code !== 200) {
      alert(res.msg || '请求失败')
      
      // 401 未授权,跳转到登录页
      if (res.code === 401) {
        localStorage.removeItem('token')
        window.location.href = '/login'
      }
      
      return Promise.reject(new Error(res.msg || 'Error'))
    }
    
    return res
  },
  error => {
    console.log('响应错误:', error)
    if (error.response) {
      switch (error.response.status) {
        case 401:
          alert('未授权,请重新登录')
          break
        case 403:
          alert('禁止访问')
          break
        case 404:
          alert('请求地址不存在')
          break
        case 500:
          alert('服务器内部错误')
          break
        default:
          alert('网络异常')
      }
    }
    return Promise.reject(error)
  }
)

export default service
JavaScript
// src/api/user.js —— 用户相关API

import request from '@/utils/request'

// 登录
export function login(data) {
  return request({
    url: '/login',
    method: 'post',
    data
  })
}

// 获取用户列表
export function getUserList(params) {
  return request({
    url: '/user/list',
    method: 'get',
    params  // GET请求参数
  })
}

// 添加用户
export function addUser(data) {
  return request({
    url: '/user/add',
    method: 'post',
    data  // POST请求体
  })
}

// 修改用户
export function updateUser(data) {
  return request({
    url: '/user/update',
    method: 'put',
    data
  })
}

// 删除用户
export function deleteUser(id) {
  return request({
    url: '/user/delete/' + id,
    method: 'delete'
  })
}
vue
<!-- 在组件中使用 -->
<script>
import { getUserList, deleteUser } from '@/api/user'

export default {
  data() {
    return {
      userList: [],
      total: 0,
      pageNum: 1,
      pageSize: 10
    }
  },
  created() {
    this.loadUsers()
  },
  methods: {
    async loadUsers() {
      const res = await getUserList({
        pageNum: this.pageNum,
        pageSize: this.pageSize
      })
      this.userList = res.data.list
      this.total = res.data.total
    },
    async handleDelete(id) {
      if (!confirm('确定删除?')) return
      await deleteUser(id)
      this.$message.success('删除成功')
      this.loadUsers()
    }
  }
}
</script>

十二、Vue生命周期与高级特性

12.1 生命周期

text
┌─────────────────────────────────────────────────┐
│              Vue 实例生命周期                     │
│                                                  │
│  new Vue()                                       │
│      │                                           │
│      ▼                                           │
│  beforeCreate  ← 实例刚创建,data和methods还没初始化│
│      │                                           │
│      ▼                                           │
│  created       ← ★ data和methods已初始化          │
│      │           可以发起网络请求                   │
│      ▼                                           │
│  beforeMount   ← 模板已编译,但还没挂载到DOM       │
│      │                                           │
│      ▼                                           │
│  mounted       ← ★ 已挂载到DOM,可以操作DOM元素    │
│      │                                           │
│  ┌───┴───┐                                       │
│  │ 更新  │                                        │
│  │       ▼                                       │
│  │  beforeUpdate  ← data变化,DOM还没更新         │
│  │       │                                       │
│  │       ▼                                       │
│  │  updated       ← DOM已更新                    │
│  └───────┘                                       │
│      │                                           │
│      ▼                                           │
│  beforeDestroy ← 实例销毁前,还可以访问data等      │
│      │                                           │
│      ▼                                           │
│  destroyed     ← 实例已销毁                       │
└─────────────────────────────────────────────────┘
JavaScript
export default {
  data() {
    return { message: 'Hello' }
  },
  
  beforeCreate() {
    // this.message → undefined
    console.log('beforeCreate: 数据还没准备好')
  },
  
  created() {
    // this.message → 'Hello'
    console.log('created: 数据已准备好,可以发Ajax请求')
    this.loadData()  // ★ 通常在这里发起数据请求
  },
  
  beforeMount() {
    console.log('beforeMount: 模板已编译,但还没渲染到页面')
  },
  
  mounted() {
    console.log('mounted: 已渲染到页面,可以操作DOM')
    // 可以在这里使用 this.$refs
  },
  
  beforeUpdate() {
    console.log('beforeUpdate: 数据变了,DOM还没更新')
  },
  
  updated() {
    console.log('updated: DOM已更新')
  },
  
  beforeDestroy() {
    console.log('beforeDestroy: 即将销毁,清理定时器等')
    clearInterval(this.timer)
  },
  
  destroyed() {
    console.log('destroyed: 已销毁')
  }
}

12.2 过滤器

JavaScript
// 全局过滤器(main.js中注册)
Vue.filter('dateFormat', function(value, format) {
  if (!value) return ''
  const date = new Date(value)
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  
  if (format === 'yyyy-MM-dd') {
    return `${year}-${month}-${day}`
  }
  
  const hours = String(date.getHours()).padStart(2, '0')
  const minutes = String(date.getMinutes()).padStart(2, '0')
  const seconds = String(date.getSeconds()).padStart(2, '0')
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
})

Vue.filter('currency', function(value) {
  if (!value) return '¥0.00'
  return '¥' + Number(value).toFixed(2)
})
HTML
<!-- 使用过滤器 -->
<p>{{ createTime | dateFormat('yyyy-MM-dd') }}</p>
<p>{{ price | currency }}</p>

<!-- 多个过滤器可以串联 -->
<p>{{ message | filterA | filterB }}</p>

12.3 组件

vue
<!-- 子组件:UserCard.vue -->
<template>
  <div class="user-card">
    <h3>{{ user.name }}</h3>
    <p>年龄:{{ user.age }}</p>
    <button @click="handleClick">选中</button>
  </div>
</template>

<script>
export default {
  name: 'UserCard',
  
  // ★ props:接收父组件传来的数据
  props: {
    user: {
      type: Object,
      required: true
    }
  },
  
  methods: {
    handleClick() {
      // ★ $emit:向父组件发送事件
      this.$emit('select', this.user)
    }
  }
}
</script>
vue
<!-- 父组件:UserList.vue -->
<template>
  <div>
    <h2>用户列表</h2>
    
    <!-- 使用子组件,传递数据 -->
    <user-card 
      v-for="user in users" 
      :key="user.id"
      :user="user"
      @select="handleSelect"
    />
    
    <p>当前选中:{{ selectedUser ? selectedUser.name : '无' }}</p>
  </div>
</template>

<script>
import UserCard from '@/components/UserCard.vue'

export default {
  components: { UserCard },  // 注册子组件
  
  data() {
    return {
      users: [
        { id: 1, name: '张三', age: 25 },
        { id: 2, name: '李四', age: 30 },
        { id: 3, name: '王五', age: 28 }
      ],
      selectedUser: null
    }
  },
  
  methods: {
    handleSelect(user) {
      this.selectedUser = user
      console.log('选中了:' + user.name)
    }
  }
}
</script>

12.4 组件Slot(插槽)

vue
<!-- 子组件:Card.vue -->
<template>
  <div class="card">
    <!-- 具名插槽 -->
    <div class="card-header">
      <slot name="header">默认标题</slot>
    </div>
    
    <!-- 默认插槽 -->
    <div class="card-body">
      <slot>默认内容</slot>
    </div>
    
    <!-- 具名插槽 -->
    <div class="card-footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>
vue
<!-- 父组件中使用 -->
<card>
  <template v-slot:header>
    <h3>自定义标题</h3>
  </template>
  
  <p>这是卡片的主要内容</p>
  
  <template v-slot:footer>
    <button>确定</button>
    <button>取消</button>
  </template>
</card>

<!-- 简写 -->
<card>
  <template #header>
    <h3>自定义标题</h3>
  </template>
  
  <p>主要内容</p>
  
  <template #footer>
    <button>确定</button>
  </template>
</card>

12.5 路由配置

Bash
# 安装路由
npm install vue-router
JavaScript
// src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    redirect: '/login'  // 重定向
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue')  // 懒加载
  },
  {
    path: '/home',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
    meta: { requireAuth: true },  // 自定义元信息(需要登录)
    children: [  // 嵌套路由
      {
        path: 'dashboard',
        name: 'Dashboard',
        component: () => import('@/views/Dashboard.vue')
      },
      {
        path: 'user',
        name: 'UserManage',
        component: () => import('@/views/UserManage.vue')
      },
      {
        path: 'user/:id',  // 动态路由参数
        name: 'UserDetail',
        component: () => import('@/views/UserDetail.vue'),
        props: true  // 将路由参数作为props传递给组件
      }
    ]
  },
  {
    path: '*',  // 404页面
    component: () => import('@/views/NotFound.vue')
  }
]

const router = new VueRouter({
  mode: 'history',  // 使用history模式(去掉URL中的#)
  routes
})

// ★ 路由守卫(登录拦截)
router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token')
  
  if (to.meta.requireAuth && !token) {
    // 需要登录但没有token,跳转到登录页
    next('/login')
  } else {
    next()  // 放行
  }
})

export default router
vue
<!-- 在组件中使用路由 -->
<template>
  <div>
    <!-- 路由链接 -->
    <router-link to="/home/dashboard">首页</router-link>
    <router-link to="/home/user">用户管理</router-link>
    <router-link :to="{ name: 'UserDetail', params: { id: 1 } }">用户详情</router-link>
    
    <!-- 路由出口(子路由的内容显示在这里) -->
    <router-view />
  </div>
</template>

<script>
export default {
  methods: {
    // 编程式导航
    goToUser() {
      this.$router.push('/home/user')
    },
    goToDetail(id) {
      this.$router.push({ name: 'UserDetail', params: { id } })
    },
    goBack() {
      this.$router.go(-1)
    }
  },
  created() {
    // 获取路由参数
    console.log(this.$route.params.id)    // 路径参数
    console.log(this.$route.query.page)   // 查询参数 ?page=1
  }
}
</script>

十三、前端UI组件(Element UI)

13.1 安装与配置

Bash
npm install element-ui
JavaScript
// main.js
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(ElementUI)

13.2 完整的用户管理页面

vue
<template>
  <div class="user-manage">
    <!-- 搜索区域 -->
    <el-form :inline="true" :model="queryParams">
      <el-form-item label="用户名">
        <el-input v-model="queryParams.username" placeholder="请输入用户名" clearable />
      </el-form-item>
      <el-form-item label="状态">
        <el-select v-model="queryParams.status" placeholder="请选择" clearable>
          <el-option label="启用" value="1" />
          <el-option label="禁用" value="0" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleSearch">搜索</el-button>
        <el-button @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <!-- 操作按钮 -->
    <el-row :gutter="10" style="margin-bottom: 15px;">
      <el-col :span="1.5">
        <el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
      </el-col>
    </el-row>

    <!-- 数据表格 -->
    <el-table :data="userList" border stripe>
      <el-table-column type="index" label="序号" width="55" />
      <el-table-column prop="username" label="用户名" />
      <el-table-column prop="realName" label="真实姓名" />
      <el-table-column prop="email" label="邮箱" />
      <el-table-column prop="status" label="状态">
        <template slot-scope="scope">
          <el-tag :type="scope.row.status === '1' ? 'success' : 'danger'">
            {{ scope.row.status === '1' ? '启用' : '禁用' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="createTime" label="创建时间" />
      <el-table-column label="操作" width="200">
        <template slot-scope="scope">
          <el-button size="mini" type="text" @click="handleEdit(scope.row)">编辑</el-button>
          <el-button size="mini" type="text" style="color:red" @click="handleDelete(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <el-pagination
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      :current-page="queryParams.pageNum"
      :page-sizes="[10, 20, 50]"
      :page-size="queryParams.pageSize"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total"
      style="margin-top: 15px;"
    />

    <!-- 添加/编辑弹窗 -->
    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px">
      <el-form :model="form" :rules="rules" ref="userForm" label-width="80px">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="form.username" placeholder="请输入用户名" />
        </el-form-item>
        <el-form-item label="密码" prop="password" v-if="!form.id">
          <el-input v-model="form.password" type="password" placeholder="请输入密码" />
        </el-form-item>
        <el-form-item label="姓名" prop="realName">
          <el-input v-model="form.realName" placeholder="请输入真实姓名" />
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input v-model="form.email" placeholder="请输入邮箱" />
        </el-form-item>
        <el-form-item label="状态">
          <el-radio-group v-model="form.status">
            <el-radio label="1">启用</el-radio>
            <el-radio label="0">禁用</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <div slot="footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitForm">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { getUserList, addUser, updateUser, deleteUser } from '@/api/user'

export default {
  name: 'UserManage',
  data() {
    return {
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        username: '',
        status: ''
      },
      userList: [],
      total: 0,
      
      // 弹窗
      dialogVisible: false,
      dialogTitle: '',
      form: {},
      
      // 表单验证规则
      rules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' },
          { min: 2, max: 20, message: '长度在2到20个字符', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 6, max: 20, message: '密码长度在6到20个字符', trigger: 'blur' }
        ],
        email: [
          { required: true, message: '请输入邮箱', trigger: 'blur' },
          { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
        ]
      }
    }
  },
  
  created() {
    this.loadUsers()
  },
  
  methods: {
    // 加载用户列表
    async loadUsers() {
      const res = await getUserList(this.queryParams)
      this.userList = res.data.list
      this.total = res.data.total
    },
    
    // 搜索
    handleSearch() {
      this.queryParams.pageNum = 1
      this.loadUsers()
    },
    
    // 重置查询
    resetQuery() {
      this.queryParams = {
        pageNum: 1,
        pageSize: 10,
        username: '',
        status: ''
      }
      this.loadUsers()
    },
    
    // 新增
    handleAdd() {
      this.dialogTitle = '新增用户'
      this.form = { status: '1' }
      this.dialogVisible = true
    },
    
    // 编辑
    handleEdit(row) {
      this.dialogTitle = '编辑用户'
      this.form = { ...row }  // 浅拷贝
      this.dialogVisible = true
    },
    
    // 提交表单
    submitForm() {
      this.$refs.userForm.validate(async (valid) => {
        if (!valid) return
        
        if (this.form.id) {
          await updateUser(this.form)
          this.$message.success('修改成功')
        } else {
          await addUser(this.form)
          this.$message.success('添加成功')
        }
        
        this.dialogVisible = false
        this.loadUsers()
      })
    },
    
    // 删除
    handleDelete(row) {
      this.$confirm('确定要删除用户 "' + row.username + '" 吗?', '提示', {
        type: 'warning'
      }).then(async () => {
        await deleteUser(row.id)
        this.$message.success('删除成功')
        this.loadUsers()
      }).catch(() => {})
    },
    
    // 分页
    handleSizeChange(val) {
      this.queryParams.pageSize = val
      this.loadUsers()
    },
    handleCurrentChange(val) {
      this.queryParams.pageNum = val
      this.loadUsers()
    }
  }
}
</script>

十四、MyBatis

14.1 ORM与基本映射

ORM(Object-Relational Mapping)对象关系映射:把数据库表映射为Java对象。

text
数据库表 user          ←→          Java类 User
├── id INT            ←→          private Integer id;
├── username VARCHAR  ←→          private String username;
├── password VARCHAR  ←→          private String password;
├── age INT           ←→          private Integer age;
└── email VARCHAR     ←→          private String email;

项目配置

XML
<!-- pom.xml 依赖 -->
<dependencies>
    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.13</version>
    </dependency>
    
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
</dependencies>
XML
<!-- mybatis-config.xml 核心配置文件 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 类型别名 -->
    <typeAliases>
        <package name="com.example.entity"/>
    </typeAliases>
    
    <!-- 数据库环境 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mydb?useSSL=false&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    
    <!-- 映射文件 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

实体类

Java
public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private String email;
    
    // getter/setter/toString...
}

Mapper接口

Java
public interface UserMapper {
    // 查询所有
    List<User> findAll();
    
    // 根据ID查询
    User findById(Integer id);
    
    // 模糊查询
    List<User> findByName(String username);
    
    // 分页查询
    List<User> findByPage(@Param("offset") int offset, @Param("limit") int limit);
    
    // 添加
    int insert(User user);
    
    // 批量添加
    int batchInsert(List<User> users);
    
    // 修改
    int update(User user);
    
    // 删除
    int deleteById(Integer id);
    
    // 批量删除
    int batchDelete(List<Integer> ids);
}

Mapper XML

XML
<!-- UserMapper.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.mapper.UserMapper">
    
    <!-- 结果集映射(当列名与属性名不一致时使用) -->
    <resultMap id="UserResultMap" type="User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="age" property="age"/>
        <result column="email" property="email"/>
    </resultMap>
    
    <!-- 查询所有 -->
    <select id="findAll" resultType="User">
        SELECT * FROM user ORDER BY id
    </select>
    
    <!-- 根据ID查询 -->
    <select id="findById" parameterType="int" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
    
    <!-- 模糊查询 -->
    <select id="findByName" parameterType="string" resultType="User">
        SELECT * FROM user WHERE username LIKE CONCAT('%', #{username}, '%')
    </select>
    
    <!-- 分页查询 -->
    <select id="findByPage" resultType="User">
        SELECT * FROM user ORDER BY id LIMIT #{offset}, #{limit}
    </select>
    
    <!-- 添加(返回自增主键) -->
    <insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user(username, password, age, email)
        VALUES(#{username}, #{password}, #{age}, #{email})
    </insert>
    
    <!-- 批量添加 -->
    <insert id="batchInsert" parameterType="list">
        INSERT INTO user(username, password, age, email) VALUES
        <foreach collection="list" item="user" separator=",">
            (#{user.username}, #{user.password}, #{user.age}, #{user.email})
        </foreach>
    </insert>
    
    <!-- 修改 -->
    <update id="update" parameterType="User">
        UPDATE user SET username = #{username}, age = #{age}, email = #{email}
        WHERE id = #{id}
    </update>
    
    <!-- 删除 -->
    <delete id="deleteById" parameterType="int">
        DELETE FROM user WHERE id = #{id}
    </delete>
    
    <!-- 批量删除 -->
    <delete id="batchDelete" parameterType="list">
        DELETE FROM user WHERE id IN
        <foreach collection="list" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </delete>
    
</mapper>

14.2 动态SQL

XML
<!-- 动态SQL示例 -->

<!-- ===== if ===== -->
<select id="findByCondition" parameterType="User" resultType="User">
    SELECT * FROM user
    <where>
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
    </where>
    ORDER BY id
</select>

<!-- ===== choose/when/otherwise(类似switch) ===== -->
<select id="findByType" resultType="User">
    SELECT * FROM user
    <where>
        <choose>
            <when test="searchType == 'username'">
                AND username = #{keyword}
            </when>
            <when test="searchType == 'email'">
                AND email = #{keyword}
            </when>
            <otherwise>
                AND id = #{keyword}
            </otherwise>
        </choose>
    </where>
</select>

<!-- ===== set(动态更新) ===== -->
<update id="updateSelective" parameterType="User">
    UPDATE user
    <set>
        <if test="username != null">username = #{username},</if>
        <if test="password != null">password = #{password},</if>
        <if test="age != null">age = #{age},</if>
        <if test="email != null">email = #{email},</if>
    </set>
    WHERE id = #{id}
</update>

<!-- ===== foreach ===== -->
<!-- 批量查询 -->
<select id="findByIds" resultType="User">
    SELECT * FROM user WHERE id IN
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>

<!-- ===== trim ===== -->
<select id="findWithTrim" resultType="User">
    SELECT * FROM user
    <trim prefix="WHERE" prefixOverrides="AND|OR">
        <if test="username != null">AND username = #{username}</if>
        <if test="age != null">AND age = #{age}</if>
    </trim>
</select>

<!-- ===== sql片段(可复用) ===== -->
<sql id="userColumns">
    id, username, password, age, email
</sql>

<select id="findAll" resultType="User">
    SELECT <include refid="userColumns"/> FROM user
</select>

14.3 关联映射

SQL
-- 数据库表
CREATE TABLE department (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50)
);

CREATE TABLE employee (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    dept_id INT,
    FOREIGN KEY (dept_id) REFERENCES department(id)
);

-- 多对多:学生-课程
CREATE TABLE student (id INT PRIMARY KEY, name VARCHAR(50));
CREATE TABLE course (id INT PRIMARY KEY, name VARCHAR(50));
CREATE TABLE student_course (
    student_id INT,
    course_id INT,
    PRIMARY KEY (student_id, course_id)
);

一对多(部门-员工)

Java
// Department.java
public class Department {
    private Integer id;
    private String name;
    private List<Employee> employees;  // 一个部门有多个员工
    // getter/setter...
}

// Employee.java
public class Employee {
    private Integer id;
    private String name;
    private Integer deptId;
    private Department department;  // 一个员工属于一个部门
    // getter/setter...
}
XML
<!-- DepartmentMapper.xml -->

<!-- 一对多查询(部门及其所有员工) -->
<resultMap id="DeptWithEmployees" type="Department">
    <id column="d_id" property="id"/>
    <result column="d_name" property="name"/>
    <!-- collection:一对多 -->
    <collection property="employees" ofType="Employee">
        <id column="e_id" property="id"/>
        <result column="e_name" property="name"/>
        <result column="dept_id" property="deptId"/>
    </collection>
</resultMap>

<select id="findDeptWithEmployees" resultMap="DeptWithEmployees">
    SELECT 
        d.id AS d_id, d.name AS d_name,
        e.id AS e_id, e.name AS e_name, e.dept_id
    FROM department d
    LEFT JOIN employee e ON d.id = e.dept_id
    WHERE d.id = #{id}
</select>

<!-- EmployeeMapper.xml -->

<!-- 多对一查询(员工及其部门信息) -->
<resultMap id="EmpWithDept" type="Employee">
    <id column="e_id" property="id"/>
    <result column="e_name" property="name"/>
    <result column="dept_id" property="deptId"/>
    <!-- association:多对一/一对一 -->
    <association property="department" javaType="Department">
        <id column="d_id" property="id"/>
        <result column="d_name" property="name"/>
    </association>
</resultMap>

<select id="findEmpWithDept" resultMap="EmpWithDept">
    SELECT 
        e.id AS e_id, e.name AS e_name, e.dept_id,
        d.id AS d_id, d.name AS d_name
    FROM employee e
    LEFT JOIN department d ON e.dept_id = d.id
    WHERE e.id = #{id}
</select>

多对多(学生-课程)

XML
<!-- StudentMapper.xml -->
<resultMap id="StudentWithCourses" type="Student">
    <id column="s_id" property="id"/>
    <result column="s_name" property="name"/>
    <collection property="courses" ofType="Course">
        <id column="c_id" property="id"/>
        <result column="c_name" property="name"/>
    </collection>
</resultMap>

<select id="findStudentWithCourses" resultMap="StudentWithCourses">
    SELECT 
        s.id AS s_id, s.name AS s_name,
        c.id AS c_id, c.name AS c_name
    FROM student s
    LEFT JOIN student_course sc ON s.id = sc.student_id
    LEFT JOIN course c ON sc.course_id = c.id
    WHERE s.id = #{id}
</select>

14.4 MyBatis-Plus

XML
<!-- pom.xml -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>
Java
// 实体类
@TableName("user")  // 指定表名
public class User {
    @TableId(type = IdType.AUTO)  // 主键自增
    private Long id;
    
    private String username;
    private String password;
    private Integer age;
    private String email;
    
    @TableField(exist = false)  // 数据库中不存在此字段
    private String deptName;
}

// Mapper接口(继承BaseMapper,自动拥有基本CRUD方法)
@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 无需编写基本的CRUD方法!
    // BaseMapper已经提供了:
    // insert(User user)
    // deleteById(id)
    // updateById(User user)
    // selectById(id)
    // selectList(queryWrapper)
    // selectPage(page, queryWrapper)
    // ... 等十几个方法
}

// Service接口
public interface UserService extends IService<User> {
    // IService提供了更丰富的方法
}

// Service实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
}
Java
// 使用MyBatis-Plus进行CRUD

@RestController
@RequestMapping("/user")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    // 查询所有
    @GetMapping("/list")
    public List<User> list() {
        return userService.list();
    }
    
    // 条件查询
    @GetMapping("/search")
    public List<User> search(String username, Integer age) {
        // QueryWrapper(条件构造器)
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        if (username != null && !username.isEmpty()) {
            wrapper.like("username", username);  // LIKE '%username%'
        }
        if (age != null) {
            wrapper.ge("age", age);  // age >= ?
        }
        wrapper.orderByDesc("id");  // ORDER BY id DESC
        
        return userService.list(wrapper);
    }
    
    // 分页查询
    @GetMapping("/page")
    public IPage<User> page(int pageNum, int pageSize) {
        Page<User> page = new Page<>(pageNum, pageSize);
        
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.orderByDesc(User::getId);
        
        return userService.page(page, wrapper);
    }
    
    // 添加
    @PostMapping("/add")
    public boolean add(@RequestBody User user) {
        return userService.save(user);
    }
    
    // 修改
    @PutMapping("/update")
    public boolean update(@RequestBody User user) {
        return userService.updateById(user);
    }
    
    // 删除
    @DeleteMapping("/delete/{id}")
    public boolean delete(@PathVariable Long id) {
        return userService.removeById(id);
    }
    
    // 批量删除
    @DeleteMapping("/batchDelete")
    public boolean batchDelete(@RequestBody List<Long> ids) {
        return userService.removeByIds(ids);
    }
}

十五、Spring

15.1 IOC(控制反转)

text
传统方式:
    对象自己创建依赖 → UserService service = new UserServiceImpl();
    
IOC方式:
    容器创建对象并注入 → Spring容器管理所有Bean的创建和依赖注入
    
控制反转:对象的创建权从"程序员"转交给"Spring容器"
依赖注入(DI):Spring容器自动将依赖对象注入到需要它们的地方
Java
// ===== XML配置方式 =====
// applicationContext.xml
/*
<beans>
    <bean id="userDao" class="com.example.dao.UserDaoImpl"/>
    <bean id="userService" class="com.example.service.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>
*/

// ===== 注解方式(推荐)=====

// DAO层
@Repository  // 标识为持久层组件
public class UserDaoImpl implements UserDao {
    // ...
}

// Service层
@Service  // 标识为业务层组件
public class UserServiceImpl implements UserService {
    
    @Autowired  // 自动注入
    private UserDao userDao;
    
    @Override
    public User findById(Integer id) {
        return userDao.findById(id);
    }
}

// Controller层
@Controller  // 标识为控制层组件
public class UserController {
    
    @Autowired
    private UserService userService;
}

// 通用组件
@Component  // 通用组件注解
public class MyUtil {
    // ...
}

15.2 AOP(面向切面编程)

text
AOP = Aspect Oriented Programming

核心思想:将横切关注点(日志、事务、权限等)从业务逻辑中分离出来

没有AOP:
    void addUser() {
        记录日志...
        开启事务...
        【添加用户业务逻辑】
        提交事务...
        记录日志...
    }

有AOP:
    void addUser() {
        【添加用户业务逻辑】  ← 只关注业务
    }
    日志切面自动处理日志
    事务切面自动处理事务

AOP术语

text
┌─────────────┬────────────────────────────────────────────┐
│  术语        │  说明                                      │
├─────────────┼────────────────────────────────────────────┤
│  切面(Aspect)│  横切关注点的模块化(日志切面、事务切面等)   │
│  连接点      │  程序执行的某个点(方法调用、异常抛出等)     │
│  (JoinPoint) │                                            │
│  切入点      │  匹配连接点的表达式(指定在哪些方法上切入)   │
│  (Pointcut)  │                                            │
│  通知(Advice)│  切面在切入点上执行的动作                   │
│  目标对象    │  被代理的对象                               │
│  (Target)    │                                            │
│  代理(Proxy) │  AOP框架创建的代理对象                      │
└─────────────┴────────────────────────────────────────────┘

通知类型:
  @Before       - 前置通知:方法执行前
  @After        - 后置通知:方法执行后(无论是否异常)
  @AfterReturning - 返回通知:方法正常返回后
  @AfterThrowing  - 异常通知:方法抛出异常后
  @Around       - 环绕通知:包围方法执行的前后
Java
@Aspect
@Component
public class LogAspect {
    
    // 定义切入点:com.example.service包下所有类的所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {}
    
    // 前置通知
    @Before("servicePointcut()")
    public void before(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("【前置通知】调用方法:" + methodName + ",参数:" + Arrays.toString(args));
    }
    
    // 返回通知
    @AfterReturning(pointcut = "servicePointcut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("【返回通知】" + methodName + " 返回值:" + result);
    }
    
    // 异常通知
    @AfterThrowing(pointcut = "servicePointcut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("【异常通知】" + methodName + " 异常:" + ex.getMessage());
    }
    
    // 环绕通知(最强大的通知类型)
    @Around("servicePointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long startTime = System.currentTimeMillis();
        String methodName = pjp.getSignature().getName();
        
        System.out.println("【环绕通知-前】" + methodName + " 开始执行");
        
        Object result = null;
        try {
            result = pjp.proceed();  // ★ 执行目标方法
        } catch (Exception e) {
            System.out.println("【环绕通知-异常】" + e.getMessage());
            throw e;
        }
        
        long endTime = System.currentTimeMillis();
        System.out.println("【环绕通知-后】" + methodName + " 执行完毕,耗时:" + (endTime - startTime) + "ms");
        
        return result;
    }
}

十六、Spring MVC

16.1 常用注解

Java
@Controller          // 标识控制器类
@RestController      // = @Controller + @ResponseBody(返回JSON)
@RequestMapping      // 映射请求路径
@GetMapping          // GET请求映射
@PostMapping         // POST请求映射
@PutMapping          // PUT请求映射
@DeleteMapping       // DELETE请求映射
@RequestParam        // 获取请求参数
@PathVariable        // 获取路径变量
@RequestBody         // 获取请求体(JSON → Java对象)
@ResponseBody        // 返回值直接作为响应体(Java对象 → JSON)

@RestController
@RequestMapping("/api/user")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    // GET /api/user/list?pageNum=1&pageSize=10
    @GetMapping("/list")
    public Result list(@RequestParam(defaultValue = "1") Integer pageNum,
                       @RequestParam(defaultValue = "10") Integer pageSize) {
        PageInfo<User> page = userService.findByPage(pageNum, pageSize);
        return Result.success(page);
    }
    
    // GET /api/user/5
    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id) {
        User user = userService.findById(id);
        return Result.success(user);
    }
    
    // POST /api/user  (请求体为JSON)
    @PostMapping
    public Result add(@RequestBody User user) {
        userService.add(user);
        return Result.success("添加成功");
    }
    
    // PUT /api/user  (请求体为JSON)
    @PutMapping
    public Result update(@RequestBody User user) {
        userService.update(user);
        return Result.success("修改成功");
    }
    
    // DELETE /api/user/5
    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id) {
        userService.delete(id);
        return Result.success("删除成功");
    }
}

16.2 统一返回结果类

Java
public class Result {
    private int code;       // 状态码
    private String msg;     // 消息
    private Object data;    // 数据
    
    public static Result success() {
        return new Result(200, "操作成功", null);
    }
    
    public static Result success(Object data) {
        return new Result(200, "操作成功", data);
    }
    
    public static Result success(String msg) {
        return new Result(200, msg, null);
    }
    
    public static Result error(String msg) {
        return new Result(500, msg, null);
    }
    
    public static Result error(int code, String msg) {
        return new Result(code, msg, null);
    }
    
    // 构造方法、getter/setter...
    public Result(int code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

16.3 拦截器

Java
@Component
public class LoginInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        
        // 获取token
        String token = request.getHeader("Authorization");
        
        if (token == null || token.isEmpty()) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}");
            return false;  // 拦截
        }
        
        // 验证token...
        
        return true;  // 放行
    }
}

// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private LoginInterceptor loginInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/api/**")         // 拦截的路径
                .excludePathPatterns("/api/login");  // 排除的路径
    }
}

16.4 SSM整合(MyBatis + Spring + SpringMVC)

YAML
# application.yml(Spring Boot配置)
server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.entity
  configuration:
    map-underscore-to-camel-case: true  # 下划线转驼峰
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # SQL日志

十七、Spring Boot

17.1 自动配置与Web应用

Java
@SpringBootApplication  // 核心注解(包含了自动配置、组件扫描等)
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

17.2 RESTful服务

text
RESTful API设计规范:

GET    /api/users       → 查询用户列表
GET    /api/users/1     → 查询ID为1的用户
POST   /api/users       → 创建用户
PUT    /api/users/1     → 修改ID为1的用户
DELETE /api/users/1     → 删除ID为1的用户

HTTP方法    对应操作
GET         查询(Read)
POST        创建(Create)
PUT         更新(Update)
DELETE      删除(Delete)

17.3 与其他框架集成

XML
<!-- pom.xml Spring Boot项目核心依赖 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.x</version>
</parent>

<dependencies>
    <!-- Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- MyBatis-Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    
    <!-- MySQL -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
    <!-- Lombok(简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

十八、Java补充知识

18.1 匿名内部类

Java
// 正常方式:创建接口的实现类
interface Animal {
    void speak();
}

class Dog implements Animal {
    @Override
    public void speak() {
        System.out.println("汪汪!");
    }
}
Animal dog = new Dog();

// 匿名内部类方式:不需要单独创建实现类
Animal cat = new Animal() {
    @Override
    public void speak() {
        System.out.println("喵喵!");
    }
};
cat.speak();

// 实际应用:线程
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程运行中...");
    }
}).start();

// Lambda表达式简化(函数式接口)
new Thread(() -> System.out.println("Lambda线程运行中...")).start();

18.2 枚举

Java
// 简单枚举
public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER
}

// 带属性的枚举
public enum StatusCode {
    SUCCESS(200, "操作成功"),
    ERROR(500, "服务器错误"),
    NOT_FOUND(404, "资源未找到"),
    UNAUTHORIZED(401, "未授权");
    
    private final int code;
    private final String message;
    
    StatusCode(int code, String message) {
        this.code = code;
        this.message = message;
    }
    
    public int getCode() { return code; }
    public String getMessage() { return message; }
}

// 使用
Season season = Season.SPRING;
StatusCode status = StatusCode.SUCCESS;
System.out.println(status.getCode());     // 200
System.out.println(status.getMessage());  // "操作成功"

18.3 文件操作与IO

Java
// ===== File类 =====
File file = new File("D:/test.txt");
file.exists();           // 是否存在
file.isFile();           // 是否是文件
file.isDirectory();      // 是否是目录
file.getName();          // 文件名
file.getAbsolutePath();  // 绝对路径
file.length();           // 文件大小(字节)
file.createNewFile();    // 创建文件
file.mkdir();            // 创建目录
file.mkdirs();           // 创建多级目录
file.delete();           // 删除
file.list();             // 列出目录下文件名数组
file.listFiles();        // 列出目录下File对象数组

// ===== 读取文本文件 =====
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("D:/test.txt"));
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (reader != null) reader.close();
}

// try-with-resources(推荐,自动关闭)
try (BufferedReader br = new BufferedReader(
        new InputStreamReader(new FileInputStream("D:/test.txt"), "UTF-8"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}

// ===== 写入文本文件 =====
try (BufferedWriter writer = new BufferedWriter(
        new OutputStreamWriter(new FileOutputStream("D:/output.txt"), "UTF-8"))) {
    writer.write("Hello World");
    writer.newLine();
    writer.write("你好世界");
}

// ===== 读写二进制文件(复制文件)=====
try (FileInputStream fis = new FileInputStream("D:/source.jpg");
     FileOutputStream fos = new FileOutputStream("D:/target.jpg")) {
    byte[] buffer = new byte[1024];
    int len;
    while ((len = fis.read(buffer)) != -1) {
        fos.write(buffer, 0, len);
    }
}

// ===== 序列化与反序列化 =====
// 对象必须实现Serializable接口
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private transient String password;  // transient关键字:不参与序列化
}

// 序列化(对象 → 文件)
try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("D:/user.dat"))) {
    User user = new User("张三", 25);
    oos.writeObject(user);
}

// 反序列化(文件 → 对象)
try (ObjectInputStream ois = new ObjectInputStream(
        new FileInputStream("D:/user.dat"))) {
    User user = (User) ois.readObject();
    System.out.println(user.getName());
}

十九、若依框架(RuoYi)

19.1 概述

若依是一套基于Spring Boot + Vue的快速开发平台,提供了代码生成、权限管理、工作流等功能。

text
若依框架技术栈:

后端:
  - Spring Boot 2.x
  - Spring Security(权限框架)
  - MyBatis / MyBatis-Plus
  - Redis(缓存)
  - JWT(Token认证)

前端:
  - Vue 2.x
  - Element UI
  - Axios
  - Vue Router

19.2 代码生成

text
1. 在若依后台管理 → 系统工具 → 代码生成
2. 导入数据库表
3. 配置生成信息(包名、模块名、业务名等)
4. 预览/下载/生成代码

生成的代码包括:
  - Entity实体类
  - Mapper接口 + XML
  - Service接口 + 实现类
  - Controller控制器
  - Vue前端页面(列表、表单)
  - SQL脚本(菜单数据)

19.3 权限控制

text
若依权限体系:

用户(User) ← 多对多 → 角色(Role) ← 多对多 → 菜单/权限(Menu)

权限类型:
  - 菜单权限:控制左侧菜单是否显示
  - 按钮权限:控制页面按钮是否显示
  - 数据权限:控制能看到哪些数据

后端权限注解:
  @PreAuthorize("@ss.hasPermi('system:user:list')")     // 有某权限
  @PreAuthorize("@ss.hasRole('admin')")                  // 有某角色

前端权限指令:
  v-hasPermi="['system:user:add']"     // 有权限才显示
  v-hasRole="['admin']"                // 有角色才显示

19.4 文件上传下载

Java
// 后端:文件上传
@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file) {
    String fileName = FileUploadUtils.upload(RuoYiConfig.getUploadPath(), file);
    AjaxResult ajax = AjaxResult.success();
    ajax.put("fileName", fileName);
    ajax.put("url", fileName);
    return ajax;
}
vue
<!-- 前端:文件上传组件 -->
<el-upload
  :action="uploadUrl"
  :headers="headers"
  :on-success="handleSuccess"
  :before-upload="beforeUpload"
  :limit="1">
  <el-button type="primary">点击上传</el-button>
</el-upload>

19.5 数据导入导出

Java
// 导出Excel
@PostMapping("/export")
public void export(HttpServletResponse response, User user) {
    List<User> list = userService.selectUserList(user);
    ExcelUtil<User> util = new ExcelUtil<>(User.class);
    util.exportExcel(response, list, "用户数据");
}

// 导入Excel
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file) {
    ExcelUtil<User> util = new ExcelUtil<>(User.class);
    List<User> userList = util.importExcel(file.getInputStream());
    String message = userService.importUser(userList);
    return AjaxResult.success(message);
}

// 实体类上的导出注解
public class User {
    @Excel(name = "用户编号")
    private Long userId;
    
    @Excel(name = "用户名称")
    private String userName;
    
    @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
    private String sex;
    
    @Excel(name = "创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
}

19.6 定时任务

Java
// 1. 创建任务类
@Component("myTask")
public class MyTask {
    
    public void execute() {
        System.out.println("定时任务执行:" + new Date());
    }
    
    public void executeWithParam(String param) {
        System.out.println("带参数的定时任务:" + param);
    }
}

// 2. 在若依后台管理 → 系统监控 → 定时任务 中配置:
//    调用目标:myTask.execute()
//    cron表达式:0/10 * * * * ?(每10秒执行一次)

19.7 WebSocket整合

Java
@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {
    
    private static final Map<String, Session> clients = new ConcurrentHashMap<>();
    
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        clients.put(userId, session);
        System.out.println("用户 " + userId + " 连接成功,当前在线:" + clients.size());
    }
    
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("收到消息:" + message);
        // 群发
        for (Session client : clients.values()) {
            client.getBasicRemote().sendText(message);
        }
    }
    
    @OnClose
    public void onClose(@PathParam("userId") String userId) {
        clients.remove(userId);
        System.out.println("用户 " + userId + " 断开连接");
    }
    
    // 指定用户发送消息
    public static void sendToUser(String userId, String message) {
        Session session = clients.get(userId);
        if (session != null) {
            session.getBasicRemote().sendText(message);
        }
    }
}

二十、Flowable工作流

20.1 基本概念

text
工作流 = 业务流程的自动化管理

例如:请假审批流程
  员工提交请假申请 → 直接主管审批 → HR审批 → 完成

术语:
┌──────────────┬──────────────────────────────────────┐
│ 流程定义      │ 流程的模板/蓝图(BPMN文件)            │
│ (Process     │ 类似于Java中的"类"                    │
│  Definition)  │                                      │
├──────────────┼──────────────────────────────────────┤
│ 流程实例      │ 根据流程定义创建的具体实例              │
│ (Process     │ 类似于Java中的"对象"                   │
│  Instance)   │ 每次发起审批就是一个流程实例             │
├──────────────┼──────────────────────────────────────┤
│ 任务(Task)    │ 流程中的一个具体步骤                   │
│              │ 如:"主管审批"就是一个任务               │
├──────────────┼──────────────────────────────────────┤
│ 网关(Gateway) │ 流程中的分支/合并节点                   │
│              │ 排他网关、并行网关、包容网关             │
├──────────────┼──────────────────────────────────────┤
│ 流程变量      │ 流程执行中传递的数据                    │
│ (Variable)   │ 如:请假天数、请假原因等                │
└──────────────┴──────────────────────────────────────┘

20.2 核心操作

Java
@Service
public class FlowService {
    
    @Autowired
    private RuntimeService runtimeService;
    
    @Autowired
    private TaskService taskService;
    
    @Autowired
    private RepositoryService repositoryService;
    
    @Autowired
    private HistoryService historyService;
    
    // ===== 1. 部署流程定义 =====
    public void deployProcess() {
        repositoryService.createDeployment()
            .addClasspathResource("processes/leave.bpmn20.xml")
            .name("请假流程")
            .deploy();
    }
    
    // ===== 2. 发起流程(创建流程实例)=====
    public void startProcess(String processKey, Map<String, Object> variables) {
        // variables中可以包含:申请人、请假天数、原因等
        variables.put("initiator", "张三");
        
        ProcessInstance instance = runtimeService.startProcessInstanceByKey(processKey, variables);
        System.out.println("流程实例ID:" + instance.getId());
    }
    
    // ===== 3. 查询待办任务 =====
    public List<Task> getMyTasks(String userId) {
        return taskService.createTaskQuery()
            .taskAssignee(userId)     // 办理人
            .orderByTaskCreateTime()  // 按创建时间排序
            .desc()
            .list();
    }
    
    // ===== 4. 审批通过 =====
    public void approve(String taskId, Map<String, Object> variables) {
        variables.put("approved", true);
        taskService.complete(taskId, variables);
    }
    
    // ===== 5. 审批驳回 =====
    public void reject(String taskId, String reason) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("approved", false);
        variables.put("rejectReason", reason);
        taskService.complete(taskId, variables);
    }
    
    // ===== 6. 撤销流程 =====
    public void cancelProcess(String processInstanceId, String reason) {
        runtimeService.deleteProcessInstance(processInstanceId, reason);
    }
    
    // ===== 7. 查询已办任务 =====
    public List<HistoricTaskInstance> getCompletedTasks(String userId) {
        return historyService.createHistoricTaskInstanceQuery()
            .taskAssignee(userId)
            .finished()
            .orderByHistoricTaskInstanceEndTime()
            .desc()
            .list();
    }
    
    // ===== 8. 转办(将任务交给其他人)=====
    public void transferTask(String taskId, String newAssignee) {
        taskService.setAssignee(taskId, newAssignee);
    }
    
    // ===== 9. 委派 =====
    public void delegateTask(String taskId, String delegateUser) {
        taskService.delegateTask(taskId, delegateUser);
    }
}

20.3 网关与条件

text
排他网关(Exclusive Gateway):
  只走一条分支(满足条件的第一条)
  
  提交申请 → [排他网关] → 天数<=3? → 主管审批
                       → 天数>3?  → 经理审批

并行网关(Parallel Gateway):
  所有分支同时执行,等待所有分支完成后合并
  
  提交申请 → [并行网关] → 主管审批
                       → HR审批
                       → [并行网关] → 完成
  (主管和HR同时审批,都通过才完成)

会签(Multi-instance):
  多个人审批同一个任务
  
  或签:任意一人通过即可
  会签:所有人都通过才可
  票签:超过一定比例通过即可
XML
<!-- BPMN中的条件表达式 -->
<sequenceFlow id="flow1" sourceRef="gateway1" targetRef="managerApprove">
    <conditionExpression xsi:type="tFormalExpression">
        ${days > 3}
    </conditionExpression>
</sequenceFlow>

<sequenceFlow id="flow2" sourceRef="gateway1" targetRef="leaderApprove">
    <conditionExpression xsi:type="tFormalExpression">
        ${days <= 3}
    </conditionExpression>
</sequenceFlow>

20.4 监听器

Java
// 任务监听器
public class MyTaskListener implements TaskListener {
    
    @Override
    public void notify(DelegateTask delegateTask) {
        String eventName = delegateTask.getEventName();
        
        switch (eventName) {
            case "create":
                // 任务创建时
                System.out.println("任务创建:" + delegateTask.getName());
                // 可以在这里发送通知
                break;
            case "assignment":
                // 任务分配时
                System.out.println("任务分配给:" + delegateTask.getAssignee());
                break;
            case "complete":
                // 任务完成时
                System.out.println("任务完成:" + delegateTask.getName());
                break;
        }
    }
}

// 执行监听器
public class MyExecutionListener implements ExecutionListener {
    
    @Override
    public void notify(DelegateExecution execution) {
        String eventName = execution.getEventName();
        
        if ("start".equals(eventName)) {
            System.out.println("流程/节点开始");
        } else if ("end".equals(eventName)) {
            System.out.println("流程/节点结束");
        }
    }
}

二十一、项目实战阶段

项目开发流程

text
第1周:
├── 项目分组(3-5人一组)
├── 组内规划与工作进度表
├── 需求讨论、功能拆解
├── 个人开发模块设计与讨论
│   ├── 数据库设计(ER图、表结构)
│   ├── 接口设计(API文档)
│   └── 页面原型设计
第2-4周(14天编码):
├── 使用若依框架搭建项目骨架
├── 代码生成基础CRUD
├── 编写业务逻辑代码
├── 前端页面开发
├── 接口联调
├── 工作流集成
第5周:
├── 组合联调(前后端对接)
├── 功能测试与Bug修改
├── PPT汇报准备及演练
└── 代码验收、考核

项目开发规范

text
1. 代码规范:
   - 统一的命名规范(驼峰命名)
   - 必要的注释
   - 统一的返回格式(Result类)
   - 统一的异常处理

2. Git协作:
   - master/main分支:稳定版本
   - develop分支:开发分支
   - feature/xxx分支:功能分支
   - 每天至少提交一次代码

3. 接口规范:
   - RESTful风格
   - 统一的API前缀
   - 统一的请求/响应格式

4. 数据库规范:
   - 表名、字段名使用下划线命名
   - 每张表必须有主键
   - 必要的索引
   - 创建时间、更新时间字段

以上是从事务处理:JDBC对事务支持开始,到课程结束的所有内容的详细剖析。整个课程体系涵盖:

  1. JDBC事务与BaseDao → 数据库操作基础
  2. Servlet/Filter → Web应用基础
  3. JavaScript/jQuery → 前端基础
  4. Vue.js + Element UI → 现代前端框架
  5. MyBatis/MyBatis-Plus → ORM持久层框架
  6. Spring/SpringMVC/Spring Boot → 后端核心框架(SSM整合)
  7. 若依框架 → 企业级快速开发平台
  8. Flowable工作流 → 业务流程自动化
  9. 项目实战 → 综合应用以上所有技术

posted on 2026-03-29 16:08  GKLBB  阅读(5)  评论(0)    收藏  举报