Spring常用注解总结(持续更新)

661 阅读40分钟

0. 引言

因为我的个人习惯,我会从: 是什么(What),为什么(Why),怎么用(How)三个方面来分析这些技术和知识点,便于我加深理解和记忆。

注解名称注解作用
@SpringBootApplicationSpring Boot 的核心组合注解,包含配置、自动装配和组件扫描。
@AutowiredSpring 的自动依赖注入注解。
@Component通用组件。
@Repository数据库层。
@Service业务逻辑层。
@Controller接口层(Web 层)。
@RestController把一个类标记为 控制器(Controller) ,并且该类下所有方法默认都以 JSON / XML 等格式返回数据,而不是返回视图(JSP/Thymeleaf 等)。
@ScopeSpring 提供的一个 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事务
  • 各种主键生成策略对照表
策略名对应类说明适用场景
uuid2UUIDGenerator使用 Java UUID v2 生成推荐,分布式环境
guidGUIDGeneratorGUID,全局唯一标识Windows/SQL Server
uuid / uuid.hexUUIDHexGenerator旧版 UUID已废弃
assignedAssigned主键由应用代码手动赋值业务系统已有主键
identityIdentityGenerator数据库自增主键MySQL 常用
sequenceSequenceStyleGenerator数据库序列Oracle/PostgreSQL
seqhiloSequenceHiLoGeneratorhi/lo 算法生成主键大并发优化
incrementIncrementGenerator自增计数器单机测试用,不推荐生产
foreignForeignGenerator使用外键作为主键一对一关系
sequence-identitySequenceIdentityGenerator序列 + 自增特殊数据库
enhanced-sequenceSequenceStyleGenerator增强版序列替代旧 sequence
enhanced-tableTableGenerator通过表维护主键无序列支持的数据库
  • 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存储大字段(如 TEXTBLOB
@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 提供的一个 核心注解

  • 它是一个 组合注解,实际上包含了三个常用注解:

@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 程序,需要在启动类上写多个注解,比如:
@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)

一般用法很简单:

  • 在入口类上加注解
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

  • 控制扫描范围(可选)
    默认情况下,@ComponentScan 会扫描入口类所在包及其子包。
    如果项目包结构复杂,可以手动指定:

    @SpringBootApplication(scanBasePackages = "com.example.project")
    public class MyApplication { ... }
    
  • 排除某些自动配置(可选)
    如果某些自动配置和你的业务冲突,可以排除:

    @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
    public class MyApplication { ... }
    

4.总结

  • 是什么@SpringBootApplication 是 Spring Boot 的核心组合注解,包含配置、自动装配和组件扫描。

  • 为什么:让项目入口简洁,减少配置,利用自动化机制提升开发效率。

  • 怎么用:加在应用主类上,main 方法调用 SpringApplication.run(...),可选指定扫描范围和排除配置。

二. Spring Bean 相关

1. @Autowired

1.1、是什么(What)

  • @AutowiredSpring 框架提供的一个 依赖注入(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 可以省去大量配置,提升开发效率。
  • 支持多种注入方式

    • 支持 字段注入、构造器注入、Setter 方法注入,灵活方便。

1.3、怎么用(How)

常见用法有三种:

1.3.1. 字段注入(最常见,但不推荐在大项目中)
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public void doSomething() {
        userRepository.save(...);
    }
}
1.3.2. 构造器注入(推荐,最安全,利于单元测试)
@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired  // Spring 5+ 单构造器时可以省略
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
1.3.3. Setter 注入(用于可选依赖)
@Service
public class UserService {
    private NotificationService notificationService;

    @Autowired(required = false)  // 表示注入可选
    public void setNotificationService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }
}
1.3.4. 自动导入对象到类中,被注入进的类同样要被 Spring 容器管理

比如:Service 类注入到 Controller 类中。

@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
    @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)

