0%

MySQL 约束详解:保障数据完整性的六大机制

约束(Constraint)是 MySQL 中用于限制表中数据的规则,通过强制数据满足特定条件,确保数据库的完整性、一致性和准确性。MySQL 支持六大类约束,虽然部分约束在实现上有局限,但仍是设计可靠数据表的核心工具。

六大约束类型及作用

约束类型 作用描述 MySQL 支持情况
NOT NULL 限制字段值不能为 NULL(必须填写)。 完全支持
DEFAULT 为字段设置默认值,当插入数据时未指定该字段则使用默认值。 完全支持
PRIMARY KEY 主键约束,唯一标识表中的每行记录,要求字段值唯一且非空 完全支持(单字段或复合主键)
UNIQUE 唯一约束,保证字段值在表中唯一,但允许为 NULL(NULL 可重复)。 完全支持
CHECK 检查约束,限制字段值必须满足指定条件(如 age > 0)。 MySQL 8.0.16+ 支持,低版本仅语法兼容但不生效
FOREIGN KEY 外键约束,限制两个表的关系,确保从表字段值必须来自主表的关联字段。 支持,但需存储引擎为 InnoDB(MyISAM 不支持)

约束的使用方法

1. NOT NULL(非空约束)

确保字段必须有值,禁止插入 NULL

1
2
3
4
5
6
7
8
CREATE TABLE student (
id INT,
name VARCHAR(50) NOT NULL, -- 姓名不能为空
age INT NOT NULL DEFAULT 0 -- 年龄不能为空,默认值为 0
);

-- 插入数据时未指定 name 会报错
INSERT INTO student (id) VALUES (1); -- 错误:Column 'name' cannot be null

2. DEFAULT(默认约束)

为字段设置默认值,简化插入操作(未指定字段时自动填充)。

1
2
3
4
5
6
7
8
9
10
CREATE TABLE employee (
id INT,
name VARCHAR(50) NOT NULL,
department VARCHAR(30) DEFAULT '未知部门', -- 默认部门
hire_date DATE DEFAULT CURRENT_DATE() -- 默认值为当前日期
);

-- 插入时未指定 department 和 hire_date,将使用默认值
INSERT INTO employee (id, name) VALUES (1, '张三');
-- 结果:department 为 '未知部门',hire_date 为插入当天日期

3. PRIMARY KEY(主键约束)

主键是表的 “唯一标识符”,具有以下特性:

阅读全文 »

Spring MVC 模型数据处理详解:4 种核心方式与错误处理

在 Spring MVC 开发中,将业务数据传递到视图(如 JSP、Thymeleaf)是核心需求之一。Spring MVC 提供了 ModelAndView、Map/Model、@SessionAttributes、@ModelAttribute 四种灵活的模型数据处理方式,覆盖从 “单次请求数据传递” 到 “跨请求数据共享” 的全场景。从 “数据传递原理→使用场景→实战示例” 三个维度,彻底讲透模型数据的处理逻辑。

核心概念:模型数据的本质

Spring MVC 中的 “模型数据” 本质是 键值对(Key-Value),存储在 ModelModelMap 中,最终会通过 HttpServletRequest.setAttribute() 绑定到请求域(Request Scope),视图技术(如 JSP 的 EL 表达式 ${key})可直接访问这些数据。

  • 作用域分类:
    1. 请求域(Request Scope):数据仅在当前请求有效(默认,如 ModelAndView、Map 传递的数据);
    2. 会话域(Session Scope):数据在当前用户会话中有效(需通过 @SessionAttributes 显式设置)

方式 1:ModelAndView —— 视图与数据的统一载体

ModelAndView 是 Spring MVC 早期最常用的模型数据处理方式,同时封装 “视图信息” 和 “模型数据”,适合需要明确指定视图且传递数据的场景。

1. 核心原理与源码解析

