Java+SpringBoot 核心知识点整合(一)
从业务代码看 SpringBoot 的分层设计
这篇文章不打算把每个注解的用法都列一遍——那些查文档就行。我想聊的是,当你真正坐在工位上面对一个 SpringBoot 项目时,代码为什么这么组织,以及每一层的边界到底在哪里。
HTTP 方法:不只是增删改查
| 方法 | 语义 | 实际使用中的注意点 |
|---|---|---|
| GET | 获取资源 | 参数走 URL,敏感信息绝不能放这里 |
| POST | 创建资源 | 参数放请求体,不要用 POST 做查询 |
| PUT | 全量替换 | 传全部字段,缺了就会被覆盖为 null |
| PATCH | 部分更新 | 只传要改的字段,但很多团队直接用 POST 代替 |
| DELETE | 删除资源 | 一般做软删除,别真把数据物理删了 |
一个实际经验:大多数中小项目,GET/POST 就够覆盖 90% 的场景。PUT 和 PATCH 的区分在理论上是合理的,但在协作成本高的团队里,强行推广 RESTful 规范往往是自找麻烦。看清团队现状再做技术选型。
分层架构的本质:谁该干什么
标准的分层结构:
1 | Controller → Service 接口 → ServiceImpl → Mapper → 数据库 |
这个分层不是教条。每一层的存在都有具体的工程目的:
Controller 层只管两件事:接收参数、返回结果。这里的”结果”是给前端的,不是给业务层的。如果你在 Controller 里直接调 Mapper 写业务逻辑,当下可能快了五分钟,但三个月后同事会在心里骂你。
Service 层是所有业务逻辑唯一该待的地方。这里有一个容易被忽略的点:Service 接口不是为了”规范”而存在的。它的真正价值在于——当你的业务逻辑有多种实现时(比如普通用户和 VIP 用户计算折扣的规则不同),Controller 不需要知道具体调了哪个实现类。这就是”面向接口编程”的真正用途,不是为了面试,是为了改代码时少改几个地方。
Mapper 层只做数据库操作,不要让它承担业务判断。比如:密码加密不是 Mapper 的事,把它放在 ServiceImpl 里。这条边界一旦模糊,排查 Bug 的难度会成倍上升。
MyBatis 的几个基本事实
@Mapper 注解告诉 MyBatis 为这个接口生成代理实现类。你用 @Select、@Insert 写的 SQL 是手写的,语法错了框架不会给你纠正——这点和 JPA 完全不同。
手写 SQL 是约束也是自由。约束在于你写错了就报错;自由在于复杂查询不会受限于框架的自动 SQL 生成。
MyBatis 能做的:单表 CRUD,简单联表查询。MyBatis 不该做的:业务流程控制、数据格式转换、加密解密——这些是 Service 层的工作。
注解不等于魔法
@RestController、@Service、@Repository、@Component 这四个注解在 Spring 容器层面的行为几乎一样——都标记一个类为 Bean。它们的区别纯粹是语义上的:@Repository 额外提供了异常转换,其他三个在容器行为上没有差别。
写代码的时候选对语义注解就行,不用纠结它们的底层差异。真正需要理解的是 @Bean:它跟上面四个不同,是作用于方法上的,用于把第三方的类(比如 DataSource、RestTemplate)交给你自己实例化后再放进容器。
DTO:项目大了才理解的设计
刚开始写练手项目的时候,很多人会用 Entity 直接跟前端交互——能跑,也没啥大问题。
但一旦你的项目有”用户”这个概念,问题就来了。Entity 的 password 字段几乎不可能直接返回给前端。这时候 DTO(Data Transfer Object)的价值就体现出来了:它是一个过滤器,隔离了数据库内部结构和外部展示。
不过话说回来,如果你现在做的就是一个只有两张表的练手项目,DTO 带来的收益远小于它的心理负担。知道有这个东西就行,等项目变大了再用。
@Override 的真正作用
很多人以为 @Override 只是个标记。实际上它提供了一个编译期检查:如果你方法签名写错了(比如参数类型不匹配),IDE 直接标红。没有这个注解,你的方法就是一个普通的新方法,编译不会报错,但运行时也不会被框架调用——这种 Bug 很难排查。
在 ServiceImpl 中实现接口方法时,始终加上 @Override。这不是风格问题,是防御性编码。