为什么要分成四个注解?

  1. 分层语义更清晰

    • @Component:通用组件。
    • @Repository:数据库层。
    • @Service:业务逻辑层。
    • @Controller:接口层(Web 层)。
      这样别人一看就能知道类的用途。
  2. 支持 AOP 扩展

    • Spring 可以根据注解识别不同层的 Bean,应用不同的切面逻辑:

      • @Repository:提供数据访问异常的转换。
      • @Service:常用于事务管理。
      • @Controller:支持 MVC 请求处理。
  3. 更好的维护性

    • 大型项目中代码多,分层注解能让团队协作时更直观。

2.3、怎么用(How)

2.3.1. @Component(通用组件)
@Component
public class MyHelper {
    public void doWork() {
        System.out.println("Doing helper work...");
    }
}
2.3.2. @Repository(数据访问层)
@Repository
public class UserRepository {
    public User findById(Long id) {
        // 假装去数据库查询
        return new User(id, "张三");
    }
}
2.3.3. @Service(业务逻辑层)
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public User getUser(Long id) {
        return userRepository.findById(id);
    }
}
2.3.4. @Controller(表现层 / Web 层)
@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工具类、通用组件
@RepositoryDAO 层@Component异常转换数据访问层
@ServiceService 层@Component语义标识业务逻辑层
@ControllerWeb 层@Component请求处理能力接口层,接收/响应请求

2.5、总结

  • @Component:最基础的 Bean 注解。
  • @Repository、@Service、@Controller:语义化的 @Component,分别用于 DAO 层、Service 层、Controller 层,让分层更清晰,同时方便 Spring AOP 和 MVC 扩展。

3.@RestController

3.1、是什么(What)

  • @RestController 是 Spring 4 之后新增的注解。

  • 它是一个 组合注解

    @Controller
    @ResponseBody
    public @interface RestController {}
    
  • 也就是说:@RestController = @Controller + @ResponseBody

  • 作用:把一个类标记为 控制器(Controller) ,并且该类下所有方法默认都以 JSON / XML 等格式返回数据,而不是返回视图(JSP/Thymeleaf 等)。


3.2、为什么(Why)

为什么要用 @RestController

  1. 简化开发

    • 以前如果要返回 JSON,需要写:

      @Controller
      public class UserController {
          @GetMapping("/user")
          @ResponseBody
          public User getUser() { ... }
      }
      
    • 每个方法都得加 @ResponseBody,很麻烦。

    • @RestController 之后:

      @RestController
      public class UserController {
          @GetMapping("/user")
          public User getUser() { ... }
      }
      

      ✅ 默认所有方法返回 JSON,更简洁。

  2. 契合前后端分离

    • 现在的项目基本都是 前后端分离,前端需要 JSON 数据,而不是 HTML 页面。
    • @RestController 就是专门为这种 RESTful 风格的接口准备的。
  3. 和 @Controller 的区别

    • @Controller 用于传统 MVC,方法返回 视图名称(如 JSP 页面)。
    • @RestController 用于 REST API,方法返回 数据对象(Spring 会自动转成 JSON/XML)。

3.3、怎么用(How)

3.3.1. 最简单的示例
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String sayHello() {
        return "Hello, Spring Boot!";
    }
}

访问 /hello,浏览器看到的不是页面,而是字符串 "Hello, Spring Boot!"

3.3.2. 返回对象(Spring 会自动转 JSON)
@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return new User(id, "张三");
    }
}

返回结果(JSON):

{
  "id": 1,
  "name": "张三"
}
3.3.3. 搭配 @RequestBody 接收 JSON 参数
@PostMapping
public User createUser(@RequestBody User user) {
    // 假装保存用户
    return user;
}

发送 JSON 请求:

{ "id": 2, "name": "李四" }

返回结果:

{ "id": 2, "name": "李四" }

3.4、总结对比表

