开启一个简单的HTTP服务
测试类
java
package apiStudy;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Test {
public static void main() {
InetSocketAddress socket = new InetSocketAddress(4399);
try {
HttpServer server = HttpServer.create(socket, 0);
server.createContext("/api1", new api1());
server.start();
System.out.println("服务创建成功");
} catch (IOException e) {
throw new RuntimeException("服务创建失败" + e);
}
}
}
api1实现类
java
package apiStudy;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
public class api1 implements HttpHandler {
@Override
public void handle(HttpExchange exchange) {
System.out.println("请求了API1" + exchange);
try {
String str = "hello word! 你好";
exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=UTF-8");
exchange.sendResponseHeaders(200, str.getBytes().length);
exchange.getResponseBody().write(str.getBytes());
exchange.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
maven 学习
前端中的 npm + vite = maven
package.json = pom.xml
不一定非要全局安装maven,后续使用框架创建项目的时候自带就行
java项目安装的依赖,会统一放在maven设置的安装目录下(为了避免重复安装),不会像前端那样,每个项目都有一个node_modules,都放在node_modules中,跟我们的pnpm很像
pom.xml
ini
groupId + artifactId => 才可以找到对应的依赖
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>myMaven</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 👇 这一行,就能自动下载 Jackson,json序列化的包,不用你去官网下载! -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- 类似前端的loadsh -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
</dependencies>
</project>

使用servlet和tomcat开启服务
-
Tomcat = HTTP 服务器 + 运行环境
-
Servlet = 处理请求的代码规范
-
Tomcat 管网络,Servlet 管业务
-
Servlet 跑在 Tomcat 里面
你可以把 Tomcat 理解成:
一个接收 HTTP 请求、并转发给你代码的 "中间件 / 门卫 / 快递站"
它不负责业务逻辑,只负责网络通信。
servlet
-
Servlet 是运行在服务器端的 Java 小程序
-
作用:接收请求 → 处理 → 返回响应
-
必须运行在 Tomcat 等容器中
-
生命周期:init() → service() → destroy()
-
是 Java Web 所有框架的底层基础(Spring MVC/Spring Boot)
数据库
为什么不能用字符串存储日期类
- 字符串不会校验,可以存非法日期,不存在的日期,非日期
- 按时间段查询,排序时,日期更高效
- 数据库会自动维护日期类,且日期计算繁琐
DATETIME(常用) vs TIMESTAMP 对比
| 对比项 | DATETIME | TIMESTAMP |
|---|---|---|
| 时间范围 | 1000-9999年 | 1970-2038年 |
| 存储空间 | 8 字节 | 4 字节 |
| 时区影响 | 不受影响 | 受时区影响 |
| 自动初始化 | MySQL 5.6.5+ 支持 | 完全支持 |
| 自动更新 | MySQL 5.6.5+ 支持 | 完全支持 |
| 索引性能 | 相同 | 相同 |
| 推荐场景 | 历史数据、未来时间 | 当前时间、跨时区应用 |
字段属性和约束
-
NOT NULL- 确保字段的值不能为空(NULL)。
- 示例 :
email VARCHAR(100) NOT NULL(插入时 email 必须提供值)
-
DEFAULT- 为字段设置默认值,当插入时未指定该字段时自动使用。
- 示例 :
status VARCHAR(20) DEFAULT 'active'
-
AUTO_INCREMENT- 整数字段自动递增(通常用于主键),新记录的值 = 当前最大值 + 1。
- 示例 :
id INT AUTO_INCREMENT
-
PRIMARY KEY- 主键,唯一标识一行记录,隐含
NOT NULL+UNIQUE。一个表只能有一个主键。 - 示例 :
user_id INT PRIMARY KEY或PRIMARY KEY (id, role_id)(复合主键)
- 主键,唯一标识一行记录,隐含
-
UNIQUE- 确保字段(或字段组合)的值在整个表中不重复,但允许多个 NULL(NULL 彼此不冲突)。
- 示例 :
username VARCHAR(50) UNIQUE
-
CHECK- 限制字段值必须满足指定的条件表达式(MySQL 8.0.16+ 才完全生效;低版本会解析但忽略)。
- 示例 :
age INT CHECK (age >= 18)
-
FOREIGN KEY- 外键约束,确保字段值必须来自另一表的主键或唯一键,用于维护引用完整性。
- 示例 :
dept_id INT, FOREIGN KEY (dept_id) REFERENCES departments(id)
-
COMMENT- 为字段添加说明文本,便于维护文档,不改变数据行为。
- 示例 :
price DECIMAL(10,2) COMMENT '商品单价,单位:元'
sql
CREATE TABLE employees (
emp_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '员工ID,自增主键',
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱,必填且唯一',
name VARCHAR(50) NOT NULL COMMENT '姓名,必填',
age INT CHECK (age >= 18 AND age <= 65) COMMENT '年龄,18-65岁',
status VARCHAR(10) DEFAULT 'active' COMMENT '状态,默认为active',
dept_id INT COMMENT '部门ID,关联departments表',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);
索引 INDEX
索引是数据库中用于快速查找数据的数据结构,类似于书的目录,如果有目录可以直接定位到想找到内容在哪一页,否则只能一页一页的找。没有索引,MySQL 必须从第一行开始逐行扫描全表(称为全表扫描),直到找到所需数据。
主键索引,在创建主键的时候,自动创建主键索引
唯一索引,给字段设置unique的时候,自动创建唯一索引
方法1:建表时直接创建索引(新项目直接创建)
sql
CREATE TABLE users (
-- 主键索引
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
-- 唯一索引(自动创建)
phone INT NOT NULL UNIQUE,
age INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 普通索引
INDEX idx_email (email)
);
方法2:表创建后单独添加索引(老项目优化索引)
sql
-- 先创建表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
age INT,
city VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 后添加普通索引
CREATE INDEX idx_email ON users(email);
| 作用 | 说明 | 类比 |
|---|---|---|
| 快速查询 | 大幅提升 SELECT 查询速度 | 书的目录直接翻到第100页 |
| 排序优化 | 避免 filesort,直接利用索引顺序 | 按拼音排序的通讯录 |
| 分组优化 | 加速 GROUP BY 操作 | 分类好的商品清单 |
| 唯一性保证 | UNIQUE 索引确保数据不重复 | 身份证号不能重复 |
| 外键支持 | 外键约束需要索引支持 | 两表关联的桥梁 |
| 索引类型 | 关键词 | 允许重复 | 允许NULL | 最大数量/表 | 底层结构 | 典型场景 |
|---|---|---|---|---|---|---|
| 主键索引 | PRIMARY KEY |
❌ | ❌ | 1个 | B+Tree(聚簇) | 唯一标识每行 |
| 唯一索引 | UNIQUE |
❌ | ✅(多个NULL) | 多个 | B+Tree | 邮箱、手机号 |
| 普通索引 | INDEX |
✅ | ✅ | 多个 | B+Tree | 常用查询列 |
| 复合索引 | INDEX(a,b,c) |
✅ | ✅ | 多个 | B+Tree | 多条件查询 |
| 全文索引 | FULLTEXT |
✅ | ❌ | 多个 | 倒排索引 | 文本搜索 |
| 空间索引 | SPATIAL |
✅ | ❌ | 多个 | R-Tree | 地理坐标 |
联表查询
1. INNER JOIN(内连接,最常用)
只返回两张表都匹配的数据。
sql
SELECT orders.order_id, users.name, orders.amount
FROM orders
INNER JOIN users ON orders.user_id = users.user_id;
2. LEFT JOIN(左连接)
返回左表全部数据,右表没有匹配的显示为 NULL。
(以主表为主,查询出所有主表的内容,如果on没有匹配到对应的从表数据,从表数据全部以null的形式展示)
sql
SELECT users.name, orders.amount
FROM users
LEFT JOIN orders ON users.user_id = orders.user_id;
3. RIGHT JOIN(右连接)
返回右表全部数据,左表没有匹配的显示为 NULL。
(以从表为主,查询出所有从表的内容,如果on没有匹配到对应的主表数据,主表数据全部以null的形式展示)
sql
SELECT users.name, orders.amount
FROM users
RIGHT JOIN orders ON users.user_id = orders.user_id;
| JOIN 类型 | 返回结果 | 使用频率 |
|---|---|---|
| INNER JOIN | 两表都匹配的记录 | ⭐⭐⭐⭐⭐ |
| LEFT JOIN | 左表全部 + 右表匹配的 | ⭐⭐⭐⭐ |
| RIGHT JOIN | 右表全部 + 左表匹配的 | ⭐ |
MySQL 聚合函数
| 函数 | 作用 | 返回类型 |
|---|---|---|
COUNT() |
统计行数 | 整数 |
SUM() |
计算总和 | 数值 |
AVG() |
计算平均值 | 数值 |
MAX() |
求最大值 | 与原类型相同 |
MIN() |
求最小值 | 与原类型相同 |
1. COUNT() - 计数
sql
-- 统计所有行(包括 NULL)
SELECT COUNT(*) FROM users; -- 返回 100
-- 统计某列非 NULL 的行数
SELECT COUNT(email) FROM users; -- 只统计有邮箱的
-- 去重统计
SELECT COUNT(DISTINCT city) FROM users; -- 有多少个不同的城市
-- 带条件统计
SELECT COUNT(*) FROM users WHERE age >= 18; -- 成年用户数
2. SUM() - 求和
sql
-- 计算总金额
SELECT SUM(amount) FROM orders; -- 所有订单金额总和
-- 带条件求和
SELECT SUM(amount) FROM orders WHERE status = 'paid'; -- 已支付订单总和
-- 条件求和(配合 IF)
SELECT SUM(IF(status = 'paid', amount, 0)) FROM orders; -- 只累加已支付的
3. AVG() - 平均值
sql
-- 计算平均年龄
SELECT AVG(age) FROM users;
-- 计算平均价格(保留2位小数)
SELECT ROUND(AVG(price), 2) FROM products;
-- 注意:AVG 自动忽略 NULL 值
SELECT AVG(score) FROM exam; -- 缺考的不计入平均
4. MAX() / MIN() - 最大/最小值
sql
-- 最高/最低价格
SELECT MAX(price), MIN(price) FROM products;
-- 最新/最早订单
SELECT MAX(create_time), MIN(create_time) FROM orders;
-- 结合条件
SELECT MAX(score) FROM exam WHERE subject = '数学';
配合 GROUP BY 使用(最重要)
sql
-- 统计每个城市的用户数量
SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city;
-- 统计每个分类的商品数量和平均价格
SELECT
category,
COUNT(*) AS product_count,
AVG(price) AS avg_price,
MAX(price) AS max_price
FROM products
GROUP BY category;
-- 统计每个用户的订单总金额
SELECT
user_id,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
GROUP BY user_id;
配合 HAVING 使用(过滤分组)
sql
-- 找出订单数大于5的用户
SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING order_count > 5;
-- 找出平均价格高于500的商品分类
SELECT category, AVG(price) AS avg_price
FROM products
GROUP BY category
HAVING avg_price > 500;
常见注意事项
| 注意点 | 说明 | 示例 |
|---|---|---|
| NULL 处理 | COUNT(列) 忽略 NULL,SUM/AVG 也忽略 | 缺考不计入平均分 |
| COUNT(*) vs COUNT(列) | * 统计所有行,(列) 统计非 NULL |
区别在于 NULL |
| 聚合函数不能用于 WHERE | WHERE 在聚合前执行 | WHERE SUM(amount) > 100 ❌ |
| 使用 HAVING | 聚合后的条件用 HAVING | HAVING COUNT(*) > 5 ✅ |
最常用的数值函数(7个)
| 函数 | 作用 | 示例 | 结果 |
|---|---|---|---|
ROUND() |
四舍五入 | ROUND(3.14159, 2) |
3.14 |
CEIL() |
向上取整 | CEIL(3.14) |
4 |
FLOOR() |
向下取整 | FLOOR(3.14) |
3 |
ABS() |
绝对值 | ABS(-5) |
5 |
MOD() |
取余数 | MOD(10, 3) |
1 |
RAND() |
随机数 | RAND() |
0.123456 |
ROUND() |
取整 | ROUND(3.5) |
4 |
实战案例
1. 创建时间和更新时间
sql
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
-- 创建时间:插入时自动设为当前时间,之后不再改变
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 更新时间:插入时设为当前时间,每次更新时自动更新
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
分组,排序,分页
怎么理解group by
group by就是根据后面的字段进行分组,整理举一个简单的例子:
SELECT dep_id,SUM(salary) FROM pyuser GROUP BY dep_id;这句
sql的意思就是,先根据dep_id进行分组,将相同dep_id的数据放在一个集合里面,再根据这个集合里的数据求和(求最大,最小,平均值)同一组数据中的salary字段,从后往前理解就一目了然
再举个例子,全世界所有人的信息都在一个表里,现在将表里的数据,根据国家分组,并求每个国家的人的平均工资
SELECT country,AVG(salary) FROM world_user GROUP BY country;
基本语法
sql
SELECT
列1,
聚合函数(列2)
FROM 表名
[WHERE 条件]
GROUP BY 列1
[HAVING 分组后条件]
[ORDER BY 列1 ASC/DESC];
注意事项
- 将表中值相同的行归入同一组
- 每个分组在结果中只输出一行
- 出现在
SELECT中的非聚合列必须出现在GROUP BY中(严格模式下会报错) having不是必须跟在group by只是通常一起使用,没有group by的时候,having将整个表视为一个分组
having 和 where 的区别
| 对比维度 | WHERE | HAVING |
|---|---|---|
| 执行时机 | 分组之前过滤行 | 分组之后过滤组 |
| 能否使用聚合函数 | ❌ 不能(会报错) | ✅ 能(如 SUM, COUNT, AVG 等) |
| 能否使用列别名 | ❌ 不能(MySQL 特定情况除外) | ✅ 能 |
| 作用对象 | 原始表的行 | 分组后的结果集 |
| 能否单独使用 | ✅ 可以 | ✅ 可以(整个表视为一组) |
| 与索引关系 | 可以命中索引 | 通常无法命中索引(操作结果集) |
分页
基本语法
所谓的偏移量,就是跳过前面 x 条数据,从 x 条数据后才开始查询
sql
SELECT 列1, 列2, ...
FROM 表名
[WHERE 条件]
[ORDER BY 排序字段]
LIMIT 每页行数 OFFSET 偏移量;
-- 简化写法(常用)
LIMIT 偏移量, 每页行数;
java
int pageNum = 3; // 当前页码
int pageSize = 10; // 每页大小
int offset = (pageNum - 1) * pageSize; // 偏移量 = 20
sql
SELECT *
FROM 表名
[WHERE 条件]
LIMIT pageSize OFFSET offset;
-- 简化写法(常用)
LIMIT offset, pageSize;