0. 引言
因为我的个人习惯,我会从: 是什么(What),为什么(Why),怎么用(How)三个方面来分析这些技术和知识点,便于我加深理解和记忆。
注解名称 | 注解作用 |
---|---|
@SpringBootApplication |
Spring Boot 的核心组合注解,包含配置、自动装配和组件扫描。 |
@Autowired |
Spring 的自动依赖注入注解。 |
@Component |
通用组件。 |
@Repository |
数据库层。 |
@Service |
业务逻辑层。 |
@Controller |
接口层(Web 层)。 |
@RestController |
把一个类标记为 控制器(Controller) ,并且该类下所有方法默认都以 JSON / XML 等格式返回数据,而不是返回视图(JSP/Thymeleaf 等)。 |
@Scope |
Spring 提供的一个 Bean 作用域(Scope)声明注解。 (单例/多例) |
@Component |
用来声明配置类,里面的方法用 @Bean 定义 Bean。 |
@PathVariable |
用于 URL 路径变量(资源定位)。 |
@RequestParam |
用于 URL 查询参数 或表单参数(条件、筛选)。 |
@RequestBody |
用来把请求体中的 JSON 数据转成 Java 对象,特别适合前后端分离的场景。 |
@Value |
读取单个配置属性。 |
@ConfigurationProperties |
将配置文件中的属性绑定到一个 Java Bean 上,支持嵌套对象和集合。 |
@PropertySource |
指定读取某个 properties 文件(不常用,多用于老项目)。 |
@Null |
被注释的元素必须为 null |
@NotNull |
不能为 null |
@NotEmpty |
字符串或集合不能为空 |
@NotBlank |
字符串不能为空且必须有非空白字符 |
@Email |
必须是合法邮箱 |
@Size(min=, max=) |
字符串或集合长度/大小限制 |
@Digits (integer, fraction) |
被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Pattern(regex=,flag=) |
必须符合指定正则 |
@Min/@Max |
数值最小/最大值 |
@DecimalMin/@DecimalMax |
小数最小/最大值 |
@Past/@Future |
日期必须为过去/将来 |
@AssertTrue/@AssertFalse |
布尔值必须为 true/false |
@Transactional |
事务 |
- 各种主键生成策略对照表
策略名 | 对应类 | 说明 | 适用场景 |
---|---|---|---|
uuid2 |
UUIDGenerator |
使用 Java UUID v2 生成 | 推荐,分布式环境 |
guid |
GUIDGenerator |
GUID,全局唯一标识 | Windows/SQL Server |
uuid / uuid.hex |
UUIDHexGenerator |
旧版 UUID | 已废弃 |
assigned |
Assigned |
主键由应用代码手动赋值 | 业务系统已有主键 |
identity |
IdentityGenerator |
数据库自增主键 | MySQL 常用 |
sequence |
SequenceStyleGenerator |
数据库序列 | Oracle/PostgreSQL |
seqhilo |
SequenceHiLoGenerator |
hi/lo 算法生成主键 | 大并发优化 |
increment |
IncrementGenerator |
自增计数器 | 单机测试用,不推荐生产 |
foreign |
ForeignGenerator |
使用外键作为主键 | 一对一关系 |
sequence-identity |
SequenceIdentityGenerator |
序列 + 自增 | 特殊数据库 |
enhanced-sequence |
SequenceStyleGenerator |
增强版序列 | 替代旧 sequence |
enhanced-table |
TableGenerator |
通过表维护主键 | 无序列支持的数据库 |
- JPA 常用注解速查表
分类 | 注解 | 说明 |
---|---|---|
表相关 | @Entity |
标记一个类为 JPA 实体类,映射数据库表 |
@Table(name = "xxx") |
指定实体类对应的表名(默认类名) | |
@Column(name = "xxx", nullable = false, length = 100) |
指定字段名、长度、是否可空等约束 | |
@Id |
标识 主键字段 | |
主键策略 | @GeneratedValue(strategy = GenerationType.IDENTITY) |
自增主键(MySQL 常用) |
@GeneratedValue(strategy = GenerationType.SEQUENCE) |
使用数据库序列生成主键(Oracle 常用) | |
@GeneratedValue(strategy = GenerationType.UUID) |
使用 UUID 作为主键(Spring Boot 3.0+ 支持) | |
字段控制 | @Transient |
该字段 不持久化(不会映射到表) |
@Lob |
存储大字段(如 TEXT 、BLOB ) |
|
@Enumerated(EnumType.STRING) |
枚举保存为 字符串 | |
@Enumerated(EnumType.ORDINAL) |
枚举保存为 序号 | |
审计功能 | @EnableJpaAuditing |
开启 JPA 审计功能(放在配置类上) |
@CreatedDate |
自动填充 创建时间 | |
@LastModifiedDate |
自动填充 修改时间 | |
@CreatedBy |
自动填充 创建人 | |
@LastModifiedBy |
自动填充 修改人 | |
修改删除 | @Modifying |
标记 更新/删除 SQL 方法(配合 @Query ) |
@Transactional |
开启事务,保证修改/删除的原子性 | |
关联关系 | @OneToOne |
一对一关系(如用户与身份证) |
@OneToMany |
一对多关系(如用户与订单) | |
@ManyToOne |
多对一关系(如订单属于用户) | |
@ManyToMany |
多对多关系(如学生与课程) |
一. @SpringBootApplication
1. 是什么(What)
-
@SpringBootApplication
是 Spring Boot 提供的一个 核心注解。 -
它是一个 组合注解,实际上包含了三个常用注解:
less
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 表示这是一个配置类,相当于@Configuration
@EnableAutoConfiguration // 启动Spring Boot的自动配置功能
@ComponentScan // 开启组件扫描,扫描当前包及子包下的Bean
public @interface SpringBootApplication {
}
- 也就是说:
@SpringBootApplication
=@SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
。
2. 为什么(Why)
为什么要用 @SpringBootApplication
?原因有三个:
-
简化配置
- 过去写 Spring 程序,需要在启动类上写多个注解,比如:
less
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class AppConfig { ... }
-
Spring Boot 把这些合并成一个
@SpringBootApplication
,让启动类更简洁。 -
根据 SpringBoot 官网,这三个注解的作用分别是:
@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制@ComponentScan
: 扫描被@Component
(@Service
,@Controller
)注解的 bean,注解默认会扫描该类所在的包下所有的类。@Configuration
:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类
-
统一入口
-
所有 Spring Boot 应用都有一个 入口类 (main 方法所在的类),加上
@SpringBootApplication
就能告诉 Spring Boot:"从我这里开始启动应用,自动配置并扫描组件"。
-
-
约定大于配置
@EnableAutoConfiguration
会根据 classpath 里的依赖和配置,自动装配 Spring Bean(例如加了spring-boot-starter-web
就自动配置 Tomcat 和 SpringMVC)。- 开发者只需要 专注业务逻辑,不用手动写繁琐的配置。
3.怎么用(How)
一般用法很简单:
- 在入口类上加注解
typescript
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
-
控制扫描范围(可选)
默认情况下,
@ComponentScan
会扫描入口类所在包及其子包。如果项目包结构复杂,可以手动指定:
kotlin@SpringBootApplication(scanBasePackages = "com.example.project") public class MyApplication { ... }
-
排除某些自动配置(可选)
如果某些自动配置和你的业务冲突,可以排除:
kotlin@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) public class MyApplication { ... }
4.总结
-
是什么 :
@SpringBootApplication
是 Spring Boot 的核心组合注解,包含配置、自动装配和组件扫描。 -
为什么:让项目入口简洁,减少配置,利用自动化机制提升开发效率。
-
怎么用 :加在应用主类上,main 方法调用
SpringApplication.run(...)
,可选指定扫描范围和排除配置。
二. Spring Bean 相关
1. @Autowired
1.1、是什么(What)
@Autowired
是 Spring 框架 提供的一个 依赖注入(Dependency Injection, DI)注解。- 它的作用是:自动装配 Bean ------ Spring 容器会根据类型(byType)自动找到合适的 Bean,并注入到标注了
@Autowired
的变量、构造方法或 setter 方法中。
换句话说:
@Autowired
就是告诉 Spring: "请帮我找一个合适的 Bean 注入到这里" 。
1.2、为什么(Why)
为什么要用 @Autowired
而不是自己 new 对象?原因有三个:
-
降低耦合度
- 如果你手动
new
对象,代码强依赖具体实现。 - 用
@Autowired
,对象由 Spring 容器统一管理,方便替换和扩展。
- 如果你手动
-
依赖注入自动化
- 传统 XML 配置需要
<bean>
配置和<property ref="xxx">
注入,非常繁琐。 @Autowired
可以省去大量配置,提升开发效率。
- 传统 XML 配置需要
-
支持多种注入方式
- 支持 字段注入、构造器注入、Setter 方法注入,灵活方便。
1.3、怎么用(How)
常见用法有三种:
1.3.1. 字段注入(最常见,但不推荐在大项目中)
typescript
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void doSomething() {
userRepository.save(...);
}
}
1.3.2. 构造器注入(推荐,最安全,利于单元测试)
kotlin
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired // Spring 5+ 单构造器时可以省略
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
1.3.3. Setter 注入(用于可选依赖)
typescript
@Service
public class UserService {
private NotificationService notificationService;
@Autowired(required = false) // 表示注入可选
public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}
}
1.3.4. 自动导入对象到类中,被注入进的类同样要被 Spring 容器管理
比如:Service 类注入到 Controller 类中。
less
@Service
public class UserService {
......
}
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
......
}
1.4、细节知识点
-
默认按类型装配
- Spring 会根据依赖的类型(class/interface)去容器中找唯一匹配的 Bean。
-
多个候选 Bean 时怎么办?
-
如果有多个候选 Bean,会报错。解决方法:
- 用
@Qualifier("beanName")
指定 Bean 名称。 - 或者给其中一个 Bean 加
@Primary
。
- 用
less@Service("emailService") public class EmailNotificationService implements NotificationService {} @Service("smsService") public class SmsNotificationService implements NotificationService {} @Service public class UserService { @Autowired @Qualifier("emailService") private NotificationService notificationService; }
-
-
和 @Resource 的区别
@Autowired
默认按 类型 注入。@Resource
默认按 名称 注入。
1.5、总结
- 是什么 :
@Autowired
是 Spring 的自动依赖注入注解。 - 为什么:减少手动创建对象,降低耦合,让 Bean 管理更灵活。
- 怎么用 :加在字段、构造器、setter 上,默认按类型注入,必要时配合
@Qualifier
或@Primary
。
2.Component
,@Repository
,@Service
, @Controller
2.1、是什么(What)
这四个注解都是 Spring 的组件注解 ,用于把类注册到 Spring 容器中,变成一个 Bean。
-
@Component
- 最基础的组件注解。
- 表示"这是一个 Spring 管理的 Bean",通用的注解,可标注任意类为
Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。。
-
@Repository
- 作用在 DAO(数据访问层) ,比如操作数据库的类。
- 在
@Component
基础上扩展了 持久层异常转换功能(把 JDBC 异常转成 Spring 的统一异常)。
-
@Service
- 作用在 Service(业务逻辑层) 。
- 本质上还是
@Component
,但语义更清晰,表示这是业务逻辑类。
-
@Controller
- 作用在 Controller(表现层 / Web 层) 。
- 搭配
@RequestMapping
、@GetMapping
、@PostMapping
等注解,用来处理 Web 请求,接受用户请求并调用 Service 层返回数据给前端页面。。
✅ 本质:这四个注解都是
@Component
的衍生注解,只是为了分层更清晰。
2.2、为什么(Why)
为什么要分成四个注解?
-
分层语义更清晰
@Component
:通用组件。@Repository
:数据库层。@Service
:业务逻辑层。@Controller
:接口层(Web 层)。
这样别人一看就能知道类的用途。
-
支持 AOP 扩展
-
Spring 可以根据注解识别不同层的 Bean,应用不同的切面逻辑:
@Repository
:提供数据访问异常的转换。@Service
:常用于事务管理。@Controller
:支持 MVC 请求处理。
-
-
更好的维护性
- 大型项目中代码多,分层注解能让团队协作时更直观。
2.3、怎么用(How)
2.3.1. @Component(通用组件)
csharp
@Component
public class MyHelper {
public void doWork() {
System.out.println("Doing helper work...");
}
}
2.3.2. @Repository(数据访问层)
kotlin
@Repository
public class UserRepository {
public User findById(Long id) {
// 假装去数据库查询
return new User(id, "张三");
}
}
2.3.3. @Service(业务逻辑层)
kotlin
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUser(Long id) {
return userRepository.findById(id);
}
}
2.3.4. @Controller(表现层 / Web 层)
less
@Controller
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/{id}")
@ResponseBody
public User getUser(@PathVariable Long id) {
return userService.getUser(id);
}
}
2.4、对比总结表
注解 | 所属层 | 本质 | 额外功能/语义 | 使用场景 |
---|---|---|---|---|
@Component |
通用 | Spring Bean | 无 | 工具类、通用组件 |
@Repository |
DAO 层 | @Component | 异常转换 | 数据访问层 |
@Service |
Service 层 | @Component | 语义标识 | 业务逻辑层 |
@Controller |
Web 层 | @Component | 请求处理能力 | 接口层,接收/响应请求 |
2.5、总结
- @Component:最基础的 Bean 注解。
- @Repository、@Service、@Controller :语义化的
@Component
,分别用于 DAO 层、Service 层、Controller 层,让分层更清晰,同时方便 Spring AOP 和 MVC 扩展。
3.@RestController
3.1、是什么(What)
-
@RestController
是 Spring 4 之后新增的注解。 -
它是一个 组合注解:
less@Controller @ResponseBody public @interface RestController {}
-
也就是说:
@RestController
=@Controller + @ResponseBody
。 -
作用:把一个类标记为 控制器(Controller) ,并且该类下所有方法默认都以 JSON / XML 等格式返回数据,而不是返回视图(JSP/Thymeleaf 等)。
3.2、为什么(Why)
为什么要用 @RestController
?
-
简化开发
-
以前如果要返回 JSON,需要写:
less@Controller public class UserController { @GetMapping("/user") @ResponseBody public User getUser() { ... } }
-
每个方法都得加
@ResponseBody
,很麻烦。 -
用
@RestController
之后:kotlin@RestController public class UserController { @GetMapping("/user") public User getUser() { ... } }
✅ 默认所有方法返回 JSON,更简洁。
-
-
契合前后端分离
- 现在的项目基本都是 前后端分离,前端需要 JSON 数据,而不是 HTML 页面。
@RestController
就是专门为这种 RESTful 风格的接口准备的。
-
和 @Controller 的区别
@Controller
用于传统 MVC,方法返回 视图名称(如 JSP 页面)。@RestController
用于 REST API,方法返回 数据对象(Spring 会自动转成 JSON/XML)。
3.3、怎么用(How)
3.3.1. 最简单的示例
kotlin
@RestController
public class HelloController {
@GetMapping("/hello")
public String sayHello() {
return "Hello, Spring Boot!";
}
}
访问 /hello
,浏览器看到的不是页面,而是字符串 "Hello, Spring Boot!"
。
3.3.2. 返回对象(Spring 会自动转 JSON)
less
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return new User(id, "张三");
}
}
返回结果(JSON):
json
{
"id": 1,
"name": "张三"
}
3.3.3. 搭配 @RequestBody
接收 JSON 参数
less
@PostMapping
public User createUser(@RequestBody User user) {
// 假装保存用户
return user;
}
发送 JSON 请求:
json
{ "id": 2, "name": "李四" }
返回结果:
json
{ "id": 2, "name": "李四" }
3.4、总结对比表
注解 | 用途 | 返回结果 | 典型场景 |
---|---|---|---|
@Controller |
Web 控制器 | 视图(HTML/JSP/Thymeleaf) | 传统 MVC |
@RestController |
REST 控制器 | JSON/XML(默认 JSON) | RESTful API / 前后端分离 |
3.5、总结
-
@RestController
=@Controller + @ResponseBody
。 -
它让控制器方法默认返回 JSON 数据 ,更适合 前后端分离 、RESTful API 项目开发。
4.@Scope
4.1、是什么(What)
@Scope
是 Spring 提供的一个 Bean 作用域(Scope)声明注解。- 它用来指定 Spring 容器中 Bean 的生命周期和作用范围。
- 可以标注在类上(和
@Component
、@Service
等一起用),也可以标注在@Bean
方法上。
常见作用域值有:
作用域值 | 含义 | 使用场景 |
---|---|---|
singleton (默认) |
单例:整个 Spring 容器中只存在一个该 Bean 实例 | 大部分业务 Bean(Service、Dao) |
prototype |
多例:每次获取都会创建一个新的实例 | 有状态的 Bean,比如多线程任务对象 |
request |
每个 HTTP 请求创建一个 Bean,request 结束后销毁 | Web 应用中,保存一次请求的数据 |
session |
每个 HTTP 会话创建一个 Bean,session 结束后销毁 | Web 应用中,保存用户会话数据 |
application |
每个 ServletContext 一个 Bean | 跨会话共享数据(类似单例,但作用于 Web 应用) |
websocket |
每个 WebSocket 会话一个 Bean | WebSocket 应用场景 |
4.2、为什么(Why)
为什么要用 @Scope
?
-
控制 Bean 的生命周期
- 有些对象适合全局共享(比如 Service、Repository) → 单例。
- 有些对象必须每次都新建(比如用户输入表单对象) → 多例。
-
适配不同的 Web 应用场景
- 请求级别的 Bean(
request
),能避免用户数据交叉污染。 - 会话级别的 Bean(
session
),方便保存用户状态。
- 请求级别的 Bean(
-
灵活管理资源
- 单例节省内存,但不适合存储用户特有的数据。
- 多例虽然更灵活,但会增加内存开销。
@Scope
让开发者根据场景选择合适的生命周期。
4.3、怎么用(How)
4.3.1. 基本用法
less
@Component
@Scope("prototype")
public class MyBean {
public MyBean() {
System.out.println("创建了一个 MyBean 实例");
}
}
- 每次
context.getBean(MyBean.class)
都会创建一个新的对象。
4.3.2. 在 @Bean 方法上用
less
@Configuration
public class AppConfig {
@Bean
@Scope("singleton") // 默认值,可以省略
public UserService userService() {
return new UserService();
}
}
4.3.3. Web 应用中的作用域
less
@Component
@Scope("request")
public class RequestScopedBean {
// 每个 HTTP 请求都会创建新的实例
}
less
@Component
@Scope("session")
public class SessionScopedBean {
// 每个用户会话对应一个实例
}
4.4、总结表
作用域 | 生命周期 | 典型场景 |
---|---|---|
singleton(默认) | Spring 容器启动时创建,容器销毁时销毁 | 大多数 Service、Dao |
prototype | 每次获取都创建 | 多线程任务、临时对象 |
request | 每次 HTTP 请求新建 | Web 应用的请求级对象 |
session | 每个 Session 新建 | 保存用户会话数据 |
application | 整个 ServletContext 一个 | 全局共享数据 |
websocket | 每个 WebSocket 会话新建 | WebSocket 应用 |
4.5、总结
-
@Scope
用来控制 Bean 的生命周期。 -
默认是
singleton
单例,如果需要多例、请求级或会话级的 Bean,就要用@Scope
来指定。
5.@Configuration
5.1、是什么(What)
@Configuration
是 Spring 提供的一个 配置类注解。- 标注在一个类上,表示这个类是 Spring 的配置类,相当于一个替代传统 XML 配置文件的 Java 类。
- 在配置类里,可以用
@Bean
方法来声明 Bean。
👉 本质上,@Configuration
类本身也是一个 Spring 容器中的 Bean 。
👉 通过 CGLIB 动态代理 ,Spring 会拦截 @Bean
方法,确保无论调用多少次,返回的都是容器中同一个 Bean(单例)。
5.2、为什么(Why)
为什么要用 @Configuration
?
-
替代 XML 配置
-
过去我们在 Spring 项目里要写:
xml<beans> <bean id="userService" class="com.example.UserService"/> </beans>
-
有了
@Configuration
后,可以直接用 Java 代码声明:typescript@Configuration public class AppConfig { @Bean public UserService userService() { return new UserService(); } }
-
-
类型安全、可读性高
- Java 配置比 XML 配置更直观,有编译器检查和 IDE 代码提示。
-
和 Spring Boot 无缝结合
- Spring Boot 默认大量使用
@Configuration
类,配合@EnableAutoConfiguration
进行自动装配。
- Spring Boot 默认大量使用
5.3、怎么用(How)
5.3.1. 声明配置类
typescript
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
@Bean
public UserRepository userRepository() {
return new UserRepository();
}
}
5.3.2. 使用配置类
ini
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
5.3.3. 单例保证(CGLIB 动态代理机制)
typescript
@Configuration
public class MyConfig {
@Bean
public A a() {
return new A(b()); // 注意这里调用了 b()
}
@Bean
public B b() {
return new B();
}
}
- 如果没有
@Configuration
(比如只用了@Component
),每次调用b()
都会 new 一个B
。 - 加上
@Configuration
后,Spring 用代理类接管了方法调用,b()
返回的始终是同一个 Bean(保证单例)。
5.3.4. 和 @Component 的区别
-
@Component
:只是把类注册成一个 Bean。 -
@Configuration
:是 专门用来写配置的类 ,里面的@Bean
方法会被 Spring 扫描并加入容器。 -
所以:
- 如果你只是想让一个类成为 Bean → 用
@Component
。 - 如果你要集中定义一批 Bean → 用
@Configuration
。
- 如果你只是想让一个类成为 Bean → 用
5.4、总结
注解 | 作用 | 是否代理 @Bean 方法 |
典型场景 |
---|---|---|---|
@Component |
把类注册成 Bean | ❌ 不代理 | 普通组件类 |
@Configuration |
声明配置类 | ✅ 代理,保证单例 | 配置 Bean,替代 XML |
-
@Configuration
用来声明配置类,里面的方法用@Bean
定义 Bean。 -
它比
@Component
更特殊,会通过 CGLIB 代理来保证@Bean
方法返回的是同一个 Bean(单例)。
三.处理常见的 HTTP 请求类型
3.1、是什么(What)
HTTP 协议定义了多种 请求方法(Method) ,常用的有 5 种:
- GET:从服务器获取资源。
- POST:向服务器提交数据,创建新资源。
- PUT:更新资源(整体替换)。
- DELETE:删除资源。
- PATCH:更新资源的部分字段。
它们共同构成了 RESTful API 的核心操作。
3.2、为什么(Why)
为什么需要区分不同请求类型?
-
语义清晰
- 不同请求方法代表不同的操作语义,前后端一看就知道接口是做什么的。
- 例如:
GET /users
→ 获取用户;POST /users
→ 新增用户。
-
符合 RESTful 风格
- RESTful API 倡导 资源 + 动作分离,动作用 HTTP 方法表示,资源用 URL 表示。
- URL 只表示资源(如
/users/12
),具体操作(获取/新增/更新/删除)由方法决定。
-
提升可维护性
- 如果所有操作都用
POST
,接口会混乱。 - 采用不同方法,让接口风格一致,便于团队协作和自动化文档生成。
- 如果所有操作都用
3.3、怎么用(How)
在 Spring Boot / Spring MVC 中,分别用不同的注解来映射请求方法:
3.3.1. GET 请求
- 用途:获取数据(安全操作,不会修改服务器数据)。
- Spring 注解 :
@GetMapping
或@RequestMapping(method=GET)
。
kotlin
@GetMapping("/users")
public List<User> getAllUsers() {
return userRepository.findAll();
}
3.3.2. POST 请求
- 用途:新增资源(非幂等,每次调用都会创建新资源)。
- Spring 注解 :
@PostMapping
。
less
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userRepository.save(user);
}
3.3.3. PUT 请求
- 用途:整体更新资源(幂等,多次调用结果一样)。
- Spring 注解 :
@PutMapping
。
less
@PutMapping("/users/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return userRepository.save(user);
}
3.3.4. DELETE 请求
- 用途:删除资源(幂等,多次删除同一个资源结果一样)。
- Spring 注解 :
@DeleteMapping
。
less
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
userRepository.deleteById(id);
}
3.3.5. PATCH 请求
- 用途:部分更新资源(非幂等,通常用于更新部分字段)。
- Spring 注解 :
@PatchMapping
。
less
@PatchMapping("/users/{id}")
public User updateUserPartially(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
User user = userRepository.findById(id).orElseThrow();
if (updates.containsKey("name")) {
user.setName((String) updates.get("name"));
}
return userRepository.save(user);
}
3.4、总结表
方法 | 含义 | 幂等性 | Spring 注解 | 示例 |
---|---|---|---|---|
GET | 获取资源 | ✅ 幂等 | @GetMapping |
GET /users |
POST | 新建资源 | ❌ 非幂等 | @PostMapping |
POST /users |
PUT | 整体更新 | ✅ 幂等 | @PutMapping |
PUT /users/12 |
DELETE | 删除资源 | ✅ 幂等 | @DeleteMapping |
DELETE /users/12 |
PATCH | 部分更新 | ❌ 通常非幂等 | @PatchMapping |
PATCH /users/12 |
markdown
- **是什么**:HTTP 的 5 种常见请求类型,代表获取/新增/更新/删除/部分更新资源。
- **为什么**:语义清晰、符合 RESTful 风格、便于维护。
- **怎么用**:在 Spring Boot 里用 `@GetMapping/@PostMapping/@PutMapping/@DeleteMapping/@PatchMapping` 来实现对应接口。
四.前后端传值
4.1、@PathVariable
4.1.1、是什么
@PathVariable
是 Spring MVC 提供的注解,用来 绑定 URL 路径中的变量 到方法参数。- 常用于 RESTful 风格的接口,例如
/users/{id}
。
4.1.2、为什么
- 在 RESTful API 中,URL 常常包含资源标识符(如用户 ID)。
@PathVariable
可以方便地将 URL 中的变量提取出来,避免手动解析字符串,提高代码可读性。
4.1.3、怎么用
less
@RestController
@RequestMapping("/users")
public class UserController {
// GET /users/12
@GetMapping("/{id}")
public String getUserById(@PathVariable("id") Long userId) {
return "用户ID: " + userId;
}
}
- 请求:
GET /users/12
- 输出:
用户ID: 12
如果方法参数名和路径变量名一致,可以省略
("id")
。
4.1.4、总结
@PathVariable
用于 URL 路径变量(资源定位)。
4.2、@RequestParam
4.2.1、是什么
@RequestParam
是 Spring MVC 提供的注解,用来 获取 URL 请求参数(? 后面的部分)或者表单参数。
4.2.2、为什么
- 常见的 GET 请求和表单提交会传递查询参数(如分页 page、size),用
@RequestParam
能直接映射到方法参数,简化代码。
4.2.3、怎么用
less
@RestController
@RequestMapping("/users")
public class UserController {
// GET /users?page=1&size=10
@GetMapping
public String getUsers(@RequestParam("page") int page,
@RequestParam(value = "size", required = false, defaultValue = "20") int size) {
return "分页参数: page=" + page + ", size=" + size;
}
}
- 请求:
GET /users?page=1&size=10
- 输出:
分页参数: page=1, size=10
required = false
:参数可选defaultValue = "20"
:没有传递时使用默认值
4.2.4、总结
@RequestParam
用于 URL 查询参数 或表单参数(条件、筛选)。
4.3、@PathVariable
和 @RequestParam
的区别总结
特性 | @PathVariable |
@RequestParam |
---|---|---|
绑定位置 | URL 路径中的占位符 | URL 的查询参数或表单参数 |
示例 URL | /users/12 |
/users?page=1&size=10 |
典型用途 | 获取资源标识符 | 获取分页、搜索、筛选条件 |
是否可选 | 一般必须有(除非配置可选路径) | 可设置 required=false 并提供 defaultValue |
4.4、@RequestBody
4.4.1. 是什么
@RequestBody
是 Spring MVC 提供的注解,作用是 把 HTTP 请求体(Request Body)中的 JSON / XML / 表单数据,自动反序列化并绑定到方法参数对象。- 底层依赖
HttpMessageConverter
(常见是Jackson
解析 JSON)。
4.4.2. 为什么
- 在前后端分离的项目中,前端常用
POST/PUT
请求并发送 JSON 格式数据。 - 如果不用
@RequestBody
,需要手动读取请求体、再自己解析 JSON,非常麻烦。 - 有了
@RequestBody
,Spring 会自动帮你把 JSON 转换为 Java 对象,提高开发效率和可维护性。
4.4.3. 怎么用
(1)绑定到单个对象
less
@RestController
@RequestMapping("/users")
public class UserController {
// POST /users
// 请求体: {"id":1, "name":"Tom", "age":20}
@PostMapping
public String createUser(@RequestBody User user) {
return "创建用户: " + user.getName() + ", 年龄: " + user.getAge();
}
}
请求示例:
bash
POST /users
Content-Type: application/json
{
"id": 1,
"name": "Tom",
"age": 20
}
返回:
makefile
创建用户: Tom, 年龄: 20
(2)绑定到集合
less
@PostMapping("/batch")
public String createUsers(@RequestBody List<User> users) {
return "接收了 " + users.size() + " 个用户";
}
请求体:
css
[ {"id":1,"name":"Tom","age":20}, {"id":2,"name":"Jerry","age":22}]
(3)和 @RequestParam
/ @PathVariable
混用
less
// PUT /users/10?notify=true
@PutMapping("/{id}")
public String updateUser(@PathVariable Long id,
@RequestParam(defaultValue = "false") boolean notify,
@RequestBody User user) {
return "更新用户ID=" + id + ", 新名字=" + user.getName() + ", 是否通知=" + notify;
}
4.4.4. 注意事项 ⚠️
- 必须有
Content-Type: application/json
,否则可能报错。 - 需要依赖
Jackson
或其他 JSON 库(Spring Boot 默认集成)。 @RequestBody
只能从 请求体 获取数据,不能获取 URL 参数。- 如果是表单提交(
application/x-www-form-urlencoded
),推荐用@RequestParam
。 - 一个请求方法只可以有一个
@RequestBody
,但是可以有多个@RequestParam
和@PathVariable
。
4.4.5. 总结
@RequestBody
用来把请求体中的 JSON 数据转成 Java 对象,特别适合前后端分离的场景。
五. 读取配置信息
5.1、是什么(What)
Spring 提供多种方式读取配置文件中的信息,常用的有三种:
@Value
:读取单个配置属性。@ConfigurationProperties
:将配置文件中的属性绑定到一个 Java Bean 上,支持嵌套对象和集合。@PropertySource
:指定读取某个properties
文件(不常用,多用于老项目)。
配置文件可以是
application.yml
或application.properties
。
5.2、为什么(Why)
-
项目中会有很多 可变参数或外部配置,比如:
- 数据库连接
- 阿里云 OSS 配置
- 微信小程序/公众号配置
- 第三方 API Key
-
将这些信息写死在代码中不好维护,也不安全。
-
Spring 提供的注解可以:
- 自动注入配置值到 Bean
- 支持类型安全
- 支持集合和嵌套对象
- 便于不同环境切换(如
application-dev.yml
、application-prod.yml
)
5.3、怎么用(How)
5.3.1. 使用 @Value
(最简单)
arduino
# application.yml
wuhan2020: "2020年初武汉爆发了新型冠状病毒,疫情严重。"
kotlin
@Component
public class WuhanInfo {
@Value("${wuhan2020}")
private String wuhan2020;
public void printInfo() {
System.out.println(wuhan2020);
}
}
- 适合读取 单个简单值。
- 支持默认值:
kotlin
@Value("${wuhan2020:武汉加油}")
private String wuhan2020;
5.3.2. 使用 @ConfigurationProperties
(常用,支持复杂对象)
yaml
# application.yml
library:
location: 湖北武汉
books:
- name: 天才基本法
description: 林朝夕故事
- name: 时间的秩序
description: 时间本质探讨
typescript
@Component
@ConfigurationProperties(prefix = "library")
public class LibraryProperties {
private String location;
private List<Book> books;
@Data // lombok 提供 getter/setter/toString
public static class Book {
private String name;
private String description;
}
}
csharp
@Component
public class LibraryService {
private final LibraryProperties libraryProperties;
public LibraryService(LibraryProperties libraryProperties) {
this.libraryProperties = libraryProperties;
}
public void printLibraryInfo() {
System.out.println(libraryProperties.getLocation());
libraryProperties.getBooks().forEach(System.out::println);
}
}
- 适合读取 复杂对象和集合。
- 支持 类型安全、校验注解 (如
@NotEmpty
)。
5.3.3. 使用 @PropertySource
(不常用)
ini
# website.properties
url=https://www.example.com
less
@Component
@PropertySource("classpath:website.properties")
public class Website {
@Value("${url}")
private String url;
public void printUrl() {
System.out.println(url);
}
}
- 适合读取 非 Spring Boot 默认配置文件。
- 在 Spring Boot 项目中通常用不到,更多用于老的 Spring 项目。
5.4、总结表
注解 | 适合场景 | 支持复杂对象 | 示例配置文件 | 使用方式 |
---|---|---|---|---|
@Value |
单个属性 | ❌ | wuhan2020: "信息" |
@Value("${wuhan2020}") |
@ConfigurationProperties |
一组属性或嵌套对象 | ✅ | library.location 、library.books |
@ConfigurationProperties(prefix="library") |
@PropertySource |
指定 properties 文件 | ❌ | website.properties |
@PropertySource("classpath:website.properties") |
markdown
- **简单值** → `@Value`
- **复杂对象 / 集合** → `@ConfigurationProperties`
- **指定文件** → `@PropertySource`
六. 参数校验
6.1、是什么(What)
- 参数校验 是对前端传入的数据进行 合法性检查,防止非法或异常数据进入后端。
- Spring Boot 使用 JSR 标准(Java Specification Requests) 和 Hibernate Validator 来实现校验。
- 常用注解在
javax.validation.constraints
包中,包括:
注解 | 作用 |
---|---|
@Null |
被注释的元素必须为 null |
@NotNull |
不能为 null |
@NotEmpty |
字符串或集合不能为空 |
@NotBlank |
字符串不能为空且必须有非空白字符 |
@Email |
必须是合法邮箱 |
@Size(min=, max=) |
字符串或集合长度/大小限制 |
@Digits (integer, fraction) |
被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Pattern(regex=,flag=) |
必须符合指定正则 |
@Min/@Max |
数值最小/最大值 |
@DecimalMin/@DecimalMax |
小数最小/最大值 |
@Past/@Future |
日期必须为过去/将来 |
@AssertTrue/@AssertFalse |
布尔值必须为 true/false |
6.2、为什么(Why)
-
前端校验容易被绕过,用户可以直接用 HTTP 工具发送非法请求。
-
后端统一校验可保证 数据完整性和安全性。
-
Spring Boot 集成 Hibernate Validator,自动支持:
- 校验请求体(
@RequestBody
) - 校验请求参数(
@PathVariable
/@RequestParam
)
- 校验请求体(
-
提供统一的异常处理机制,可以返回标准错误信息给前端。
6.3、怎么用(How)
6.3.1. 验证请求体(@RequestBody
)
less
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
@NotNull(message = "classId 不能为空")
private String classId;
@Size(max = 33)
@NotNull(message = "name 不能为空")
private String name;
@Pattern(regexp = "(^Man$|^Woman$|^UGM$)", message = "sex 值不在可选范围")
@NotNull(message = "sex 不能为空")
private String sex;
@Email(message = "email 格式不正确")
@NotNull(message = "email 不能为空")
private String email;
}
less
@RestController
@RequestMapping("/api")
public class PersonController {
@PostMapping("/person")
public ResponseEntity<Person> createPerson(@RequestBody @Valid Person person) {
return ResponseEntity.ok(person);
}
}
- 使用
@Valid
注解触发校验。 - 校验失败会抛出
MethodArgumentNotValidException
。
6.3.2. 验证请求参数(@PathVariable
/ @RequestParam
)
- 一定要在类上加
@Validated
,Spring 才会校验方法参数。
less
@RestController
@RequestMapping("/api")
@Validated
public class PersonController {
// GET /api/person/3
@GetMapping("/person/{id}")
public ResponseEntity<Integer> getPersonById(
@PathVariable @Max(value = 5, message = "超过 id 的范围了") Integer id) {
return ResponseEntity.ok(id);
}
}
- 可以在方法参数上使用 JSR 注解,如
@Max
,@Min
,@NotNull
等。 - 校验失败会抛出
ConstraintViolationException
。
6.3.3. 异常统一处理(可选)
scss
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleValidException(MethodArgumentNotValidException ex) {
String msg = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
return ResponseEntity.badRequest().body(msg);
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<String> handleConstraintException(ConstraintViolationException ex) {
String msg = ex.getConstraintViolations().iterator().next().getMessage();
return ResponseEntity.badRequest().body(msg);
}
}
- 可以统一处理校验异常,返回友好的提示信息给前端。
6.4、总结表
校验方式 | 使用场景 | 注解 | 配合注解 |
---|---|---|---|
请求体 | @RequestBody JSON 对象 |
@NotNull, @Size, @Email... |
@Valid |
方法参数 | @PathVariable / @RequestParam |
@Max, @Min, @NotNull... |
@Validated |
markdown
- **参数校验**保证后端数据安全和完整性;
- **JSR 注解 + Spring Boot** 能轻松校验请求体和请求参数;
- `@Valid` 用于对象,`@Validated` 用于方法参数。
七. 全局处理 Controller 层异常
7.1、是什么
在 Spring MVC 项目中,当 Controller 方法抛出异常时,如果没有统一处理,异常会直接冒泡到前端,返回的是复杂的堆栈信息(不友好,也不安全)。
Spring 提供了 全局异常处理机制:
@ControllerAdvice
:标记一个类为全局异常处理类,可以捕获所有 Controller 层的异常。@ExceptionHandler
:在@ControllerAdvice
标注的类里,声明具体的异常处理方法。
7.2、为什么要用
主要解决几个问题:
- 统一管理异常:不用在每个 Controller 方法里写 try-catch。
- 返回统一格式 :比如所有异常返回
{code, message, data}
的 JSON。 - 增强可维护性:异常逻辑和业务逻辑解耦。
- 安全性:避免系统堆栈直接暴露给前端。
7.3、怎么用
7.3.1. 示例代码
less
// 定义统一的响应结构
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private Integer code; // 状态码
private String message; // 提示信息
private T data; // 数据
}
typescript
// 全局异常处理类
@ControllerAdvice
@ResponseBody // 保证返回 JSON,而不是页面
public class GlobalExceptionHandler {
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
// 获取第一个错误提示
String errorMsg = ex.getBindingResult()
.getFieldError()
.getDefaultMessage();
return new Result<>(400, errorMsg, null);
}
/**
* 处理空指针异常
*/
@ExceptionHandler(NullPointerException.class)
public Result<?> handleNullPointerException(NullPointerException ex) {
return new Result<>(500, "系统发生空指针错误,请联系管理员!", null);
}
/**
* 处理所有未捕获的异常
*/
@ExceptionHandler(Exception.class)
public Result<?> handleException(Exception ex) {
return new Result<>(500, "系统异常:" + ex.getMessage(), null);
}
}
7.3.2. 使用效果
- 当 Controller 抛出异常(例如参数不合法、空指针等),会被
GlobalExceptionHandler
捕获。 - 前端拿到的就是统一格式的 JSON:
json
{
"code": 400,
"message": "用户名不能为空",
"data": null
}
7.4、总结
-
是什么 :Spring 提供的全局异常捕获机制(
@ControllerAdvice + @ExceptionHandler
)。 -
为什么:统一管理异常、规范返回结构、提升安全性和可维护性。
-
怎么用 :写一个带
@ControllerAdvice
的类,定义多个@ExceptionHandler
方法处理不同异常。
八. JPA相关
8.1、创建表
8.1.1. 是什么
@Entity
:声明一个类是 JPA 实体,映射到数据库表。@Table
:设置数据库表名、索引等。
8.1.2. 为什么
- JPA 通过注解映射实体类与数据库表,开发者无需手写 SQL 建表语句,大大提高效率。
8.1.3. 怎么用
less
@Entity
@Table(name = "role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自增
private Long id;
private String name;
private String description;
}
👉 数据库会自动生成 role
表。
8.2、创建主键
8.2.1. 是什么
@Id
:声明主键字段。@GeneratedValue
:定义主键生成策略。
8.2.2. 为什么
- 不同数据库支持的主键策略不同,需要灵活选择(自增、序列、UUID 等)。
8.2.3. 怎么用
常见 4 种策略:
arduino
public enum GenerationType {
TABLE, // 通过一张表维护主键
SEQUENCE, // 使用数据库序列 (Oracle, PostgreSQL)
IDENTITY, // 主键自增 (MySQL 常用)
AUTO // JPA 自动选择 (默认)
}
示例:
less
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
自定义主键策略:
less
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
8.2.4. jpa 提供的主键生成策略有如下几种:
scss
/**
* Hibernate 默认的主键生成器工厂
* 实现了 MutableIdentifierGeneratorFactory,用于注册各种主键生成策略
*/
public class DefaultIdentifierGeneratorFactory
implements MutableIdentifierGeneratorFactory, Serializable, ServiceRegistryAwareService {
@SuppressWarnings("deprecation")
public DefaultIdentifierGeneratorFactory() {
// 注册 UUID v2 生成器(基于 Java UUID,推荐使用的 UUID 生成方式)
register("uuid2", UUIDGenerator.class);
// 注册 GUID 生成器(和 uuid2 类似,但依赖数据库/操作系统生成全局唯一标识)
register("guid", GUIDGenerator.class);
// 注册 UUID 十六进制生成器(旧版 UUID,已经不推荐使用)
register("uuid", UUIDHexGenerator.class);
// 注册 uuid.hex,等价于 uuid(已废弃)
register("uuid.hex", UUIDHexGenerator.class);
// 注册 Assigned 策略 ------ 由开发者手动赋值主键,而不是自动生成
register("assigned", Assigned.class);
// 注册 Identity 策略 ------ 使用数据库自增字段生成主键(MySQL 常用)
register("identity", IdentityGenerator.class);
// 注册 Select 策略 ------ 通过查询数据库获取主键(少见)
register("select", SelectGenerator.class);
// 注册 Sequence 策略 ------ 使用数据库序列生成主键(Oracle、PostgreSQL 常用)
register("sequence", SequenceStyleGenerator.class);
// 注册 seqhilo ------ 基于高低位(hi/lo)的序列生成器(性能优化用,较少使用)
register("seqhilo", SequenceHiLoGenerator.class);
// 注册 Increment 策略 ------ 简单自增(非线程安全,不推荐生产使用)
register("increment", IncrementGenerator.class);
// 注册 Foreign 策略 ------ 主键来自另一张表的外键(常见于一对一关联关系)
register("foreign", ForeignGenerator.class);
// 注册 sequence-identity ------ 序列 + 自增混合模式
register("sequence-identity", SequenceIdentityGenerator.class);
// 注册 enhanced-sequence ------ 增强版序列生成器(推荐替代旧的 sequence)
register("enhanced-sequence", SequenceStyleGenerator.class);
// 注册 enhanced-table ------ 增强版表生成器(通过维护一张表来生成主键)
register("enhanced-table", TableGenerator.class);
}
/**
* 注册主键生成策略
* @param strategy 策略名称(如 "uuid"、"identity")
* @param generatorClass 对应的生成器实现类
*/
public void register(String strategy, Class generatorClass) {
LOG.debugf("Registering IdentifierGenerator strategy [%s] -> [%s]",
strategy, generatorClass.getName());
final Class previous = generatorStrategyToClassNameMap.put(strategy, generatorClass);
if (previous != null) {
LOG.debugf(" - overriding [%s]", previous.getName());
}
}
}
- 各种主键生成策略对照表
策略名 | 对应类 | 说明 | 适用场景 |
---|---|---|---|
uuid2 |
UUIDGenerator |
使用 Java UUID v2 生成 | 推荐,分布式环境 |
guid |
GUIDGenerator |
GUID,全局唯一标识 | Windows/SQL Server |
uuid / uuid.hex |
UUIDHexGenerator |
旧版 UUID | 已废弃 |
assigned |
Assigned |
主键由应用代码手动赋值 | 业务系统已有主键 |
identity |
IdentityGenerator |
数据库自增主键 | MySQL 常用 |
sequence |
SequenceStyleGenerator |
数据库序列 | Oracle/PostgreSQL |
seqhilo |
SequenceHiLoGenerator |
hi/lo 算法生成主键 | 大并发优化 |
increment |
IncrementGenerator |
自增计数器 | 单机测试用,不推荐生产 |
foreign |
ForeignGenerator |
使用外键作为主键 | 一对一关系 |
sequence-identity |
SequenceIdentityGenerator |
序列 + 自增 | 特殊数据库 |
enhanced-sequence |
SequenceStyleGenerator |
增强版序列 | 替代旧 sequence |
enhanced-table |
TableGenerator |
通过表维护主键 | 无序列支持的数据库 |
8.3、设置字段类型
8.3.1. 是什么
@Column
:声明数据库列属性(名称、长度、非空、默认值等)。
8.3.2. 为什么
- 可以精确控制数据库表字段的属性。
8.3.3. 怎么用
ini
@Column(name = "user_name", nullable = false, length = 32)
private String userName;
@Column(columnDefinition = "tinyint(1) default 1")
private Boolean enabled;
8.4、指定不持久化的字段
8.4.1. 是什么
@Transient
:该字段不会映射到数据库列。
8.4.2. 为什么
- 某些字段只在业务逻辑中使用,不需要保存到数据库。
8.4.3. 怎么用
typescript
@Transient
private String secrect;
8.5、声明大字段
8.5.1. 是什么
@Lob
:声明大字段(如 TEXT, BLOB)。
8.5.2. 为什么
- 存储大文本(文章内容)或二进制数据(图片、文件)。
8.5.3. 怎么用
less
@Lob
@Basic(fetch = FetchType.EAGER)
@Column(name = "content", columnDefinition = "LONGTEXT NOT NULL")
private String content;
8.6、创建枚举字段
8.6.1. 是什么
@Enumerated
:声明字段是枚举类型。
8.6.2. 为什么
- 可以把枚举保存为
int
或String
,可读性更好。
8.6.3. 怎么用
kotlin
public enum Gender {
MALE("男性"), FEMALE("女性");
private String value;
Gender(String str){ value = str; }
}
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING) // 存储为字符串
private Gender gender;
}
数据库保存结果:MALE
或 FEMALE
。
8.7、审计功能(自动维护创建时间/修改时间)
8.7.1. 是什么
@CreatedDate
、@LastModifiedDate
:自动填充创建时间和修改时间。@CreatedBy
、@LastModifiedBy
:自动填充创建人和修改人。@EnableJpaAuditing
:开启 JPA 审计功能。
8.7.2. 为什么
- 开发者无需手动维护时间戳和用户,避免出错。
8.7.3. 怎么用
抽象基类:
less
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditBase {
@CreatedDate
@Column(updatable = false)
private Instant createdAt;
@LastModifiedDate
private Instant updatedAt;
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String updatedBy;
}
配置类:
less
@Configuration
@EnableJpaAuditing
public class AuditSecurityConfiguration {
@Bean
AuditorAware<String> auditorAware() {
return () -> Optional.ofNullable(
SecurityContextHolder.getContext().getAuthentication().getName()
);
}
}
8.8、修改/删除数据
8.8.1. 是什么
@Modifying
:标识修改/删除操作。@Transactional
:保证事务。
8.8.2. 为什么
- 默认 JPA Repository 只支持简单 CRUD,自定义 SQL 需要额外标记。
8.8.3. 怎么用
less
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
@Modifying
@Transactional(rollbackFor = Exception.class)
void deleteByUserName(String userName);
@Modifying
@Transactional
@Query("update User u set u.name = ?1 where u.id = ?2")
void updateUserNameById(String name, Long id);
}
8.9、关联关系
8.9.1 是什么
JPA 提供注解来描述实体之间的关系,常见的四种关系类型:
@OneToOne
:一对一@OneToMany
:一对多@ManyToOne
:多对一@ManyToMany
:多对多
这些注解可以将面向对象中的对象关系映射到数据库表的外键、关联表上,实现对象关系映射(ORM)。
8.9.2 为什么
- 在面向对象编程中,实体类之间存在引用关系(比如用户有一个详细资料,部门有多个员工)。
- 在关系型数据库中,表之间通常用 外键或关联表 建立关系。
- 使用 JPA 注解可以自动将对象关系映射到数据库关系,简化开发,并可支持 级联操作、懒加载、事务管理。
8.9.3 怎么用
(1) 一对一 (@OneToOne
)
-
说明:一个实体对应另一个实体,数据库表中通常通过外键关联。
-
常用属性:
mappedBy
:被动方不拥有外键,用在关系的另一方。cascade
:级联操作,如CascadeType.ALL
包括新增、更新、删除。fetch
:懒加载或立即加载,FetchType.LAZY
/FetchType.EAGER
。
less
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 一个用户对应一个详细资料
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "profile_id") // 外键字段
private UserProfile profile;
}
- 级联删除 :删除
User
时,UserProfile
会被自动删除(因为CascadeType.ALL
包含 REMOVE)。
(2) 一对多 + 多对一 (@OneToMany
/ @ManyToOne
)
- 说明:一个实体对应多个实体,多方实体对应一个实体。
mappedBy
用在一方,表示由多方维护外键。
less
@Entity
public class Department {
@Id
private Long id;
// 一个部门有多个员工
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees;
}
@Entity
public class Employee {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id") // 外键
private Department department;
}
-
说明:
- 删除
Department
时,如果cascade = CascadeType.ALL
+orphanRemoval = true
,对应员工会自动删除。 FetchType.LAZY
可以延迟加载员工列表,避免一次性加载所有员工。
- 删除
(3) 多对多 (@ManyToMany
)
- 说明:两个实体之间多对多关系,通常通过 中间关联表 维护。
less
@Entity
public class Student {
@Id
private Long id;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
@JoinTable(
name = "student_course", // 中间表
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private List<Course> courses;
}
@Entity
public class Course {
@Id
private Long id;
@ManyToMany(mappedBy = "courses") // 被动方
private List<Student> students;
}
-
说明:
- 中间表
student_course
保存学生和课程的对应关系。 - 删除
Student
或Course
时,关联表的数据会同步删除,但默认不会删除对方实体。 cascade
和orphanRemoval
可以控制级联操作。
- 中间表
8.9.4 其他常用属性和最佳实践
属性/注解 | 作用 |
---|---|
cascade |
设置级联操作类型,如 PERSIST、MERGE、REMOVE、ALL |
orphanRemoval |
是否删除孤儿对象(从集合中移除即删除数据库记录) |
fetch |
延迟加载 LAZY 或立即加载 EAGER |
mappedBy |
指定关系维护方,一方加 mappedBy 表示不维护外键 |
@JoinColumn |
指定外键字段名 |
@JoinTable |
多对多关系中指定中间表和关联列 |
8.10、总结
-
表相关 :
@Entity @Table @Column @Id
-
主键策略 :
IDENTITY / SEQUENCE / UUID
-
字段控制 :
@Transient @Lob @Enumerated
-
审计功能 :
@EnableJpaAuditing + @CreatedDate
-
修改删除 :
@Modifying @Transactional
-
关联关系 :
@OneToOne @OneToMany @ManyToOne @ManyToMany
8.11、JPA 常用注解速查表
分类 | 注解 | 说明 |
---|---|---|
表相关 | @Entity |
标记一个类为 JPA 实体类,映射数据库表 |
@Table(name = "xxx") |
指定实体类对应的表名(默认类名) | |
@Column(name = "xxx", nullable = false, length = 100) |
指定字段名、长度、是否可空等约束 | |
@Id |
标识 主键字段 | |
主键策略 | @GeneratedValue(strategy = GenerationType.IDENTITY) |
自增主键(MySQL 常用) |
@GeneratedValue(strategy = GenerationType.SEQUENCE) |
使用数据库序列生成主键(Oracle 常用) | |
@GeneratedValue(strategy = GenerationType.UUID) |
使用 UUID 作为主键(Spring Boot 3.0+ 支持) | |
字段控制 | @Transient |
该字段 不持久化(不会映射到表) |
@Lob |
存储大字段(如 TEXT 、BLOB ) |
|
@Enumerated(EnumType.STRING) |
枚举保存为 字符串 | |
@Enumerated(EnumType.ORDINAL) |
枚举保存为 序号 | |
审计功能 | @EnableJpaAuditing |
开启 JPA 审计功能(放在配置类上) |
@CreatedDate |
自动填充 创建时间 | |
@LastModifiedDate |
自动填充 修改时间 | |
@CreatedBy |
自动填充 创建人 | |
@LastModifiedBy |
自动填充 修改人 | |
修改删除 | @Modifying |
标记 更新/删除 SQL 方法(配合 @Query ) |
@Transactional |
开启事务,保证修改/删除的原子性 | |
关联关系 | @OneToOne |
一对一关系(如用户与身份证) |
@OneToMany |
一对多关系(如用户与订单) | |
@ManyToOne |
多对一关系(如订单属于用户) | |
@ManyToMany |
多对多关系(如学生与课程) |
九. 事务@Transactional
9.1、是什么
@Transactional
是 Spring 提供的 声明式事务管理注解 ,用于定义方法或类的事务边界。
主要特点:
- 可以自动开启、提交或回滚事务。
- 支持配置事务的传播行为、隔离级别、回滚规则、超时时间等属性。
- 可作用于方法或类。
9.2、为什么需要事务
在数据库操作中,经常存在 多步操作,例如:
- 保存用户信息。
- 保存用户权限。
- 保存操作日志。
如果中间某步失败而前面已执行成功,会造成 数据不一致 。
事务能保证:
- 原子性:所有操作要么全部成功,要么全部回滚。
- 一致性:事务执行前后数据保持一致。
- 隔离性:并发事务之间互不干扰。
- 持久性:事务提交后,数据永久存储。
总结:事务是保证数据库数据可靠性和一致性的核心机制。
9.3、怎么用
(1) 方法级事务
在单个方法上声明事务:
java
@Service
public class UserService {
@Transactional(rollbackFor = Exception.class) // 指定遇到 Exception 也回滚
public void saveUser(User user) throws Exception {
userRepository.save(user); // 保存用户
// 模拟异常
if(user.getName() == null) {
throw new Exception("用户名不能为空"); // 会回滚
}
logRepository.save(new Log("新增用户")); // 保存日志
}
}
-
说明:
-
rollbackFor
: 指定哪些异常触发回滚,默认只有RuntimeException
回滚。 -
noRollbackFor
: 指定某些异常不回滚。 -
propagation
: 事务传播行为,常用值:REQUIRED
(默认):支持当前事务,如果没有则新建。REQUIRES_NEW
:总是新建事务,挂起当前事务。NESTED
:嵌套事务。
-
isolation
: 事务隔离级别,常用值:READ_UNCOMMITTED
:读未提交READ_COMMITTED
:读已提交REPEATABLE_READ
:可重复读(MySQL 默认)SERIALIZABLE
:串行化
-
timeout
: 超时时间(秒),事务执行超过时间会回滚。
-
(2) 类级事务
在类上声明事务,类中所有 public 方法都会自动开启事务:
typescript
@Service
@Transactional(rollbackFor = Exception.class)
public class UserService {
public void saveUser(User user) {
userRepository.save(user);
}
public void updateUser(User user) {
userRepository.save(user);
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
-
说明:
- 方法级
@Transactional
会覆盖类级的事务配置。 - 对类中非 public 方法、private 方法不起作用。
- 方法级
(3) 注意事项
-
事务只对 public 方法有效
- 因为 Spring 事务是通过 AOP 代理实现的,private/protected 方法不会被代理拦截。
-
内部方法调用导致事务失效
- 同一个类内部调用方法(
this.method()
),事务不会生效,因为是绕过了代理对象。 - 解决方案:通过 自调用注入自身 Bean 或将方法拆到不同类。
- 同一个类内部调用方法(
-
回滚策略
- 默认:只有
RuntimeException
和Error
回滚。 - 自定义:
rollbackFor = Exception.class
让非运行时异常也回滚。 noRollbackFor = CustomException.class
让指定异常不回滚。
- 默认:只有
-
事务传播行为示例
typescript
@Transactional
public void outer() {
inner(); // 默认传播行为 REQUIRED,内层方法会加入外层事务
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// 无论外层事务如何,inner 方法总是新建事务
}
-
事务与 JPA 关联
- JPA 的增删改查操作,如果没有事务,可能 立即执行 SQL 或在 flush 时才执行。
- 对
@OneToMany
、@ManyToOne
等级联操作,事务必须存在,否则会报错或数据不一致。
9.4、总结
@Transactional
是 Spring 声明式事务的核心。- 支持方法级或类级配置。
- 默认只回滚运行时异常,可通过
rollbackFor
配置回滚规则。 - 事务传播、隔离、超时等配置可根据业务需求灵活调整。
- 注意内部调用和 private 方法可能导致事务失效。
十. JSON 数据处理
10.1、过滤 JSON 数据
10.1.1. 是什么
过滤 JSON 数据就是在 序列化(Java 对象 → JSON)或反序列化(JSON → Java 对象) 时,排除掉某些字段不参与生成或解析。
Jackson 提供两个主要注解:
@JsonIgnoreProperties
:类级别,忽略指定字段。@JsonIgnore
:字段级别,忽略该字段。
10.1.2. 为什么
- 保护敏感信息:如密码、权限列表不想暴露给前端。
- 减少数据冗余:有些字段对前端无用,省流量。
- 避免循环引用:对象之间相互引用时,过滤字段可防止 JSON 序列化报错。
10.1.3. 怎么用
1️⃣ @JsonIgnoreProperties(类级别)
typescript
// 忽略 userRoles 字段
@JsonIgnoreProperties({"userRoles"})
public class User {
private String userName;
private String fullName;
private String password;
private List<UserRole> userRoles = new ArrayList<>();
}
- 放在类上,数组中写要忽略的字段名。
- 同时适用于序列化和反序列化。
2️⃣ @JsonIgnore(字段级别)
typescript
public class User {
private String userName;
private String fullName;
private String password;
@JsonIgnore
private List<UserRole> userRoles = new ArrayList<>();
}
- 放在字段上,只有该字段被忽略。
- 更精细,适合单个字段控制。
💡 区别 :@JsonIgnoreProperties
可以一次性忽略多个字段,@JsonIgnore
针对单个字段。
10.2、格式化 JSON 数据
10.2.1. 是什么
JSON 格式化指的是在序列化或反序列化时,对字段值进行特定格式化,比如日期格式。
Jackson 提供 @JsonFormat
注解。
10.2.2. 为什么
- 保证前后端统一:前端接收到的日期格式固定,避免解析错误。
- 满足业务需求 :有些接口需要特定时间格式,如
yyyy-MM-dd
或 ISO8601。
10.2.3. 怎么用
ini
public class Event {
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
timezone = "GMT"
)
private Date eventTime;
}
shape
:数据形态,STRING
表示作为字符串输出。pattern
:日期格式。timezone
:时区。
序列化效果:
json
{
"eventTime": "2025-09-01T10:00:00.000Z"
}
10.3、扁平化对象
10.3.1. 是什么
扁平化对象是将 嵌套的对象字段"拉平" 到 JSON 的顶层,而不是保留嵌套结构。
使用注解:@JsonUnwrapped
。
10.3.2. 为什么
- 简化前端处理:前端不必访问嵌套对象字段。
- 接口风格统一:REST API 输出可以统一扁平化字段。
- 减少冗余嵌套:JSON 更清晰,数据结构更直观。
10.3.3. 怎么用
less
@Getter
@Setter
@ToString
public class Account {
@JsonUnwrapped
private Location location;
@JsonUnwrapped
private PersonInfo personInfo;
@Getter
@Setter
@ToString
public static class Location {
private String provinceName;
private String countyName;
}
@Getter
@Setter
@ToString
public static class PersonInfo {
private String userName;
private String fullName;
}
}
未扁平化 JSON
json
{
"location": {
"provinceName":"湖北",
"countyName":"武汉"
},
"personInfo": {
"userName": "coder1234",
"fullName": "shaungkou"
}
}
扁平化 JSON(使用 @JsonUnwrapped)
json
{
"provinceName":"湖北",
"countyName":"武汉",
"userName": "coder1234",
"fullName": "shaungkou"
}
10.4、总结表格
功能 | 注解 | 作用 | 使用场景 |
---|---|---|---|
过滤字段 | @JsonIgnore / @JsonIgnoreProperties |
序列化或反序列化时忽略字段 | 隐私字段、减少冗余、避免循环引用 |
格式化数据 | @JsonFormat |
控制字段的输出格式(如日期) | 日期/时间格式统一,前后端一致 |
扁平化对象 | @JsonUnwrapped |
将嵌套对象字段拉平到顶层 | 接口简化、前端易用、JSON 美化 |
十一. 测试相关注解
11.1、@ActiveProfiles
11.1.1. 是什么
- Spring Boot 提供的注解,用于指定 测试类运行时生效的配置文件(Spring Profile)。
- 可以理解为告诉 Spring "在运行这个测试时,使用哪个环境的配置"。
11.1.2. 为什么
- 测试与开发环境配置不同,比如数据库、缓存等连接信息。
- 避免测试影响正式配置,保证测试隔离。
- 可以针对不同环境运行不同的测试数据或逻辑。
11.1.3. 怎么用
less
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test") // 使用 application-test.yml 或 application-test.properties
@Slf4j
public abstract class TestBase {
// 公共测试配置
}
"test"
对应application-test.yml
或application-test.properties
。- 支持多 profile:
@ActiveProfiles({"test", "dev"})
。
11.2、@Test
11.2.1. 是什么
- JUnit 提供的注解,用于声明一个方法为 测试方法。
- 测试框架会自动识别并运行标注了
@Test
的方法。
11.2.2. 为什么
- 明确标记测试入口,框架自动执行。
- 让测试方法与普通方法区分开,便于测试管理。
11.2.3. 怎么用
typescript
@Test
void should_import_student_success() {
// 测试逻辑
}
- 方法可以抛异常,也可以包含断言(
Assertions.assertEquals
等)。
11.3、@Transactional
(测试方法)
11.3.1. 是什么
- Spring 提供的事务管理注解,声明在测试方法上时,方法执行完毕后会回滚事务。
11.3.2. 为什么
- 避免污染测试数据:测试中插入、修改的数据库记录不会影响正式数据。
- 保证每次测试独立:每次运行方法都是干净的数据状态。
11.3.3. 怎么用
less
@Test
@Transactional
void should_save_user_success() {
// 测试数据库操作,执行完自动回滚
}
💡 注意:
- 默认只在使用 Spring 测试上下文时生效(
@SpringBootTest
)。 - 回滚可以通过
@Rollback(false)
禁用。
11.4、@WithMockUser
(Spring Security)
11.4.1. 是什么
- Spring Security 提供的测试注解,用于 模拟一个登录用户,并可赋予角色或权限。
11.4.2. 为什么
- 测试安全控制逻辑,比如接口权限校验。
- 避免每次测试都实际登录,方便自动化测试。
11.4.3. 怎么用
less
@Test
@Transactional
@WithMockUser(username = "user-id-18163138155", authorities = "ROLE_TEACHER")
void should_import_student_success() throws Exception {
// 模拟用户 ROLE_TEACHER 执行操作
}
username
:模拟登录用户名authorities
:模拟用户角色或权限- 可组合使用
@Test
和@Transactional
11.5、总结表格
注解 | 作用 | 使用场景 |
---|---|---|
@ActiveProfiles |
指定测试使用的 Spring 配置文件 | 测试隔离环境,加载特定配置 |
@Test |
声明测试方法 | 所有单元测试/集成测试 |
@Transactional |
测试方法事务执行后回滚 | 测试数据库操作,保证数据不污染 |
@WithMockUser |
模拟登录用户及角色权限 | 测试安全接口、权限校验 |