注解用途返回结果典型场景
@ControllerWeb 控制器视图(HTML/JSP/Thymeleaf)传统 MVC
@RestControllerREST 控制器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 会话一个 BeanWebSocket 应用场景

4.2、为什么(Why)

为什么要用 @Scope

  1. 控制 Bean 的生命周期

    • 有些对象适合全局共享(比如 Service、Repository) → 单例。
    • 有些对象必须每次都新建(比如用户输入表单对象) → 多例。
  2. 适配不同的 Web 应用场景

    • 请求级别的 Bean(request),能避免用户数据交叉污染。
    • 会话级别的 Bean(session),方便保存用户状态。
  3. 灵活管理资源

    • 单例节省内存,但不适合存储用户特有的数据。
    • 多例虽然更灵活,但会增加内存开销。
    • @Scope 让开发者根据场景选择合适的生命周期。

4.3、怎么用(How)

4.3.1. 基本用法
@Component
@Scope("prototype")
public class MyBean {
    public MyBean() {
        System.out.println("创建了一个 MyBean 实例");
    }
}
  • 每次 context.getBean(MyBean.class) 都会创建一个新的对象。
4.3.2. 在 @Bean 方法上用
@Configuration
public class AppConfig {
    @Bean
    @Scope("singleton")  // 默认值,可以省略
    public UserService userService() {
        return new UserService();
    }
}
4.3.3. Web 应用中的作用域
@Component
@Scope("request")
public class RequestScopedBean {
    // 每个 HTTP 请求都会创建新的实例
}
@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

  1. 替代 XML 配置

    • 过去我们在 Spring 项目里要写:

      <beans>
          <bean id="userService" class="com.example.UserService"/>
      </beans>
      
    • 有了 @Configuration 后,可以直接用 Java 代码声明:

      @Configuration
      public class AppConfig {
          @Bean
          public UserService userService() {
              return new UserService();
          }
      }
      
  2. 类型安全、可读性高

    • Java 配置比 XML 配置更直观,有编译器检查和 IDE 代码提示。
  3. 和 Spring Boot 无缝结合

    • Spring Boot 默认大量使用 @Configuration 类,配合 @EnableAutoConfiguration 进行自动装配。

5.3、怎么用(How)

5.3.1. 声明配置类
@Configuration
public class AppConfig {
    
    @Bean
    public UserService userService() {
        return new UserService();
    }

    @Bean
    public UserRepository userRepository() {
        return new UserRepository();
    }
}
5.3.2. 使用配置类
AnnotationConfigApplicationContext context =
        new AnnotationConfigApplicationContext(AppConfig.class);

UserService userService = context.getBean(UserService.class);
5.3.3. 单例保证(CGLIB 动态代理机制)
@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

5.4、总结

注解作用是否代理 @Bean 方法典型场景
@Component把类注册成 Bean❌ 不代理普通组件类
@Configuration声明配置类✅ 代理,保证单例配置 Bean,替代 XML
  • @Configuration 用来声明配置类,里面的方法用 @Bean 定义 Bean。

  • 它比 @Component 更特殊,会通过 CGLIB 代理来保证 @Bean 方法返回的是同一个 Bean(单例)。

三.处理常见的 HTTP 请求类型

3.1、是什么(What)

HTTP 协议定义了多种 请求方法(Method) ,常用的有 5 种:

  1. GET:从服务器获取资源。
  2. POST:向服务器提交数据,创建新资源。
  3. PUT:更新资源(整体替换)。
  4. DELETE:删除资源。
  5. PATCH:更新资源的部分字段。

它们共同构成了 RESTful API 的核心操作


3.2、为什么(Why)

