0%

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() 方法选择代理方式:

阅读全文 »

MySQL 数据删除:DELETE 与 TRUNCATE 的区别及应用场景

在 MySQL 中,删除表数据主要有 DELETETRUNCATE 两种方式,二者虽都能清除数据,但在功能、性能和底层实现上有显著差异。本文详细解析两者的区别,帮助你根据场景选择合适的删除方式。

基本语法

1. DELETE 语句

用于删除表中满足条件的部分或全部数据,支持 WHERE 子句精确筛选:

1
2
3
4
5
-- 删除满足条件的行
DELETE FROM 表名 WHERE 条件;

-- 删除全表数据(不推荐,效率低)
DELETE FROM 表名;

2. TRUNCATE 语句

用于删除表中所有数据,无法指定条件,等价于 “清空表”:

阅读全文 »

Spring MVC 数据绑定详解:从流程到自定义类型转换

Spring MVC 的数据绑定机制是连接 HTTP 请求与业务方法参数的核心桥梁,它自动将请求中的参数(如 URL 路径、表单、请求头)转换为业务方法所需的 Java 类型(如 IntegerUser 自定义对象),并支持数据校验。从 “数据绑定全流程→核心组件 ConversionService→自定义类型转换器” 三个维度,彻底讲透数据绑定的实现原理与实践。

数据绑定核心流程:从请求参数到方法入参

Spring MVC 数据绑定的本质是 “将 Servlet 请求信息转换为目标方法入参对象”,整个流程由 WebDataBinder 主导,配合 ConversionService(类型转换)和 Validator(数据校验)完成,共 4 个关键步骤:

步骤 1:创建 DataBinder 实例

  • 触发点:DispatcherServlet 调用 HandlerAdapter(如 RequestMappingHandlerAdapter)后,HandlerAdapter 会获取 WebDataBinderFactory 实例;
  • 核心操作WebDataBinderFactory 根据当前请求 ServletRequest 和目标方法入参类型,创建 DataBinder 实例(默认实现 ServletRequestDataBinder);
  • DataBinder 作用:作为数据绑定的 “工作器”,负责后续的参数提取、类型转换、数据校验。

步骤 2:DataBinder 提取请求参数并转换类型

  • 参数提取DataBinderServletRequest 中提取请求参数(如 request.getParameter("username")、URL 路径参数、JSON 请求体);
  • 类型转换DataBinder 调用 ConversionService,将提取的字符串参数转换为目标入参类型(如将请求中的 "2024" 转换为 Integer,将 "1:zhangsan" 转换为 User 对象);
  • 参数填充:转换后的参数值被填充到目标入参对象中(如 User 对象的 idname 字段)。

步骤 3:Validator 进行数据合法性校验

  • 校验触发:若目标入参对象添加了校验注解(如 @NotNull@Size),DataBinder 会调用 Spring 上下文的 Validator 组件(默认 LocalValidatorFactoryBean)进行校验;
  • 校验结果:校验结果被封装到 BindingResult 对象中,包含 “校验通过 / 失败” 状态和错误信息(如 “用户名长度不能小于 3”)。

步骤 4:绑定结果传递给目标方法

  • 参数注入:Spring MVC 将 “转换后的入参对象” 和 “BindingResult 校验结果” 注入到目标业务方法的参数中;
  • 开发者处理:开发者可通过 BindingResult 判断校验结果,若有错误则返回错误页面或提示,若无错误则执行业务逻辑。
流程总结(简化)
阅读全文 »

MySQL LIKE 模糊查询与通配符使用详解

在 MySQL 中,LIKE 运算符结合通配符可实现灵活的模糊查询,适用于根据部分字符匹配查找数据的场景(如搜索用户名、关键词等)。本文详细讲解 LIKE 的用法、通配符规则及注意事项。

基本语法

1
2
3
SELECT <字段列表>
FROM <表名>
WHERE <字段名> LIKE <匹配模式>;
  • LIKE 用于判断字段值是否与指定的 “匹配模式” 相符。
  • 匹配模式需结合通配符使用,否则等价于 = 运算符(如 name LIKE '张三' 等价于 name = '张三')。

通配符类型及用法

MySQL 支持两种通配符,用于构建匹配模式:

1. %:匹配任意多个字符(包括 0 个)

  • 表示 “任意长度的任意字符”(字母、数字、符号等均可)。
  • 可放在模式的任意位置(开头、中间、结尾)。
示例:
1
2
3
4
5
6
7
8
9
10
11
-- 1. 字段值包含 'a'(无论位置)
SELECT * FROM user WHERE name LIKE '%a%';
-- 匹配:'a'、'ab'、'ba'、'abc'、'xay' 等

-- 2. 字段值以 'a' 开头
SELECT * FROM user WHERE name LIKE 'a%';
-- 匹配:'a'、'ab'、'abc' 等,不匹配 'ba'

-- 3. 字段值以 'a' 结尾
SELECT * FROM user WHERE name LIKE '%a';
-- 匹配:'a'、'ba'、'cba' 等,不匹配 'ab'

2. _:匹配任意单个字符

  • 表示 “恰好一个任意字符”(必须有且仅有一个)。
示例:
阅读全文 »