ModelAndView 内部维护两个核心变量,分别对应 “视图” 和 “数据”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class ModelAndView {
// 视图:支持两种类型——1. 逻辑视图名(String,如 "hello");2. 具体 View 对象(如 InternalResourceView)
private Object view;
// 模型数据:本质是 ModelMap(继承自 LinkedHashMap,有序存储键值对)
private ModelMap model;

// ------------------------------ 视图相关方法 ------------------------------
// 设置逻辑视图名(最常用)
public void setViewName(String viewName) {
this.view = viewName;
}
// 获取逻辑视图名
public String getViewName() {
return this.view instanceof String ? (String) this.view : null;
}

// ------------------------------ 模型数据相关方法 ------------------------------
// 获取模型(懒加载,第一次调用时初始化 ModelMap)
public ModelMap getModelMap() {
if (this.model == null) {
this.model = new ModelMap();
}
return this.model;
}
// 添加模型数据(键值对)
public ModelAndView addObject(String attributeName, Object attributeValue) {
this.getModelMap().addAttribute(attributeName, attributeValue);
return this; // 链式调用,支持连续 addObject
}
// 重载方法:省略键名,Spring 自动生成键名(如 User 对象→键名 "user")
public ModelAndView addObject(Object attributeValue) {
this.getModelMap().addAttribute(attributeValue);
return this;
}
}
阅读全文 »

Spring 事务深度解析:从 ACID 到分布式事务

Spring 事务是企业级应用中确保数据一致性的核心机制,基于 AOP(面向切面编程) 实现,将事务管理逻辑与业务逻辑解耦。它不仅支持本地事务(单数据库),还通过 JTA 等规范支持分布式事务(跨数据库 / 服务)。从 “事务基础→核心属性→实现原理→实战配置→分布式事务” 五个维度,系统拆解 Spring 事务的工作机制与最佳实践。

事务基础:理解 ACID 与并发问题

事务(Transaction)是数据库操作的最小单元,必须满足 ACID 特性,同时需解决并发场景下的脏读、不可重复读、幻读问题。

1. 事务的 ACID 特性

ACID 是事务的四大核心特性,确保数据操作的一致性与可靠性:

特性 英文全称 核心含义 示例场景(转账:A 转 100 给 B)
原子性(Atomicity) Atomicity 事务中的操作 “要么全成功,要么全失败”,无中间状态 A 扣款 100 成功,但 B 到账 100 失败 → 事务回滚,A 余额恢复
一致性(Consistency) Consistency 事务执行前后,业务状态需符合预期(数据完整性约束不被破坏) 转账前 A+B 余额 = 2000 → 转账后仍为 2000(不会出现 A 扣了但 B 没到账)
隔离性(Isolation) Isolation 多个事务并发执行时,彼此隔离,互不干扰(避免并发问题) 事务 1 读取 A 余额时,事务 2 修改 A 余额 → 事务 1 读取的是隔离后的数据
持久性(Durability) Durability 事务提交后,结果永久保存到数据库(即使数据库崩溃,数据也不丢失) 转账成功后,即使数据库重启,A 扣 100、B 加 100 的结果仍存在

2. 并发事务的三大问题

当多个事务同时操作同一批数据时,若隔离性不足,会引发三类典型问题:

并发问题 具体表现 示例(事务 T1 读取数据,事务 T2 修改数据)
脏读(Dirty Read) T1 读取 T2 未提交的修改 → T2 回滚后,T1 读取的是 “无效数据” T2 给 A 加 100(未提交)→ T1 读 A 余额为 1100 → T2 回滚 → T1 基于 1100 做业务,结果错误
不可重复读(Non-Repeatable Read) T1 多次读取同一数据 → T2 提交修改后,T1 两次读取结果不一致 T1 第一次读 A 余额 1000 → T2 给 A 加 100(提交)→ T1 第二次读 A 余额 1100,结果不同
幻读(Phantom Read) T1 按条件查询数据 → T2 插入 / 删除符合条件的行 → T1 再次查询,行数变化 T1 查 “余额> 500 的用户”(2 人)→ T2 新增 1 个余额 600 的用户(提交)→ T1 再次查询,结果为 3 人

3. 事务隔离级别:解决并发问题

数据库通过 隔离级别 控制并发事务的干扰程度,Spring 支持数据库的所有隔离级别,并新增 DEFAULT(继承数据库默认级别)。不同级别对并发问题的解决能力不同,性能也不同:

隔离级别 解决脏读 解决不可重复读 解决幻读 性能 数据库默认(常见)
READ_UNCOMMITTED 最高
READ_COMMITTED 较高 Oracle、SQL Server
REPEATABLE_READ 中等 MySQL(InnoDB)
SERIALIZABLE 最低
DEFAULT(Spring 新增) 继承数据库默认级别 继承数据库默认级别 继承数据库默认级别 取决于数据库 -
阅读全文 »

Spring AOP 深度解析:从原理到实战

Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架的核心特性之一,旨在解决横切关注点(如日志、事务、权限)在代码中分散、冗余的问题。它通过动态代理技术,将横切逻辑与业务逻辑解耦,实现 “一次定义,多处复用”。从 “核心价值→概念体系→动态代理→实战配置→避坑指南” 五个维度,彻底拆解 Spring AOP 的底层机制与使用方法。

为什么需要 Spring AOP?—— 解决横切关注点的痛点

在传统开发中,日志、事务、异常处理等横切关注点(跨越多个模块的通用逻辑)会嵌入到业务代码中,导致三大核心问题:

问题 具体表现 示例场景
代码冗余 相同的日志 / 事务逻辑重复出现在数十个方法中,修改时需逐一修改 每个 Service 方法都要写 log.info("方法开始执行")
代码混乱 业务逻辑与横切逻辑混杂,难以区分核心功能与辅助功能 一个 createOrder() 方法中,订单业务、日志、事务代码交织
维护困难 横切逻辑变更时,需修改所有相关业务类,风险高、效率低 日志格式从 “INFO” 改为 “DEBUG”,需修改所有日志语句

AOP 的解决方案:将横切逻辑横向抽取为 “切面(Aspect)”,通过动态代理技术,在不修改业务代码的前提下,将切面 “织入” 到目标方法的指定位置(如方法执行前、执行后),实现与业务逻辑的解耦。

Spring AOP 核心概念:理解 AOP 的 “语言体系”

AOP 有一套标准化的概念体系,是理解和使用 AOP 的基础。用 “日志切面” 案例对应解释每个概念:

概念 官方定义 日志切面案例对应
横切关注点 从业务逻辑中抽取的、跨越多个模块的通用逻辑(如日志、事务) 所有方法的 “执行日志记录” 逻辑
目标对象(Target) 被切面织入的对象(即业务逻辑对象) OrderServiceUserService 等业务类实例
代理对象(Proxy) 目标对象被织入切面后,Spring 生成的代理对象(包含目标方法 + 增强逻辑) OrderService 的代理对象,调用 createOrder() 时会先记录日志
连接点(Joinpoint) 程序执行过程中可插入切面的 “点”(Spring AOP 中仅支持方法执行 OrderService.createOrder()UserService.getUser() 等方法
切点(Pointcut) 对连接点的 “筛选规则”,定义哪些方法会被织入切面(即 “要增强哪些方法”) “所有 Service 类的 public 方法”
通知(Advice) 切面的具体增强逻辑(即 “要做什么”),按执行时机分为 5 种类型 “方法执行前记录日志”“方法异常时记录错误日志”
织入(Weaving) 将通知(Advice)应用到目标对象(Target)的过程(Spring 中是运行期织入 动态代理生成时,将日志逻辑嵌入 createOrder() 方法的执行流程
切面(Aspect) 切点(Pointcut)+ 通知(Advice)的组合(即 “对哪些方法,做什么增强”) “对所有 Service 的 public 方法,执行前记录日志”
通知器(Advisor) 简化版切面,仅包含 “一个切点 + 一个通知”(Spring 早期接口式 AOP 常用) “对 createOrder() 方法,执行后记录事务状态”

Spring AOP 的动态代理:AOP 的 “实现引擎”

Spring AOP 不修改目标类的字节码,而是在运行期通过动态代理生成代理对象,将切面逻辑织入。核心支持两种代理方式,由 DefaultAopProxyFactory 决定选择逻辑。

1. 代理方式的选择逻辑(源码解析)

Spring 通过 DefaultAopProxyFactory.createAopProxy() 方法选择代理方式:

阅读全文 »