为什么需要区分不同请求类型?

  1. 语义清晰

    • 不同请求方法代表不同的操作语义,前后端一看就知道接口是做什么的。
    • 例如:GET /users → 获取用户;POST /users → 新增用户。
  2. 符合 RESTful 风格

    • RESTful API 倡导 资源 + 动作分离,动作用 HTTP 方法表示,资源用 URL 表示。
    • URL 只表示资源(如 /users/12),具体操作(获取/新增/更新/删除)由方法决定。
  3. 提升可维护性

    • 如果所有操作都用 POST,接口会混乱。
    • 采用不同方法,让接口风格一致,便于团队协作和自动化文档生成。

3.3、怎么用(How)

在 Spring Boot / Spring MVC 中,分别用不同的注解来映射请求方法:

3.3.1. GET 请求

  • 用途:获取数据(安全操作,不会修改服务器数据)。
  • Spring 注解@GetMapping@RequestMapping(method=GET)
@GetMapping("/users")
public List<User> getAllUsers() {
    return userRepository.findAll();
}

3.3.2. POST 请求

  • 用途:新增资源(非幂等,每次调用都会创建新资源)。
  • Spring 注解@PostMapping
@PostMapping("/users")
public User createUser(@RequestBody User user) {
    return userRepository.save(user);
}

3.3.3. PUT 请求

  • 用途:整体更新资源(幂等,多次调用结果一样)。
  • Spring 注解@PutMapping
@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
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
    userRepository.deleteById(id);
}

3.3.5. PATCH 请求

  • 用途:部分更新资源(非幂等,通常用于更新部分字段)。
  • Spring 注解@PatchMapping
@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获取资源✅ 幂等@GetMappingGET /users
POST新建资源❌ 非幂等@PostMappingPOST /users
PUT整体更新✅ 幂等@PutMappingPUT /users/12
DELETE删除资源✅ 幂等@DeleteMappingDELETE /users/12
PATCH部分更新❌ 通常非幂等@PatchMappingPATCH /users/12
-   **是什么**: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、怎么用

@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、怎么用

@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)绑定到单个对象
@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();
    }
}

请求示例:

POST /users
Content-Type: application/json

{
  "id": 1,
  "name": "Tom",
  "age": 20
}

返回:

创建用户: Tom, 年龄: 20

(2)绑定到集合
@PostMapping("/batch")
public String createUsers(@RequestBody List<User> users) {
    return "接收了 " + users.size() + " 个用户";
}

请求体:

[  {"id":1,"name":"Tom","age":20},  {"id":2,"name":"Jerry","age":22}]

(3)和 @RequestParam / @PathVariable 混用
// 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. 注意事项 ⚠️

  1. 必须有 Content-Type: application/json,否则可能报错。
  2. 需要依赖 Jackson 或其他 JSON 库(Spring Boot 默认集成)。
  3. @RequestBody 只能从 请求体 获取数据,不能获取 URL 参数。
  4. 如果是表单提交(application/x-www-form-urlencoded),推荐用 @RequestParam
  5. 一个请求方法只可以有一个@RequestBody,但是可以有多个@RequestParam@PathVariable

4.4.5. 总结

  • @RequestBody 用来把请求体中的 JSON 数据转成 Java 对象,特别适合前后端分离的场景。

五. 读取配置信息

5.1、是什么(What)

Spring 提供多种方式读取配置文件中的信息,常用的有三种:

  1. @Value:读取单个配置属性。
  2. @ConfigurationProperties:将配置文件中的属性绑定到一个 Java Bean 上,支持嵌套对象和集合。
  3. @PropertySource:指定读取某个 properties 文件(不常用,多用于老项目)。

配置文件可以是 application.ymlapplication.properties


