前端学 Java 课程笔记

前端学 Java 课程笔记

本笔记面向前端开发人员,以前端概念类比 Java,速成实战导向。

不涉及深度 JVM 优化、数据库优化、分布式等进阶内容。


文章目录

  • [第 0 章 介绍](#第 0 章 介绍)
  • [第 1 章 Java 基础](#第 1 章 Java 基础)
    • [1-1 安装和准备](#1-1 安装和准备)
    • [1-2 Java 的基本写法](#1-2 Java 的基本写法)
    • [1-3 运行 Java 程序](#1-3 运行 Java 程序)
    • [1-4 包的概念和使用](#1-4 包的概念和使用)
    • [1-5 修饰符](#1-5 修饰符)
    • [1-6 Java 中的基本类型](#1-6 Java 中的基本类型)
    • [第 2 章 IO 与网络](#第 2 章 IO 与网络)
    • [第 3 章 工程化与数据库](#第 3 章 工程化与数据库)
    • [第 4 章 Spring Boot 实战](#第 4 章 Spring Boot 实战)
      • [4-1 Spring Boot 创建项目](#4-1 Spring Boot 创建项目)
      • [4-2 定义第一个接口](#4-2 定义第一个接口)
        • 核心注解
        • [注解在 Spring Boot 中的意义](#注解在 Spring Boot 中的意义)
      • [4-3 请求参数处理](#4-3 请求参数处理)
      • [4-4 项目层级规范](#4-4 项目层级规范)
      • [4-5 设计一个接口的分层](#4-5 设计一个接口的分层)
        • [目标:根据 id 查询用户信息](#目标:根据 id 查询用户信息)
        • [Lombok 库](#Lombok 库)
      • [4-6 Spring 依赖注入体系(IOC)](#4-6 Spring 依赖注入体系(IOC))
      • [4-7 操作数据库(上)](#4-7 操作数据库(上))
      • [4-8 操作数据库(下)](#4-8 操作数据库(下))
        • [MyBatis 使用](#MyBatis 使用)
        • [MyBatis-Plus 使用](#MyBatis-Plus 使用)
      • [4-9 登录与注册接口](#4-9 登录与注册接口)
      • [4-10 修改密码和注销](#4-10 修改密码和注销)
      • [4-11 用 MyBatis-Plus 简化增删改查](#4-11 用 MyBatis-Plus 简化增删改查)
        • [第一步:定义 Entity](#第一步:定义 Entity)
        • [第二步:定义 Mapper](#第二步:定义 Mapper)
        • 常用方法
        • [QueryWrapper 条件构造](#QueryWrapper 条件构造)
      • [4-12 拦截器、过滤器和全局配置](#4-12 拦截器、过滤器和全局配置)
      • [4-13 CORS 跨域设置](#4-13 CORS 跨域设置)
      • [4-14 Token 体系(JWT)](#4-14 Token 体系(JWT))
        • [Token 流程](#Token 流程)
        • [JWT 组成](#JWT 组成)
        • 使用的库(jjwt)
        • [生成 Token](#生成 Token)
        • [验证 Token(拦截器中)](#验证 Token(拦截器中))
      • [4-15 利用 Session 做验证码](#4-15 利用 Session 做验证码)
        • [什么是 Session](#什么是 Session)
        • [Spring Boot 中使用 Session](#Spring Boot 中使用 Session)
        • [图形验证码(Kaptcha 库)](#图形验证码(Kaptcha 库))
        • [Base64 操作](#Base64 操作)
        • 相关类
      • [4-16 用户列表查询(分页)](#4-16 用户列表查询(分页))
      • [4-17 页面模块](#4-17 页面模块)
      • [4-18 查询用户权限](#4-18 查询用户权限)
      • [4-19 ~ 4-22 综合实战模块](#4-19 ~ 4-22 综合实战模块)
        • [4-19 订单模块](#4-19 订单模块)
        • [4-20 用户余额](#4-20 用户余额)
        • [4-21 定时任务](#4-21 定时任务)
        • [4-22 统计查询](#4-22 统计查询)
    • [附录:前端开发者学 Java 的关键映射](#附录:前端开发者学 Java 的关键映射)

第 0 章 介绍

特点

  • 面向前端开发人员:大量以前端概念类比 Java,复杂代码先写 JS 再写 Java

内容

从 0 基础 → Java 语法 → Java 生态 → Spring Boot + MyBatis 实战开发。


第 1 章 Java 基础

1-1 安装和准备

安装步骤

  1. 下载 JDK:Oracle 官网下载 Java 8,Windows 下载 exe 包,傻瓜式安装
  2. 配置环境变量:Java 安装后不会自动注册为系统命令,需手动配置

环境变量本质

  • 命令行输入一个命令,本质是在环境变量 Path 中查找该命令对应的可执行程序
  • 配置两步:① 新建一个环境变量(如 JAVA_HOME)指向 JDK 安装目录;② 将 %JAVA_HOME%\bin 添加到 Path 中

1-2 Java 的基本写法

核心原则

Java 中一切代码都基于类,方法、变量都不能在类外定义。

类的基本结构

java 复制代码
// 类
// 修饰符public + 类关键字class  + 自定义类名Run 
public class Run {
	// 方法
	// 修饰符public  + 修饰符static + 方法返回类型void  + 方法名main  + 参数类型String[] + 方法接受的参数args
    public static void main(String[] args) {
        // 变量:修饰符(可省略) + 类型 + 名字
        final String str = "aaa";
        // 属性:修饰符 + 类型 + 名字
        private String sort = "bbb";
    }
}

变量、属性的基本结构

  • 局部变量(方法内的变量 / 常量)

    java 复制代码
    //修饰符 变量类型 变量名 = 值
    final String str = "aaa";
    /*
     * ✅**局部变量**:写在方法、代码块里面
     * 修饰符:**只能写 `final`**,不能用 `private/public/protected`
     * 加`final`后就变成**常量**,值不能修改
     * 局部变量**没有默认值**,使用前必须手动赋值
     * 
     * 变量一般不写修饰符,要写也只能写`final`
     */
  • 属性(成员变量,写在类里面、方法外面)

    java 复制代码
    // 修饰符 变量类型 属性名 = 值;
    private String sort = "bbb";
    /*
    * ✅**属性 / 成员变量**:属于类,写在类中,方法之外
    * 修饰符可以多种:`private、public、protected、static、final`可以组合使用
    * 加`final`后就变成**常量**,值不能修改
    * 有默认初始值,不手动赋值也有默认值(String 默认 null)
    * 
    * 属性修饰符可以很多种
    */
       ```
  • 核心区别总结

    局部变量(方法内) 属性(成员变量,类中)
    位置方法 循环 代码块内部类里面,方法外
    可用修饰符 final private/public/protected/static/final
    默认值 ❌无,必须赋值才能用 ✅有默认值
    生命周期 方法执行完就销毁 对象存在就存在
    java 复制代码
    public class Demo {
        // 👉这是【属性】,可以用private修饰
        private String sort = "bbb";
    
        public void test(){
            // 👉这是【局部变量】,只能加final,不能写private
            final String str = "aaa";
        }
    }

代码对比

  • Java & JS 对照完整 Demo

    🔹Java Demo(文件名叫 Run.java)

    规则:public 类名 = 文件名;方法必须写在类内部;强类型,参数个数类型严格校验;局部变量具备方法 + 块级作用域

    java 复制代码
    // Run.java 文件名必须是 Run
    public class Run {
        // 类属性(成员变量,方法外面)
        public String attr = "我是类属性";
    
        // Java方法,属于类
        public int calc(int b1, int b2) {
            // 方法局部变量:仅本方法内可访问
            int b = 123;
            b -= 1;
    
            if (true) {
                // 块级局部变量:仅if大括号内部有效
                int blockVal = 888;
                System.out.println("if块内:" + blockVal);
            }
            // System.out.println(blockVal); // ❌编译报错,出大括号不能访问
    
            return b + b1 + b2;
        }
    
        public static void main(String[] args) {
            Run run = new Run();
            int res = run.calc(10, 20); // ✅必须传2个int参数
            System.out.println("Java方法返回结果:" + res);
    
            // run.calc(); // ❌编译报错!必须传入2个参数,Java强校验
        }
    }
    
    // 同一个文件可以放普通class,不能加public
    class OtherClass {
    
    }
    /*
    *运行输出:
    *
    *if块内:888
    *Java方法返回结果:152
    */

    🔹JavaScript Demo(run.js)

    无文件绑定规则;函数可以独立存在;弱类型,传参随意;let/const块级作用域,var只有函数作用域

    javascript 复制代码
    // JS独立函数,不需要依附类
    function calc(b1, b2) {
        // 函数局部变量,仅函数内部
        let b = 123;
        b -= 1;
    
        if (true) {
            // let块级作用域,只在if{}内
            let blockVal = 888;
            console.log("if块内:", blockVal);
        }
        // console.log(blockVal); // ❌运行报错,出大括号访问不到
    
        return b + b1 + b2;
    }
    
    // ✅正常传参调用
    let result1 = calc(10, 20);
    console.log("JS正常传参返回:", result1);
    
    // ✅JS允许不传参数,不会报错,b1、b2变成 undefined,相加得到NaN
    let result2 = calc();
    console.log("JS不传参返回:", result2);
    
    /*
    *运行输出:
    *
    *if块内: 888
    *JS正常传参返回: 152
    *if块内: 888
    *JS不传参返回: NaN
    */

关键规则

  1. 一个 .java 文件中,与文件同名的 public 类只能有一个,可以有其他非 public 类
  2. 写方法/变量/属性的基本模板:修饰符 → 类型 → 名字
  3. Java 是强类型语言,定义任何东西都要说明类型
  4. 修饰符通常在最开头,一般只能有一个;方法和属性可以有两个不同功能的修饰符(修饰符可以不写,当不写的时候等同于写了一个默认的修饰符)
  5. 类是java 的基本构成单元 一切逻辑,方法,变量都要先写一个类

1-3 运行 Java 程序

程序入口

Java 程序从 public static void main(String[] args) 方法开始执行,程序中必须有一个类包含 main 方法。

编译运行流程

复制代码
源代码:xx.java
       ↓ 【有中文时  javac -encoding UTF-8 xx.java】
javac xx.java     【编译命令,要写 .java后缀】
       ↓
字节码文件:xx.class   编译产出
       ↓
java xx【运行命令,只写类名,❌禁止写 .class】
       ↓
程序执行
  • .java 文件不能直接运行,需先用 javac 编译为 .class 字节码
  • 有中文时需用 -encoding 指定编码
  • 实际开发中借助 IDE(如 IDEA、VSCode)一键运行

1-4 包的概念和使用

什么是包

包(Package)可类比前端的模块,一个文件夹就是一个包,包名就是文件夹路径。

包的基本原则

  1. 同包内的类可以互相直接使用(除非修饰符限制了访问范围)
  2. 不同包之间需要引入 :用 package 定义包,用 import 引入包

两个核心操作

java 复制代码
// 1. 定义包(文件第一行),包名与文件夹路径一致
package com.study.demo;

// 2. 引入包
import com.study.other.Run;   // 引入单个类
import com.study.other.*;     // 批量引入

拓展点

  • 同包可以直接用,但子包不能用父包的类
  • 一个 java 文件可以有多个类,但只有一个 public 类,外部只能访问到 public 类,同包内可访问第二个类

1-5 修饰符

访问修饰符(控制访问范围,只能存在一个)

修饰符 访问范围
public 任何其他类都可访问
protected 子类和同包可访问
默认(不写) 同包可访问
private 只有类自己可访问

范围从大到小:public > protected > 默认 > private

非访问修饰符(可与访问修饰符叠加)

修饰符 作用
static 静态属性/方法,只能用类名访问
final 变量不能重新赋值;类不能被继承;方法不能被重写
abstract 定义抽象类、抽象方法
synchronized 同时只能被一个线程访问
transient 标记不被序列化的字段
volatile 多线程并发访问时确保可见性

1-6 Java 中的基本类型

类型分类

  • 基本类型 :四类八种(整型、浮点、布尔、字符
  • 引用类型字符串、对象、数组、枚举等

四类八种基本类型

Java 8 种基本数据类型,每一种都有对应的包装类,包装类内置静态常量:

  • MAX_VALUE:该类型最大值
  • MIN_VALUE:该类型最小值
  • 整型(存储整数)

    类型 字节 取值范围
    byte 1 -128 ~ 127
    short 2 -32768 ~ 32767
    int 4 -21亿 ~ 21亿(默认数字类型)
    long 8 -2^63 ~ 2^63-1

    注意:定义 long 类型字面量,数字末尾必须加 L /l ,推荐大写L

    java 复制代码
    public class IntDemo {
        public static void main(String[] args) {
            // byte 1字节
            byte b1 = 10;
            // byte b2 = 130; // 报错,超出byte范围 -128~127
    
            // short 2字节
            short s1 = 200;
    
            // int 4字节,整数默认类型
            int i1 = 10000;
    
            // long 8字节,数字后面加L
            // long num = 8888888888; // ❌报错,数字默认int,超出int范围
            long l1 = 9999999999L;
            // 1. Java 写整数字面量,**默认就是 int 类型**
            // 2. long 类型赋值大数,末尾不加`L`会编译报错
    
            System.out.println("byte值:" + b1);
            System.out.println("short值:" + s1);
            System.out.println("int值:" + i1);
            System.out.println("long值:" + l1);
            // byte值:10
            // short值:200
            // int值:10000
            // long值:9999999999
    
            // 变量溢出:超过类型最大值,不会报错,但数值会错乱。
            int maxInt = 2147483647;
            System.out.println(maxInt + 1); // 溢出,输出负数,输出:`-2147483648`
        }
    }
  • 浮点(带小数点)

    类型 字节 精度
    float 4 6-7 位小数
    double 8 15-16 位小数(默认小数类型)

    ⚠️重点规则

    1. 直接写小数字面量,默认是 double 类型
    2. float赋值,数字末尾必须加 F/f ,推荐大写F
    3. 浮点数存在精度丢失问题,不要直接用==比较两个浮点数
    java 复制代码
    public class FloatDemo {
        public static void main(String[] args) {
            // float 末尾加F
            float f1 = 3.14F;
            // float f2 = 3.14; //❌报错,3.14默认double,不能直接赋值给float
    
            // double,小数默认类型,可以不加后缀
            double d1 = 3.1415926;
            double d2 = 3.1415926D; //也可以加D/d,可省略
    
            System.out.println(f1); // 3.14
            System.out.println(d1); // 3.1415926
        }
    }

    包装类常量(可复制)

    java 复制代码
    //float
    System.out.println(Float.MAX_VALUE);  //float最大正数
    System.out.println(Float.MIN_VALUE);  //float最小的正小数(不是负数)
    
    //double
    System.out.println(Double.MAX_VALUE); //double最大正数
    System.out.println(Double.MIN_VALUE); //double最小的正小数

    精度丢失示例(高频坑)

    java 复制代码
    double a = 0.1 + 0.2;
    System.out.println(a); //输出 0.30000000000000004,不等于0.3
    // ❌不能写 if(0.1+0.2 == 0.3)

    补充:MIN_VALUE对 float/double,代表最小正数 ,不是最小负数;最小负数写 -Float.MAX_VALUE-Double.MAX_VALUE

  • 其他

    boolean:只能 true / false

    char:单个字符 ,字符一定是单引号 ' ',字符串一定是双引号 " "

    java 复制代码
    public class CharBoolDemo {
        public static void main(String[] args) {
            // boolean
            boolean flag1 = true;
            boolean flag2 = false;
    
            // char 单引号,只能放1个字符
            char c1 = 'A';
            char c2 = '中';
            char c3 = '9';
            char c4 = '\n'; //转义换行符
    
            // char可以直接赋值数字,对应Unicode码
            char c5 = 65;
            System.out.println(c5); //输出 A
    
            // String 字符串,双引号,引用类型,不是基本类型
            String str = "ABC中文";
    
            System.out.println(flag1);
            System.out.println(c1);
            System.out.println(c2);
            System.out.println(str);
           // true
           // A
           // 中
           // ABC中文
        }
    }

    注意

    1. char c = 'AB'; ❌报错,单引号只能一个字符
    2. char c = "A"; ❌报错,字符必须单引号;双引号是 String 字符串
    3. Java 中boolean不能用 1、0 代替 ,不能写 boolean b = 1;

    为什么不都用最大的类型?

    内存有限,能用小类型就用小类型,节省内存。

类型转换

  • 自动转换 :小转大(如 byte → int),无风险自动完成
    • byte、short 相加结果自动转为 int
    • char 赋值给整型自动转为 ASCII 码
    • 顺序:byte → short → int → long → float → double
  • 强制转换 :大转小有风险,需手动强转 (int) someDouble
  • 根本不兼容的类型无法转换(如对象转数字、布尔转其他)
java 复制代码
public class ConvertDemo {
    public static void main(String[] args) {
        // ==========自动转换 小转大==========
        byte b = 10;
        int i = b;   // byte自动转int ✅

        char ch = 'A';
        int num = ch; // char自动转数字,A对应65
        System.out.println(num); //65

        byte b1=1,b2=2;
        int res = b1 + b2; // byte相加,结果自动提升为int

        // ==========强制转换 大转小==========
        double d = 3.99;
        int a = (int) d; //强制截取整数部分,小数直接丢掉
        System.out.println(a); //输出3

        int big = 130;
        byte bb = (byte) big;
        System.out.println(bb); //超出byte范围,溢出,输出‑126

        long lo = 1000L;
        short sh = (short) lo;

        // ==========不允许转换==========
        // boolean bl = true;
        // int x = (int)bl; // ❌ boolean不允许和数字转换
       
        
        // `byte`、`short`、`char`互相运算,**先提升为 int 再计算**,结果一定 int

        short s1 = 1;
        short s2 = 2;
        //short s3 = s1 + s2; //❌ s1+s2结果是int,不能直接赋值short
        short s3 = (short)(s1 + s2); //✅需要强制转换
        int m = (int)3.5 + 4.8; // 只把3.5转int变成3,再加4.8,结果是double 7.8
        int n = (int)(3.5 + 4.8); //先求和,再整体强转,结果8
    }
}

1-7 类和对象

Object 是所有类的源头

类比 JS 原型链的源头是 Object,Java 中所有类(包括 null)都是 Object 的子类。因此:

  • 所有类型都可以用 Object 概括
  • 所有类都有 Object 的方法,如 toString()getClass()equals()
引用类型的特性

引用类型变量存储的是对象的内存地址引用,和 JS 引用类型同理:

  1. 两个内容相同的对象用 == 判断不相等(比的是地址),判断内容相等用 equals()

  2. 一个对象赋值给两个变量,修改一个另一个也会变(指向同一地址)

    new User() → 内存中创建对象(假设地址 1002)
    User a = new User() → a 存的是地址 1002
    User b = a → b 存的也是地址 1002


1-8 字符串操作

Java 中字符串是引用类型(对象),不能按 JS 的逻辑处理。

创建
java 复制代码
String str1 = "abc";
String str2 = new String("asdasd");
常见操作
操作 方法
判断相等 equals()(不能用 ==)
空字符串判断 isEmpty()equals("")
替换 replace() / replaceAll()
正则匹配 matches()
分割为数组 split()
查找位置 indexOf()
拼接 +concat()StringBuilderStringBufferString.join()
格式化 String.format()(类似模板引擎占位替换)

大量字符串拼接推荐用 StringBuilder,性能优于 +


1-9 Map 和枚举

Map(键值对)

类比前端用对象做键值对映射。

java 复制代码
// 前端
let mapObj = {};
mapObj["a"] = 123;

// Java
Map<String, Number> mapObj = new HashMap<>();
mapObj.put("a", 123);
mapObj.put("b", 456);

常见操作put(增/改)、remove(删)、get(查)、clear(清空)、keySet(所有 key)、values(所有值)、entrySet(键值对对象)、containsKey(是否存在 key)、forEach(遍历)

Map 的种类

  • HashMap:无序(最常用)
  • LinkedHashMap:按 put 顺序排序
  • TreeMap:按 key 升序
枚举(enum)

用于定义一组状态常量,如订单状态。

java 复制代码
public enum OrderStatus {
    UNFINISH("未完成", "UNFINISH"),
    FINISH("完成", "FINISH"),
    FAIL("失败", "FAIL");

    public String name;
    public String code;

    OrderStatus(String name, String code) {
        this.name = name;
        this.code = code;
    }
}

// 使用
System.out.println(OrderStatus.FAIL.name);

枚举可自由拓展方法(如转 JSON、转 Map),属性可设为私有提升安全性。


1-10 数组和 List、Set

标准数组
  • 内存中连续存储 ,元素同类型、同大小、长度固定
  • 前端的数组并不是真正的数组(长度可变)
  • 业务开发中很少用标准数组,太固定
java 复制代码
int[] arr1 = new int[4];              // 指定长度
int[] arr2 = new int[]{1, 2, 3};      // 指定元素
int[] arr3 = {1, 2, 3};               // 简写
  • 取值/改值:下标操作 arr[0]
  • 不允许增删(长度固定)
  • 遍历:普通 for、增强 for(for :)、转 stream 后 forEach
集合家族(Collection)

因为数组太固定,业务中用集合家族,类比前端的 JS 数组。

复制代码
Collection(抽象接口,不可 new)
├── List(有序可重复,类似 JS 数组)
│   ├── ArrayList(数组实现,最常用)
│   └── LinkedList(链表实现)
├── Set(元素唯一)
│   ├── HashSet
│   ├── LinkedHashSet
│   └── TreeSet
└── Queue(队列,先进先出)

橙色 = 抽象接口不可 new;蓝色 = 具体实现类可 new。

List 操作
  • 创建:new ArrayList<>()
  • 增删改查:addremovecleargetset
  • 遍历:for、增强 for、forEach、迭代器
  • filter/map/sort:转 stream 后操作,转回 List 用 collect

ArrayList vs LinkedList:操作 API 一样,差异在内部存储(数组 vs 链表),大多数情况 ArrayList 更优。

Set

操作和 List 基本一致,区别是元素不能重复


1-11 null 以及 null 的处理

null 的概念
  • null 表示"无对象"或"空引用",类似前端的 null
  • null 是唯一的值,只有 null == null 为 true
  • null 本身没有类型,但可以赋值给任何引用类型
  • 常见出现场景:引用类型数组默认值、数据库查询无结果、对象初始无值
Optional(Java 8 引入)

当方法可能返回 null 时,用 Optional 包装,强制调用者处理空值。

方法 作用
Optional.of() 包装非空值(为 null 会报错)
Optional.ofNullable() 包装可能为 null 的值
Optional.empty() 创建空 Optional
isPresent() 判断是否有值
get() 获取值(空则报错)
orElse(default) 空时返回默认值
ifPresent(consumer) 有值时执行操作
isEmpty() Java 11+,判断是否为空
ifPresentOrElse() Java 9+,有值执行第一个,空执行第二个

核心意义:如果方法可能返回 null,用 Optional 作为返回类型,强制使用者记得判空


1-12 构造函数和继承

构造函数
  • 类比 JS 的构造函数,在 new 类时执行
  • Java 中构造函数一定存在,不写则有默认空构造函数

写法要点

  1. 名字必须和类名相同
  2. 没有返回类型
  3. 可以有多个(重载),new 时根据参数匹配
  4. 修饰符可用访问修饰符,private 会导致外部无法直接 new
java 复制代码
public class Dog {
    public Dog(String name) {  // 构造函数
        // 初始化逻辑
    }
}
继承(extends)
java 复制代码
public class Keji extends Dog {
    // 子类继承父类
}

注意点

  1. 父类的私有属性被继承但子类无法直接访问
  2. 父类如果显式定义了有参构造函数,子类必须定义构造函数并调用 super()

1-13 重载和重写

重载(Overload)

同一个类中多个同名方法 ,但参数不同(个数/类型/顺序),调用时根据参数匹配执行哪个。这就是 Java 的多态。

java 复制代码
public int add(int a, int b) { return a + b; }
public int add(int a, int b, int c) { return a + b + c; }  // 重载
重写(Override)

子类定义一个与父类方法名、参数完全相同的方法,覆盖父类实现。

java 复制代码
// 父类
protected int doCalc(int a, int b) { return a + b; }

// 子类
@Override
protected int doCalc(int a, int b) { return a - b; }  // 重写

重写规则

  1. 方法名、参数列表必须相同
  2. 返回类型相同或兼容
  3. 访问权限不能更严格(如不能从 public 变 protected)
@Override 注解

加上 @Override 明确告诉编译器这是重写,编译器会按重写规则校验,防止误写成重载。功能上无意义,是辅助说明。

重载 vs 重写的判断
  • 方法名+参数完全相同 → 重写
  • 方法名相同但参数不同 → 重载(即使在子类中)

1-14 抽象类和接口的作用

抽象类(abstract class)
  1. 自身不能被实例化,作用是给子类继承
  2. 可以定义有实际功能的属性、构造函数和方法
  3. 抽象方法(abstract)在子类中必须实现------这是抽象类最大的意义
java 复制代码
public abstract class Animal {
    public abstract void speak();  // 抽象方法,子类必须实现
    public void breathe() {        // 普通方法,有实现
        System.out.println("呼吸");
    }
}
接口(interface)
  1. 一个类可以实现多个接口
  2. 接口里不能定义普通属性,只能定义静态常量
  3. 接口里所有方法默认都是 public abstract
java 复制代码
public interface Pay {
    void pay(double amount);  // 默认为 public abstract
}
抽象类 vs 接口
对比项 抽象类 接口
多继承 不可以(单继承) 可以(多实现)
可写内容 实际方法、属性 抽象方法、静态常量
抽象层次 抽象某一个种类 抽象一种行为
使用场景 规定某一种类的基本模板 定义一种行为能力,不固定归属某个类

记忆:抽象类是"是什么",接口是"能做什么"。


1-15 错误类型与处理

异常体系
复制代码
Throwable
├── Error(严重错误,必中断,无法恢复)
│   ├── OutOfMemoryError(内存不足)
│   └── StackOverflowError(栈溢出/无限递归)
└── Exception
    ├── 受检异常(必须 try-catch 或 throws 声明)
    │   ├── IOException(IO 流异常)
    │   └── SQLException(数据库操作失败)
    └── 非受检异常(RuntimeException,可不处理,但不处理会中断线程)
        ├── NullPointerException(空指针)
        ├── ArrayIndexOutOfBoundsException(数组越界)
        └── ArithmeticException(算术错误)
try-catch-finally
java 复制代码
try {
    // 可能异常的代码
} catch (IOException e) {
    // 捕获 IO 异常,写日志,不要直接抛出
} catch (SQLException e) {
    // 多个 catch,按类型匹配
} finally {
    // 无论是否异常都执行
}

要点

  1. catch 可以有多个,按错误类型匹配
  2. catch 中可以继续抛出,但建议写入日志而非直接抛出
  3. 方法如果抛出受检异常,必须用 throws 声明
自定义异常

继承 Exception(受检)或 RuntimeException(非受检),写构造函数即可。

java 复制代码
public class MyException extends RuntimeException {
    public MyException(String msg) {
        super(msg);
    }
}
总结
  • Error 必中断;其他异常被 try-catch 捕获且 catch 中不再抛出就不会中断
  • 受检异常必须处理(try-catch 或 throws);非受检异常可选择不处理
  • 业务中常用自定义异常统一处理业务错误

第 2 章 IO 与网络

2-1 流的概念与文件读写

什么是流

Java 与外部系统(磁盘、网络)进行数据交互,基本都以的形式。比如读文件、接收前端请求、返回响应数据。

为什么要用流

如果一个文件 2G,内存只有 1G,一次性读入会内存溢出。流的思路是:一点点读取,处理完再读下一部分,类比用管子把水从一个桶导到另一个桶,而不是直接抱桶倒。

输入流 / 输出流
  • 输入流 InputStream:Java 从外部接收数据(读文件、接收请求)
  • 输出流 OutputStream:Java 向外部发送数据(写文件、返回响应)
主流子类
特点
FileInputStream / FileOutputStream 最基础的文件流,无缓冲区
BufferedInputStream / BufferedOutputStream 自带缓冲区(默认 8KB),大文件操作效率高

2-2 专门的文件读写类

实际接口开发中很少直接操作文件,了解即可。

NIO 包(Java 7+,内置)

两大核心类:

  • Paths:文件路径相关操作
  • Files:文件读写、获取文件信息

Files 常用操作:判断存在、判断目录、获取目录内容、获取文件信息、读、写、拷贝、删除、新建/删除文件夹。

IO.File 类(老版本)

老版本 Java 用的文件操作类,只能提供文件信息和路径信息,不能直接读写文件内容。现在基本不用。

第三方包

Java 自带的文件操作有局限(如不能直接拷贝文件夹、不能删除非空文件夹),常用第三方包:

  • org.apache.commons.io.FileUtils

2-3 Java 开启 HTTP 服务

本节课用 Java 原生方式开 HTTP 服务,仅用于理解接口本质,实际开发用 Spring Boot。

接口服务的本质

四步走:

  1. 开启服务:一个一直运行的程序,等待请求
  2. 定义路由:哪个地址对应哪段代码
  3. 构建响应:每个路由中,收到请求后构建响应头 + 响应体
  4. 发送响应:把响应体返回给前端
原生方式涉及的类
  • HttpServer:创建服务器对象
  • InetSocketAddress:指定端口
  • HttpHandler:处理请求的接口
  • HttpExchange:封装请求和响应对象
关键注意点
  1. 返回内容基本都要以 byte 数组形式搭载在 OutputStream 中
  2. 字符串转 byte 数组用 getBytes()
  3. 响应体长度必须设置准确(用 byte 数组的 length,字符串 length 不一定准)

2-4 根据 id 模糊搜索(实战)

目标

写一个 GET 接口,接收 query 上的 id 参数,找出对应用户并返回。

涉及知识点
  1. 开启 HTTP 服务(HttpServer)
  2. 字符串操作(解析 query 参数)
  3. List 查找(遍历匹配)

2-5 使用第三方包处理 JSON

目标

用第三方库 Jackson 处理对象转 JSON(上节课手动拼接 JSON 太麻烦)。

知识点
  1. Jackson 库 :Java 最主流的 JSON 处理库
    • jackson-corejackson-databindjackson-annotations
  2. 如何使用第三方包:下载 jar 包 → 添加到项目 classpath → import 使用

后续会用 Maven 管理依赖,不再手动下载 jar。


第 3 章 工程化与数据库

3-1 Maven 的概念与安装

类比前端
前端 Java
npm(包管理) Maven(包管理 + 构建)
webpack/vite(打包) Maven(打包)
Maven 核心工作
  1. 依赖管理 :在 pom.xml(类比 package.json)中写依赖,自动下载安装
  2. 项目构建:打包成 jar 包(库)或 war 包(服务)
安装
  • 官网下载 zip → 解压 → 配置环境变量 → mvn 命令可用
  • 也可以不全局安装,项目生成时自带 Maven Wrapper(类似前端项目本地 webpack)
必要配置
  1. 国内镜像:加速依赖下载(一般必配)
  2. 依赖存放路径 :可不配,默认 ~/.m2/repository

3-2 Maven 管理模块和构建

pom.xml

Maven 的配置文件,等同于前端的 package.json + webpack.config.js

依赖配置
xml 复制代码
<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>      <!-- 组织ID -->
        <artifactId>javax.servlet-api</artifactId>  <!-- 项目ID -->
        <version>4.0.1</version>              <!-- 版本 -->
        <scope>provided</scope>               <!-- 范围(类似 devDependencies) -->
    </dependency>
</dependencies>
  • groupId :组织标识,也决定项目代码目录(通常 src/main/java/${groupId 按点分割}
  • artifactId:项目标识
  • version:版本号
  • scope:依赖范围(compile/test/provided/runtime)
查找依赖
Maven 常用命令
  • mvn compile:编译为 class
  • mvn package:打包(jar/war)
  • mvn clean:清理构建结果
  • mvn install:安装到本地仓库
打包类型

pom.xml 中 <packaging> 指定:jar(库/可执行程序)或 war(Web 服务)。


3-3 用 Servlet 与 Tomcat 开启服务

本节课理解原理即可,实际开发用 Spring Boot(内置 Tomcat)。

Tomcat
  • 轻量级 Web 服务器,用 Java 写的
  • 把 Servlet 代码交给 Tomcat,它帮你跑起来
  • 也可以跑 JSP、静态 HTML
Servlet
  • Java 编写 Web 服务的规范(Java 只提供规范,需额外安装 javax.servlet-api
  • Servlet 3.0+ 的写法已经和 Spring Boot 很像了
  • 第一次接触到用注解(@Annotation)表达功能,Spring Boot 中会大量使用

3-4 MySQL 安装

为什么需要数据库

之前的接口数据都是一次性的,重启服务数据就重置。数据库可以:

  1. 持久化保存增删改查的数据
  2. 作为独立服务运行,Java 接口只是调用它
常见数据库
  • MySQL:开源免费,功能齐全(本课程用)
  • Oracle:收费,功能更强
  • MongoDB:非关系型,Java 不推荐
  • SQL Server:Windows 生态,.NET 集成
安装注意
  1. 选择 Server only 安装
  2. 设置好 root 用户密码

3-5 数据库模型

基本结构
复制代码
MySQL 服务
├── 库1(一个项目一个库)
│   ├── 用户表
│   ├── 订单表
│   └── 页面表
└── 库2
    └── ...
表设计示例(OA 系统)
  • 用户表:id、username、password、nickname
  • 用户详细资料表:id、avatar、location、introduction(和用户表通过 id 关联)
  • 用户角色表:user_id、role_id
  • 角色权限表:role_id、power

设计原则:只把核心内容放主表,扩展信息拆到关联表,通过 id 关联。


3-6 建立表和库

库操作
sql 复制代码
CREATE DATABASE myoa;    -- 新建库
DROP DATABASE myoa;      -- 删除库
SHOW DATABASES;          -- 查看所有库
USE myoa;                -- 进入某个库
表操作
sql 复制代码
CREATE TABLE users (
    id INT,
    username VARCHAR(50),
    password VARCHAR(255)
);

SHOW TABLES;                    -- 查看所有表
DROP TABLE users;               -- 删除表
ALTER TABLE users ADD COLUMN email VARCHAR(100);  -- 加字段
ALTER TABLE users DROP COLUMN email;              -- 删字段
ALTER TABLE users MODIFY COLUMN username VARCHAR(100);  -- 改字段
MySQL 字段类型

整数类

类型 字节 范围
TINYINT 1 -128~127
SMALLINT 2 -32768~32767
MEDIUMINT 3 -838万~838万
INT 4 -21亿~21亿
BIGINT 8 -263~263-1

浮点类:FLOAT(4字节,7位精度)、DOUBLE(8字节,15位精度)

字符类

  • CHAR(n):固定长度,不足补空格
  • VARCHAR(n):可变长度,最常用
  • TEXT / MEDIUMTEXT / LONGTEXT:大文本

日期类

类型 格式 范围
DATE YYYY-MM-DD 1000-01-01 ~ 9999-12-31
TIME HH:MM:SS -838:59:59 ~ 838:59:59
DATETIME YYYY-MM-DD HH:MM:SS 1000~9999
TIMESTAMP YYYY-MM-DD HH:MM:SS 1970~2038(支持自动更新、时区转换)
YEAR YYYY 1901~2155

DATETIME vs TIMESTAMP

  • TIMESTAMP 范围小(2038 问题),但支持自动设为当前时间、时区转换
  • DATETIME 范围大,适合固定时间
  • 记录创建/更新时间推荐用 TIMESTAMP + 默认值

3-7 SQL 语句增删改查

插入(INSERT)
sql 复制代码
-- 简写(按字段顺序)
INSERT INTO users VALUES (1, 'admin', '123');

-- 显式指定字段(推荐,可缺省有默认值的字段)
INSERT INTO users(id, username, password) VALUES (1, 'admin', '123');
INSERT INTO users(username, password) VALUES ('admin', '123');
查询(SELECT)
sql 复制代码
SELECT * FROM users;                          -- 查所有字段
SELECT username, password FROM users;         -- 查指定字段
SELECT username FROM users WHERE id = 1;      -- 条件查询
更新(UPDATE)
sql 复制代码
UPDATE users SET username = 'admin2', password = '666';  -- 全表更新(危险!)
UPDATE users SET username = 'admin2' WHERE id = 1;       -- 条件更新
删除(DELETE)
sql 复制代码
DELETE FROM users;              -- 清空表(危险!)
DELETE FROM users WHERE id = 1; -- 条件删除

关键字按英语语法理解:SELECT 字段 FROM 表 WHERE 条件。


3-8 字段属性和约束

完整字段定义
sql 复制代码
CREATE TABLE users (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    ...
);
-- 字段名 类型 约束/属性 约束/属性...
约束
约束 作用
PRIMARY KEY 主键,唯一标识一行,查找最快,不能为空
FOREIGN KEY 外键,关联其他表的主键
UNIQUE 唯一约束,值不能重复
NOT NULL 非空约束
CHECK 检查约束,值必须符合条件
主键
  • 一般设为 id,主键查找性能最优
  • 支持复合主键(多个字段共同作为主键)
  • 特性:唯一、非空、可做其他表外键
外键
sql 复制代码
CREATE TABLE usersinfo (
    user_id INT NOT NULL,
    ...
    FOREIGN KEY(user_id) REFERENCES users(id)
        ON DELETE CASCADE    -- 主表删除时从表自动删除
        ON UPDATE CASCADE    -- 主表更新时从表自动更新
);

外键定义给表(不是字段),保证关联数据的一致性。

属性
  • DEFAULT:默认值
  • AUTO_INCREMENT:自增(一般用于主键,一个表只能有一个)

3-9 索引

索引的本质

索引相当于给表建立了目录,查找时先查目录定位,不用全表扫描。设为主键会自动建立主键索引。

常用索引类型
类型 创建方式
普通索引 CREATE INDEX 索引名 ON 表名(字段);
唯一索引 CREATE UNIQUE INDEX 索引名 ON 表名(字段);(设 UNIQUE 约束自动创建)
主键索引 设置主键自动创建,无法手动创建
sql 复制代码
CREATE INDEX idx_create_time ON users(create_time);
CREATE UNIQUE INDEX idx_username ON users(username);
索引的代价
  1. 额外存储空间:索引本身占物理空间(B-Tree 结构)
  2. 降低写入性能:插入/更新时需同步更新索引树

原则:按查询频率建立尽可能少的索引,不要所有字段都加索引。

其他索引

复合索引、全文索引、空间索引、前缀索引、函数索引(了解即可)。


3-10 条件与连表查询

条件判断

比较运算

sql 复制代码
WHERE name = '张三'        -- 相等
WHERE name != '张三'       -- 不等
WHERE age > 12             -- 大于/小于/大于等于/小于等于

模糊匹配(LIKE)

sql 复制代码
WHERE username LIKE '张%'      -- 以"张"开头
WHERE username LIKE '%勇'      -- 以"勇"结尾
WHERE username LIKE '%勇%'     -- 包含"勇"

多条件联合

sql 复制代码
WHERE age > 12 AND name LIKE '%张%'   -- 并且
WHERE age > 12 OR name LIKE '%张%'    -- 或者
WHERE age BETWEEN 10 AND 18           -- 区间(等价于 >=10 AND <=18)
WHERE name IN ('张三', '李四', '王五') -- 在列表中
WHERE age IS NULL                     -- 为空
WHERE age IS NOT NULL                 -- 不为空
连表查询

当数据分散在多个表中,需要联合查询。

sql 复制代码
SELECT u.id, u.username, c.company_name, c.location
FROM users AS u
INNER JOIN companys AS c ON u.company_id = c.company_id;

三种连接方式

类型 行为
INNER JOIN 只返回两表匹配上的数据
LEFT JOIN 以左表为主,左表全部返回,右表没匹配上的字段为 null
RIGHT JOIN 以右表为主,右表全部返回,左表没匹配上的字段为 null

3-11 数据库函数

不同数据库函数有差异,常用的记住即可。

聚合函数(最常用)
函数 作用
COUNT() 计算总条数
SUM() 求和
AVG() 平均值
MAX() 最大值
MIN() 最小值
sql 复制代码
SELECT AVG(salary) AS avg_salary FROM users;
SELECT COUNT(*) AS total FROM users;
字符串函数

CONCAT()(拼接)、LENGTH()(长度)、UPPER()/LOWER()(大小写)、SUBSTRING()(截取)

数字函数

RAND()(随机数)、CEILING()(向上取整)、FLOOR()(向下取整)、MOD()(取余)

时间函数 / 关键字
  • 推荐用标准关键字 而非 MySQL 独有函数:
    • CURRENT_DATE:当前日期
    • CURRENT_TIME:当前时间
    • CURRENT_TIMESTAMP:当前时间戳
  • 关键字可作为默认值(如创建时间字段默认 CURRENT_TIMESTAMP),函数不行
ID 生成函数
  • UUID():生成 UUID
  • MD5(CURRENT_TIMESTAMP):MD5 加密时间戳作为 ID
什么时候用 SQL 函数?
  • 大量数据聚合运算(求和、平均值):用 SQL,效率高于查出来再 Java 循环
  • 复杂逻辑/算法:用 Java 更合适
  • 字符串/数字/日期处理:优先 Java,避免 SQL 不兼容,提高可读性

3-12 分组、排序、分页

分组(GROUP BY)

按某个字段将相同值的数据合并为一组。

sql 复制代码
-- 统计每个公司的平均工资
SELECT company_id, AVG(salary) AS avg_salary
FROM users
GROUP BY company_id;
  • GROUP BY 后,SELECT 只能选分组字段 + 聚合函数结果
  • HAVING:对分组结果再筛选(WHERE 是分组前筛选,不能跟聚合函数)
sql 复制代码
-- 统计平均工资大于 400 的公司
SELECT company_id, AVG(salary) AS avg_salary
FROM users
GROUP BY company_id
HAVING avg_salary > 400;

WHERE vs HAVING

  • WHERE:分组前筛选原始数据
  • HAVING:分组后筛选聚合结果,可跟聚合函数
排序(ORDER BY)
sql 复制代码
SELECT * FROM users ORDER BY salary DESC;   -- 降序
SELECT * FROM users ORDER BY username ASC;  -- 升序(默认)
分页(LIMIT + OFFSET)
sql 复制代码
SELECT * FROM users LIMIT 10 OFFSET 20;  -- 跳过20条,取10条(第3页)
-- MySQL 简写:LIMIT 偏移, 数量
SELECT * FROM users LIMIT 20, 10;

3-13 数据库可视化软件

软件 特点
DBeaver 免费,支持多种数据库
MySQL Workbench MySQL 官方工具
Navicat 收费商业软件,好用,支持多种数据库

第 4 章 Spring Boot 实战

4-1 Spring Boot 创建项目

什么是 Spring Boot

类比前端开发:

  • Vue 提供简便开发方式和指令 → Spring Boot 提供简便开发方式和指令
  • npm 管理包 → Maven 管理依赖和构建
  • webpack/vite 构建 → Maven 打包
  • Node.js 运行时 → Servlet + Tomcat 运行服务

Spring Boot 项目核心构成:Servlet + Tomcat(开服务)+ Spring Boot 本身(开发指令)+ Maven(包管理和构建)

项目目录结构
复制代码
项目根目录
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com.study.study1     # 写代码的地方(groupId.artifactId)
│   │   │       └── Study1Application.java  # 启动类(等同 Vue 的 main.js)
│   │   └── resources
│   │       ├── static/               # 静态资源(可直接访问)
│   │       ├── templates/            # HTML 模板(接口开发一般不用)
│   │       └── application.yml       # 项目配置(端口、数据库等)
│   └── test/                         # 单元测试
├── target/                           # Maven 构建结果
└── pom.xml                           # Maven 配置和依赖
关键文件
  • application.yml / application.properties:Spring Boot 配置文件
  • pom.xml:Maven 依赖管理
  • XxxApplication.java:启动类,带 @SpringBootApplication 注解

4-2 定义第一个接口

核心注解
注解 作用
@RestController 声明这是控制器,方法返回值自动序列化为 JSON(= @Controller + @ResponseBody)
@GetMapping("/api1") 定义 GET 接口
@PostMapping("/api2") 定义 POST 接口
@RequestMapping("/user") 不区分请求方式,一般用于类上定义统一前缀
java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/hello")
    public String hello() {
        return "hello world";
    }

    @GetMapping("/json")
    public Map<String, Object> getJson() {
        Map<String, Object> result = new HashMap<>();
        result.put("name", "张三");
        return result;
    }
}
注解在 Spring Boot 中的意义

Spring Boot 坊间叫"面向注解编程"。注解类比 Vue 的指令(如 v-for),Spring Boot 扫描代码时看到注解就知道要做什么,帮你自动完成底层工作。


4-3 请求参数处理

五种参数场景及注解
场景 注解 示例
GET query 单个参数 @RequestParam @RequestParam("id") String id
GET query 整个对象 @ModelAttribute @ModelAttribute User user
POST body @RequestBody @RequestBody User user
请求头参数 @RequestHeader @RequestHeader("token") String token
路径参数 @PathVariable /api1/{id} + @PathVariable Long id
java 复制代码
@GetMapping("/api1")
public String api1(
    @RequestParam(name = "id", defaultValue = "23", required = false) String id,
    @RequestHeader("token") String token
) {
    return "id=" + id;
}

@PostMapping("/api2")
public String api2(@RequestBody User user) {
    return user.getUsername();
}

@RequestParam 进阶required 控制是否必传(默认 true),defaultValue 设置默认值。


4-4 项目层级规范

类比前端拆分组件层、工具层、请求层,Java 项目也分层:

复制代码
前端请求 → Controller层 → Service层 → Repository层 → 数据库
              ↓              ↓             ↓
            DTO入参       业务逻辑       数据库操作
              ↓              ↓             ↓
            VO出参 ← ─── Entity ← ─── 数据库数据
各层作用
作用
Controller 定义接口地址和入口方法,接收参数,返回结果
Service 业务逻辑处理
Repository 数据库增删改查操作
Model 数据结构定义
Model 层细分
类型 含义
DTO 前端发来的数据 / 层间传递的数据结构
Entity 数据库表对应的实体(从数据库取出的数据结构)
VO 返回给前端展示的数据结构

实战中 Model 不会分得这么细,很多时候数据结构一样就共用了。


4-5 设计一个接口的分层

目标:根据 id 查询用户信息

分层实现

  1. Controller 层UserController,定义 searchUser 接口
  2. Service 层UserService,写 searchUser 业务方法
  3. Repository 层UserRepository,数据库操作
  4. Model 层
    • 通用返回 VO 类(BaseVo:success、msg、data)
    • 用户 Entity(数据库结构)
    • 用户 VO(返回前端结构)
Lombok 库

写 Model 类时不用手写 getter/setter,用 @Data 注解自动生成。

xml 复制代码
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
</dependency>
java 复制代码
@Data
public class User {
    private String id;
    private String username;
    private String password;
}
// 自动生成 getter、setter、toString、equals、hashCode

4-6 Spring 依赖注入体系(IOC)

IOC(控制反转)思想

传统方式:代码中手动 new 对象。

IOC 方式:对象的创建和管理交给 Spring 容器,开发者只定义类,需要时让 Spring 自动注入。

类比:你不用自己 new Service,Spring 帮你创建好并赋值。

加入容器的注解(告诉 Spring 管理这个类)
注解 用途
@Controller / @RestController 控制器层
@Service Service 层
@Repository Repository 层
@Component 通用组件(工具类等)
@Configuration 配置类
@Bean 把方法返回值加入容器
从容器取出的注解
注解 作用
@Autowired 按类型自动从容器取出并注入
@Value 注入配置文件中的值
@Qualifier 按名称指定,消除歧义
java 复制代码
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;  // Spring 自动注入
}
@SpringBootApplication 拆解

由三个注解组成:

  • @Configuration:定义为配置类
  • @EnableAutoConfiguration:启动自动配置
  • @ComponentScan:定义扫描范围(可配置扫描/排除某些包)

4-7 操作数据库(上)

第一步:连接数据库

1. 安装 MySQL 驱动

xml 复制代码
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>9.2.0</version>
</dependency>

2. 配置数据库连接(application.yml)

yaml 复制代码
spring:
  datasource:
    url: "jdbc:mysql://localhost:3306/db_name?useSSL=false"
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name: "adminback"
server:
  port: 3000
主流数据库操作工具对比
工具 占有率 简便度 复杂查询
JPA 国外高 简单(方法名即查询) 不太好用
MyBatis 国内高 需写 SQL 最能应对复杂业务
JDBC 需写 SQL 中等
MyBatis-Plus 配合 MyBatis API 式查询,简单 不擅长复杂查询

本课程主要用 MyBatis

JPA 使用方式
  1. 定义 Entity,加 @Entity,注明主键和表名
  2. Repository 继承 JpaRepository<Entity, 主键类型>
  3. 直接写方法名即可查询,不用写 SQL

4-8 操作数据库(下)

MyBatis 使用
xml 复制代码
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.4</version>
</dependency>

Repository 接口中用 @Select@Insert@Update@Delete 注解写 SQL。

java 复制代码
@Mapper
public interface UserRepository {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User findById(String id);

    @Select("SELECT * FROM user WHERE username LIKE CONCAT('%', #{name}, '%')")
    List<User> findByUsername(String name);
}
MyBatis-Plus 使用
xml 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version>
</dependency>

继承 BaseMapper<T>,不用写 SQL 即可完成基础增删改查。


4-9 登录与注册接口

用户表设计
字段 类型 说明
id 字符串 主键,UID 生成
username 字符串 唯一索引
password 字符串 密码
create_time Date 创建时自动填写
update_time Date 创建时填写,更新时更新
注册接口逻辑
  1. 接收 username、password
  2. 根据 username 查询数据库,判断是否已存在
  3. 已存在 → 返回"用户名已经存在"
  4. 不存在 → 插入新用户 → 返回成功
登录接口逻辑
  1. 接收 username、password
  2. 根据 username 查询用户
  3. 不存在 → 返回"用户不存在"
  4. 存在 → 判断密码是否相等
  5. 密码正确 → 返回用户信息(id、username)
  6. 密码错误 → 返回失败
统一返回格式
json 复制代码
{
    "success": true,
    "msg": "成功",
    "data": { "id": "xxx", "username": "xxx" }
}

4-10 修改密码和注销

修改密码接口
  • 入参:id、oldPassword、newPassword
  • 逻辑:根据 id 查用户 → 校验老密码 → 匹配则更新密码,不匹配返回失败
注销(删除)接口
  • 入参:id
  • 逻辑:根据 id 查用户 → 存在则删除,不存在返回失败

通用入参格式可抽取为公共 DTO 复用(如 UserIdDTO)。


4-11 用 MyBatis-Plus 简化增删改查

对于简单的增删改查,用 MyBatis-Plus 比手写 SQL 更高效。

第一步:定义 Entity
java 复制代码
@TableName("user")          // 对应表名
public class User {
    @TableId(type = IdType.ASSIGN_UUID)  // 主键,自动生成
    private String id;

    @TableField(value = "username")
    private String username;

    @TableField(fill = FieldFill.INSERT)  // 插入时自动填充
    private Date createTime;
}
第二步:定义 Mapper
java 复制代码
public interface UserRepository extends BaseMapper<User> {
    // 不用写任何方法,基础 CRUD 已继承
}
常用方法
操作 方法
按主键查 selectById(id)
查单条 selectOne(queryWrapper)
查列表 selectList(queryWrapper)
插入 insert(entity)
按主键更新 updateById(entity)
按条件更新 update(entity, queryWrapper)
按主键删除 deleteById(id)
按条件删除 delete(queryWrapper)
QueryWrapper 条件构造
java 复制代码
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("username", "admin");     // 相等
wrapper.like("username", "张");       // 模糊查询
wrapper.gt("age", 18);               // 大于
wrapper.ge("age", 18);               // 大于等于
wrapper.lt("age", 60);               // 小于
wrapper.le("age", 60);               // 小于等于
wrapper.last("LIMIT 10");            // 追加 SQL

User user = userRepository.selectOne(wrapper);

MyBatis-Plus 不适合复杂查询(连表、多条件联合),复杂查询仍用 MyBatis 手写 SQL。


4-12 拦截器、过滤器和全局配置

类比前端的路由守卫、axios 请求/响应拦截器。

执行顺序
复制代码
请求 → Servlet容器 → 过滤器 → Spring → 拦截器(前置) → Controller → 拦截器(后置) → 响应
过滤器(Filter)

Servlet 提供,最早执行,只能获取基本的请求和响应。

java 复制代码
@Component
@Order(1)                          // 执行顺序,数字越小越优先
@WebFilter(urlPatterns = "/*")     // 拦截路径
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        // 前置处理
        chain.doFilter(req, res);  // 通过,继续执行
        // 后置处理
    }
}
拦截器(Interceptor)

Spring 提供,进入 Spring 后执行,可获取 Spring 上下文。

java 复制代码
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) {
        // 控制器执行前,返回 false 则中断
        return true;
    }

    @Override
    public void postHandle(...) {
        // 控制器执行后,视图渲染前
    }

    @Override
    public void afterCompletion(...) {
        // 整个请求完成后
    }
}

注册拦截器

java 复制代码
@Configuration
public class GlobalMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login", "/register");
    }
}
过滤器 vs 拦截器
对比 过滤器 拦截器
提供者 Servlet Spring
时机 最早 进入 Spring 后
能获取 基本请求响应 Spring 包装后的请求响应 + 上下文
MVC 全局配置(WebMvcConfigurer)

可配置:拦截器、跨域(CORS)、视图解析、静态资源、消息转换、参数解析等。


4-13 CORS 跨域设置

跨域解决方案
  1. 前端 dev 服务代理(仅开发环境)
  2. 后端设置 CORS(生产环境)
CORS 本质

在响应头中设置三个字段:

响应头 作用
Access-Control-Allow-Origin 允许的域名(* 为所有,生产环境设为前端地址)
Access-Control-Allow-Methods 允许的请求方式(GET、POST 等)
Access-Control-Allow-Headers 允许的请求头(如 token、自定义头)

实现方式:过滤器、拦截器、MVC 全局配置都可以做。


4-14 Token 体系(JWT)

Token 流程
  1. 用户登录成功 → 后端生成 token → 返回前端
  2. 前端存 token(localStorage)→ 后续请求放在请求头
  3. 后端拦截器解析 token → 验证合法性和有效期
JWT 组成

JWT 由三部分组成,用 . 分隔:

  1. Header(头部) :JSON,说明加密算法和类型 {"alg":"HS256","typ":"JWT"}
  2. Payload(荷载):JSON,携带用户信息(id、username、过期时间 exp)
  3. Signature(签名):用密钥对前两部分加密,防止伪造
使用的库(jjwt)
xml 复制代码
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-gson</artifactId>
    <version>0.11.2</version>
</dependency>
生成 Token
java 复制代码
String token = Jwts.builder()
    .setSubject(userId)                    // 设置荷载
    .setExpiration(new Date(System.currentTimeMillis() + 86400000))  // 过期时间
    .signWith(secretKey)                   // 签名
    .compact();
验证 Token(拦截器中)
java 复制代码
Claims claims = Jwts.parserBuilder()
    .setSigningKey(secretKey)
    .build()
    .parseClaimsJws(token)
    .getBody();
String userId = claims.getSubject();
Date exp = claims.getExpiration();

验证失败(格式错误、密钥错误、过期)→ 返回 401。


4-15 利用 Session 做验证码

什么是 Session

后端与前端的临时会话,针对当前用户临时存储一些信息(不存数据库)。

工作流程

  1. 用户请求验证码 → 后端生成验证码并存入 Session → 生成 sessionId → 通过 Set-Cookie 响应头返回
  2. 浏览器自动存 cookie(含 sessionId)
  3. 用户登录时带上 cookie → 后端解析 sessionId → 取出之前存的验证码比对
Spring Boot 中使用 Session
java 复制代码
// 存 session
@GetMapping("/captcha")
public String captcha(HttpSession session) {
    session.setAttribute("code", "1234");
    return "ok";
}

// 取 session
@PostMapping("/login")
public String login(HttpSession session) {
    String code = (String) session.getAttribute("code");
    return code;
}
图形验证码(Kaptcha 库)
xml 复制代码
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

流程:生成验证码文字 → 存 Session → 生成验证码图片 → 图片转 Base64 → 返回前端

Base64 操作
java 复制代码
// 编码:byte[] → Base64 字符串
String base64 = Base64.getEncoder().encodeToString(byteArr);

// 解码:Base64 字符串 → byte[]
byte[] origin = Base64.getDecoder().decode(base64Str);

Base64 只能编码 byte\[\],文件要先转为字节输出流,字符串用 getBytes()

相关类
  • ImageIO:Java 自带图片读写库,可读取为 BufferedImage
  • BufferedImage:可操作的图片对象(获取宽高、操作像素)
  • ByteArrayOutputStream:内存中的输出流(不写入硬盘),用于生成图片/Excel 后直接返回前端

4-16 用户列表查询(分页)

入参
json 复制代码
{
    "pageNum": 1,
    "pageSize": 10,
    "id": "xx",
    "username": "xxx",
    "startTime": "2025-01-12 00:00:00",
    "endTime": "2025-01-16 23:59:59"
}
出参
json 复制代码
{
    "success": true,
    "msg": "成功",
    "data": {
        "total": 100,
        "list": [],
        "pageNum": 1,
        "pageSize": 10,
        "hasMore": true
    }
}
分页逻辑
  1. 计算 offset = (pageNum - 1) * pageSize
  2. 查询用户列表(带条件 + LIMIT + OFFSET)
  3. 查询用户总数(COUNT)
  4. 组装 VO 返回
MyBatis XML 写复杂 SQL

当 SQL 复杂时,用单独的 XML 文件而非注解:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.repository.UserRepository">
    <select id="findUserList" resultType="com.example.entity.User">
        SELECT * FROM user
        <where>
            <if test="id != null">AND id LIKE CONCAT('%', #{id}, '%')</if>
            <if test="username != null">AND username LIKE CONCAT('%', #{username}, '%')</if>
        </where>
        LIMIT #{pageSize} OFFSET #{offset}
    </select>
</mapper>

核心标签<mapper>(根)、<select>(查询)、<where>(条件)、<if>(动态判断)。


4-17 页面模块

需求

后台管理系统的权限控制第一步:以树状结构查询所有页面(模块 → 顶层页面 → 子页面 → ...)。

表设计
  • 模块表(modules):module_id、module_name
  • 页面表(pages):page_id、page_name、page_path、parent_id(父页面 id,顶层为 null)、module_id
业务逻辑
  1. 查出所有模块
  2. 循环每个模块,查该模块下的顶层页面(parent_id IS NULL)
  3. 对每个顶层页面,递归查询子页面,塞入 children
  4. 无子页面则 children 为空数组
Model 设计
  • Entity:Modules、Pages(对应数据库表,用 MyBatis-Plus)
  • VO:ModulesVo(含 page 列表)、PageVo(含 children 列表,支持无限嵌套)

4-18 查询用户权限

需求

根据用户 id 查询该用户能访问哪些页面(涉及多表关联)。

新增 3 张表
  1. 角色表(roles):role_id、role_name(超级管理员、系统管理员、职员等)
  2. 用户角色表(user_role):user_id、role_id(用户属于哪个角色)
  3. 角色页面表(role_page):role_id、page_id(角色能访问哪些页面)
查询流程
  1. 用户表 + 用户角色表 + 角色表 + 角色页面表 → 四表联查,查出用户能访问的 page_id 列表
  2. 根据 page_id 列表查页面详情和模块信息
  3. GROUP_CONCAT 按模块分组聚合页面 id
  4. Java 中分割 id 字符串 → 查每个页面详情 → 组装成树状结构
联查 SQL
sql 复制代码
SELECT u.id, u.username, r.role_name, rp.page_id
FROM user AS u
LEFT JOIN user_role AS ur ON u.id = ur.user_id
LEFT JOIN roles AS r ON ur.role_id = r.role_id
LEFT JOIN role_page AS rp ON rp.role_id = ur.role_id
WHERE u.id = #{id}

4-19 ~ 4-22 综合实战模块

以下章节为代码实战,在前面的权限系统基础上继续扩展。

4-19 订单模块
  • 新增订单表(orders):order_id、order_name、status、user_id 等
  • 实现订单的增删改查接口
  • 订单状态用枚举管理
  • InsertOrderDTO 作为新增入参
4-20 用户余额
  • 新增用户余额表(user_money)
  • 下单时扣减余额(涉及事务控制)
  • 余额不足时返回业务异常
4-21 定时任务
  • 使用 Spring @Scheduled 注解实现定时任务
  • 典型场景:定时取消超时未支付订单
  • 启动类需加 @EnableScheduling 开启定时任务
java 复制代码
@Component
public class OrderScheduled {
    @Scheduled(cron = "0 0/30 * * * ?")  // 每30分钟执行
    public void cancelTimeoutOrders() {
        // 取消超时订单
    }
}
4-22 统计查询
  • 按天统计订单数量/金额
  • 使用 SQL 聚合函数 + GROUP BY 日期
  • CountDTO、CountByDayDTO 作为统计结果出参
  • 典型 SQL:SELECT DATE(create_time) AS day, COUNT(*) AS count FROM orders GROUP BY DATE(create_time)

附录:前端开发者学 Java 的关键映射

前端概念 Java 对应
JS 数组(可变) List(ArrayList)
JS 对象(键值对) Map(HashMap)
const final
模块(import/export) 包(package/import)
npm + package.json Maven + pom.xml
webpack/vite Maven 构建
Vue 指令(v-for 等) Spring Boot 注解
路由守卫 / axios 拦截 过滤器 / 拦截器
localStorage token 同样的 token 机制
Node.js 服务 Servlet + Tomcat
ORM(如 Prisma) JPA / MyBatis-Plus
环境变量 .env application.yml
依赖注入(Vue provide/inject) Spring IOC(@Autowired)

笔记整理完毕。覆盖 Java 基础 → IO/网络 → Maven/数据库 → Spring Boot 全栈开发

相关推荐
九硕智慧建筑一体化厂家1 小时前
楼宇自控|智慧建筑核心引擎!楼宇自控系统,赋能建筑高效低碳运维
运维·人工智能·笔记·智慧城市
mmsx1 小时前
基于 Android 的校园信息管理系统源码
android·java·okhttp
广州华水科技2 小时前
2026年单北斗GNSS变形监测系统推荐,破解水库安全监测难题
前端
Buke..2 小时前
【APP 逆向】哔哩哔哩 sign 参数逆向(上):Frida 反调试绕过与 unidbg 调用
java·开发语言·爬虫·python·安卓
竹枝溪2 小时前
Tomcat与Servlet全套小白教程
java·servlet·tomcat·cookie·filter·session·listener
GGMM7892 小时前
施耐德 VX5A1HC90110(VX5A1HD9011)ATV61/ATV71 90‑110KW 电源驱动板详解
笔记·变频器·变频器维修
独爱香菜2 小时前
Next.js 15 + Cloudflare Workers 实战:零中间件多语言 SEO 站点架构
前端
计算机魔术师2 小时前
DeepSeek、Qwen3比肩GPT-5,本地跑大模型的时代来了
前端
xcyxiner2 小时前
flutter wsl2安装记录(使用宿主机虚拟机)
前端·flutter