5.2、为什么(Why)

  • 项目中会有很多 可变参数或外部配置,比如:

    • 数据库连接
    • 阿里云 OSS 配置
    • 微信小程序/公众号配置
    • 第三方 API Key
  • 将这些信息写死在代码中不好维护,也不安全。

  • Spring 提供的注解可以:

    • 自动注入配置值到 Bean
    • 支持类型安全
    • 支持集合和嵌套对象
    • 便于不同环境切换(如 application-dev.ymlapplication-prod.yml

5.3、怎么用(How)

5.3.1. 使用 @Value(最简单)

# application.yml
wuhan2020: "2020年初武汉爆发了新型冠状病毒,疫情严重。"
@Component
public class WuhanInfo {

    @Value("${wuhan2020}")
    private String wuhan2020;

    public void printInfo() {
        System.out.println(wuhan2020);
    }
}
  • 适合读取 单个简单值
  • 支持默认值:
@Value("${wuhan2020:武汉加油}")
private String wuhan2020;

5.3.2. 使用 @ConfigurationProperties(常用,支持复杂对象)

# application.yml
library:
  location: 湖北武汉
  books:
    - name: 天才基本法
      description: 林朝夕故事
    - name: 时间的秩序
      description: 时间本质探讨
@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;
    }
}
@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(不常用)

# website.properties
url=https://www.example.com
@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.locationlibrary.books@ConfigurationProperties(prefix="library")
@PropertySource指定 properties 文件website.properties@PropertySource("classpath:website.properties")
-   **简单值**`@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

@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;
}
@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 才会校验方法参数。
@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. 异常统一处理(可选)

@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
-   **参数校验**保证后端数据安全和完整性;

-   **JSR 注解 + Spring Boot** 能轻松校验请求体和请求参数;

-   `@Valid` 用于对象,`@Validated` 用于方法参数。

七. 全局处理 Controller 层异常

7.1、是什么

在 Spring MVC 项目中,当 Controller 方法抛出异常时,如果没有统一处理,异常会直接冒泡到前端,返回的是复杂的堆栈信息(不友好,也不安全)。

Spring 提供了 全局异常处理机制

  • @ControllerAdvice:标记一个类为全局异常处理类,可以捕获所有 Controller 层的异常。
  • @ExceptionHandler:在 @ControllerAdvice 标注的类里,声明具体的异常处理方法。

7.2、为什么要用

主要解决几个问题:

  1. 统一管理异常:不用在每个 Controller 方法里写 try-catch。
  2. 返回统一格式:比如所有异常返回 {code, message, data} 的 JSON。
  3. 增强可维护性:异常逻辑和业务逻辑解耦。
  4. 安全性:避免系统堆栈直接暴露给前端。

7.3、怎么用

7.3.1. 示例代码

// 定义统一的响应结构
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
    private Integer code;  // 状态码
    private String message; // 提示信息
    private T data;        // 数据
}
// 全局异常处理类
@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:
{
  "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. 怎么用

@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 种策略:

public enum GenerationType {
    TABLE,      // 通过一张表维护主键
    SEQUENCE,   // 使用数据库序列 (Oracle, PostgreSQL)
    IDENTITY,   // 主键自增 (MySQL 常用)
    AUTO        // JPA 自动选择 (默认)
}

示例:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

自定义主键策略:

@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;

8.2.4. jpa 提供的主键生成策略有如下几种:

/**
 * 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());
        }
    }
}
  • 各种主键生成策略对照表
策略名对应类说明适用场景
uuid2UUIDGenerator使用 Java UUID v2 生成推荐,分布式环境
guidGUIDGeneratorGUID,全局唯一标识Windows/SQL Server
uuid / uuid.hexUUIDHexGenerator旧版 UUID已废弃
assignedAssigned主键由应用代码手动赋值业务系统已有主键
identityIdentityGenerator数据库自增主键MySQL 常用
sequenceSequenceStyleGenerator数据库序列Oracle/PostgreSQL
seqhiloSequenceHiLoGeneratorhi/lo 算法生成主键大并发优化
incrementIncrementGenerator自增计数器单机测试用,不推荐生产
foreignForeignGenerator使用外键作为主键一对一关系
sequence-identitySequenceIdentityGenerator序列 + 自增特殊数据库
enhanced-sequenceSequenceStyleGenerator增强版序列替代旧 sequence
enhanced-tableTableGenerator通过表维护主键无序列支持的数据库

8.3、设置字段类型

8.3.1. 是什么

  • @Column:声明数据库列属性(名称、长度、非空、默认值等)。

8.3.2. 为什么

  • 可以精确控制数据库表字段的属性。

8.3.3. 怎么用

@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. 怎么用

@Transient
private String secrect;

8.5、声明大字段

8.5.1. 是什么

  • @Lob:声明大字段(如 TEXT, BLOB)。

8.5.2. 为什么

  • 存储大文本(文章内容)或二进制数据(图片、文件)。

8.5.3. 怎么用

@Lob
@Basic(fetch = FetchType.EAGER)
@Column(name = "content", columnDefinition = "LONGTEXT NOT NULL")
private String content;

8.6、创建枚举字段

8.6.1. 是什么

  • @Enumerated:声明字段是枚举类型。

8.6.2. 为什么

  • 可以把枚举保存为 intString,可读性更好。

8.6.3. 怎么用

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;
}

数据库保存结果:MALEFEMALE


8.7、审计功能(自动维护创建时间/修改时间)

8.7.1. 是什么

  • @CreatedDate@LastModifiedDate:自动填充创建时间和修改时间。
  • @CreatedBy@LastModifiedBy:自动填充创建人和修改人。
  • @EnableJpaAuditing:开启 JPA 审计功能。

8.7.2. 为什么

  • 开发者无需手动维护时间戳和用户,避免出错。

8.7.3. 怎么用

抽象基类:

@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;
}

配置类:

@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. 怎么用

@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
@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 用在一方,表示由多方维护外键。
@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)
  • 说明:两个实体之间多对多关系,通常通过 中间关联表 维护。
@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 保存学生和课程的对应关系。
    • 删除 StudentCourse 时,关联表的数据会同步删除,但默认不会删除对方实体。
    • cascadeorphanRemoval 可以控制级联操作。

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存储大字段(如 TEXTBLOB
@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、为什么需要事务

在数据库操作中,经常存在 多步操作,例如:

  1. 保存用户信息。
  2. 保存用户权限。
  3. 保存操作日志。

如果中间某步失败而前面已执行成功,会造成 数据不一致
事务能保证:

  • 原子性:所有操作要么全部成功,要么全部回滚。
  • 一致性:事务执行前后数据保持一致。
  • 隔离性:并发事务之间互不干扰。
  • 持久性:事务提交后,数据永久存储。

总结:事务是保证数据库数据可靠性和一致性的核心机制。


9.3、怎么用

(1) 方法级事务

在单个方法上声明事务:

@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 方法都会自动开启事务:

@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) 注意事项

  1. 事务只对 public 方法有效

    • 因为 Spring 事务是通过 AOP 代理实现的,private/protected 方法不会被代理拦截。
  2. 内部方法调用导致事务失效

    • 同一个类内部调用方法(this.method()),事务不会生效,因为是绕过了代理对象。
    • 解决方案:通过 自调用注入自身 Bean 或将方法拆到不同类。
  3. 回滚策略

    • 默认:只有 RuntimeExceptionError 回滚。
    • 自定义:rollbackFor = Exception.class 让非运行时异常也回滚。
    • noRollbackFor = CustomException.class 让指定异常不回滚。
  4. 事务传播行为示例

@Transactional
public void outer() {
    inner(); // 默认传播行为 REQUIRED,内层方法会加入外层事务
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
    // 无论外层事务如何,inner 方法总是新建事务
}
  1. 事务与 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 提供两个主要注解:

  1. @JsonIgnoreProperties:类级别,忽略指定字段。
  2. @JsonIgnore:字段级别,忽略该字段。

10.1.2. 为什么

  1. 保护敏感信息:如密码、权限列表不想暴露给前端。
  2. 减少数据冗余:有些字段对前端无用,省流量。
  3. 避免循环引用:对象之间相互引用时,过滤字段可防止 JSON 序列化报错。

10.1.3. 怎么用

1️⃣ @JsonIgnoreProperties(类级别)

// 忽略 userRoles 字段
@JsonIgnoreProperties({"userRoles"})
public class User {
    private String userName;
    private String fullName;
    private String password;
    private List<UserRole> userRoles = new ArrayList<>();
}
  • 放在类上,数组中写要忽略的字段名。
  • 同时适用于序列化和反序列化。

2️⃣ @JsonIgnore(字段级别)

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. 为什么

  1. 保证前后端统一:前端接收到的日期格式固定,避免解析错误。
  2. 满足业务需求:有些接口需要特定时间格式,如 yyyy-MM-dd 或 ISO8601。

10.2.3. 怎么用

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:时区。

序列化效果:

{
  "eventTime": "2025-09-01T10:00:00.000Z"
}

10.3、扁平化对象

10.3.1. 是什么

扁平化对象是将 嵌套的对象字段“拉平” 到 JSON 的顶层,而不是保留嵌套结构。
使用注解:@JsonUnwrapped


10.3.2. 为什么

  1. 简化前端处理:前端不必访问嵌套对象字段。
  2. 接口风格统一:REST API 输出可以统一扁平化字段。
  3. 减少冗余嵌套:JSON 更清晰,数据结构更直观。

10.3.3. 怎么用

@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

{
  "location": {
    "provinceName":"湖北",
    "countyName":"武汉"
  },
  "personInfo": {
    "userName": "coder1234",
    "fullName": "shaungkou"
  }
}

扁平化 JSON(使用 @JsonUnwrapped)

{
  "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. 为什么

  1. 测试与开发环境配置不同,比如数据库、缓存等连接信息。
  2. 避免测试影响正式配置,保证测试隔离。
  3. 可以针对不同环境运行不同的测试数据或逻辑。

11.1.3. 怎么用

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")  // 使用 application-test.yml 或 application-test.properties
@Slf4j
public abstract class TestBase {
    // 公共测试配置
}
  • "test" 对应 application-test.ymlapplication-test.properties
  • 支持多 profile:@ActiveProfiles({"test", "dev"})

11.2、@Test

11.2.1. 是什么

  • JUnit 提供的注解,用于声明一个方法为 测试方法
  • 测试框架会自动识别并运行标注了 @Test 的方法。

11.2.2. 为什么

  • 明确标记测试入口,框架自动执行。
  • 让测试方法与普通方法区分开,便于测试管理。

11.2.3. 怎么用

@Test
void should_import_student_success() {
    // 测试逻辑
}
  • 方法可以抛异常,也可以包含断言(Assertions.assertEquals 等)。

11.3、@Transactional(测试方法)

11.3.1. 是什么

  • Spring 提供的事务管理注解,声明在测试方法上时,方法执行完毕后会回滚事务

11.3.2. 为什么

  1. 避免污染测试数据:测试中插入、修改的数据库记录不会影响正式数据。
  2. 保证每次测试独立:每次运行方法都是干净的数据状态。

11.3.3. 怎么用

@Test
@Transactional
void should_save_user_success() {
    // 测试数据库操作,执行完自动回滚
}

💡 注意

  • 默认只在使用 Spring 测试上下文时生效(@SpringBootTest)。
  • 回滚可以通过 @Rollback(false) 禁用。

11.4、@WithMockUser(Spring Security)

11.4.1. 是什么

  • Spring Security 提供的测试注解,用于 模拟一个登录用户,并可赋予角色或权限。

11.4.2. 为什么

  1. 测试安全控制逻辑,比如接口权限校验。
  2. 避免每次测试都实际登录,方便自动化测试。

11.4.3. 怎么用

@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模拟登录用户及角色权限测试安全接口、权限校验