spring-boot-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Spring Boot Patterns Skill

Spring Boot模式技能

Best practices and patterns for Spring Boot applications.
Spring Boot应用的最佳实践与设计模式。

When to Use

适用场景

  • User says "create controller" / "add service" / "Spring Boot help"
  • Reviewing Spring Boot code
  • Setting up new Spring Boot project structure
  • 用户提到「创建控制器」/「添加服务」/「Spring Boot相关帮助」
  • 审查Spring Boot代码
  • 搭建新的Spring Boot项目结构

Project Structure

项目结构

src/main/java/com/example/myapp/
├── MyAppApplication.java          # @SpringBootApplication
├── config/                        # Configuration classes
│   ├── SecurityConfig.java
│   └── WebConfig.java
├── controller/                    # REST controllers
│   └── UserController.java
├── service/                       # Business logic
│   ├── UserService.java
│   └── impl/
│       └── UserServiceImpl.java
├── repository/                    # Data access
│   └── UserRepository.java
├── model/                         # Entities
│   └── User.java
├── dto/                           # Data transfer objects
│   ├── request/
│   │   └── CreateUserRequest.java
│   └── response/
│       └── UserResponse.java
├── exception/                     # Custom exceptions
│   ├── ResourceNotFoundException.java
│   └── GlobalExceptionHandler.java
└── util/                          # Utilities
    └── DateUtils.java

src/main/java/com/example/myapp/
├── MyAppApplication.java          # @SpringBootApplication
├── config/                        # 配置类
│   ├── SecurityConfig.java
│   └── WebConfig.java
├── controller/                    # REST控制器
│   └── UserController.java
├── service/                       # 业务逻辑
│   ├── UserService.java
│   └── impl/
│       └── UserServiceImpl.java
├── repository/                    # 数据访问层
│   └── UserRepository.java
├── model/                         # 实体类
│   └── User.java
├── dto/                           # 数据传输对象
│   ├── request/
│   │   └── CreateUserRequest.java
│   └── response/
│       └── UserResponse.java
├── exception/                     # 自定义异常
│   ├── ResourceNotFoundException.java
│   └── GlobalExceptionHandler.java
└── util/                          # 工具类
    └── DateUtils.java

Controller Patterns

控制器模式

REST Controller Template

REST控制器模板

java
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor  // Lombok for constructor injection
public class UserController {

    private final UserService userService;

    @GetMapping
    public ResponseEntity<List<UserResponse>> getAll() {
        return ResponseEntity.ok(userService.findAll());
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }

    @PostMapping
    public ResponseEntity<UserResponse> create(
            @Valid @RequestBody CreateUserRequest request) {
        UserResponse created = userService.create(request);
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(created.getId())
            .toUri();
        return ResponseEntity.created(location).body(created);
    }

    @PutMapping("/{id}")
    public ResponseEntity<UserResponse> update(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.update(id, request));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}
java
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor  // Lombok实现构造器注入
public class UserController {

    private final UserService userService;

    @GetMapping
    public ResponseEntity<List<UserResponse>> getAll() {
        return ResponseEntity.ok(userService.findAll());
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }

    @PostMapping
    public ResponseEntity<UserResponse> create(
            @Valid @RequestBody CreateUserRequest request) {
        UserResponse created = userService.create(request);
        URI location = ServletUriComponentsBuilder.fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(created.getId())
            .toUri();
        return ResponseEntity.created(location).body(created);
    }

    @PutMapping("/{id}")
    public ResponseEntity<UserResponse> update(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.update(id, request));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

Controller Best Practices

控制器最佳实践

PracticeExample
Versioned API
/api/v1/users
Plural nouns
/users
not
/user
HTTP methodsGET=read, POST=create, PUT=update, DELETE=delete
Status codes200=OK, 201=Created, 204=NoContent, 404=NotFound
Validation
@Valid
on request body
实践规范示例
API版本化
/api/v1/users
使用复数名词
/users
而非
/user
正确使用HTTP方法GET=查询, POST=创建, PUT=更新, DELETE=删除
正确使用状态码200=成功, 201=已创建, 204=无内容, 404=未找到
参数校验请求体添加
@Valid
注解

❌ Anti-patterns

❌ 反模式

java
// ❌ Business logic in controller
@PostMapping
public User create(@RequestBody User user) {
    user.setCreatedAt(LocalDateTime.now());  // Logic belongs in service
    return userRepository.save(user);         // Direct repo access
}

// ❌ Returning entity directly (exposes internals)
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
    return userRepository.findById(id).get();
}

java
// ❌ 控制器中编写业务逻辑
@PostMapping
public User create(@RequestBody User user) {
    user.setCreatedAt(LocalDateTime.now());  // 业务逻辑应放在service层
    return userRepository.save(user);         // 禁止直接调用repository
}

// ❌ 直接返回实体类(暴露内部结构)
@GetMapping("/{id}")
public User getById(@PathVariable Long id) {
    return userRepository.findById(id).get();
}

Service Patterns

服务层模式

Service Interface + Implementation

服务接口+实现类

java
// Interface
public interface UserService {
    List<UserResponse> findAll();
    UserResponse findById(Long id);
    UserResponse create(CreateUserRequest request);
    UserResponse update(Long id, UpdateUserRequest request);
    void delete(Long id);
}

// Implementation
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)  // Default read-only
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;
    private final UserMapper userMapper;

    @Override
    public List<UserResponse> findAll() {
        return userRepository.findAll().stream()
            .map(userMapper::toResponse)
            .toList();
    }

    @Override
    public UserResponse findById(Long id) {
        return userRepository.findById(id)
            .map(userMapper::toResponse)
            .orElseThrow(() -> new ResourceNotFoundException("User", id));
    }

    @Override
    @Transactional  // Write transaction
    public UserResponse create(CreateUserRequest request) {
        User user = userMapper.toEntity(request);
        User saved = userRepository.save(user);
        return userMapper.toResponse(saved);
    }

    @Override
    @Transactional
    public void delete(Long id) {
        if (!userRepository.existsById(id)) {
            throw new ResourceNotFoundException("User", id);
        }
        userRepository.deleteById(id);
    }
}
java
// 接口
public interface UserService {
    List<UserResponse> findAll();
    UserResponse findById(Long id);
    UserResponse create(CreateUserRequest request);
    UserResponse update(Long id, UpdateUserRequest request);
    void delete(Long id);
}

// 实现类
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)  // 默认只读事务
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;
    private final UserMapper userMapper;

    @Override
    public List<UserResponse> findAll() {
        return userRepository.findAll().stream()
            .map(userMapper::toResponse)
            .toList();
    }

    @Override
    public UserResponse findById(Long id) {
        return userRepository.findById(id)
            .map(userMapper::toResponse)
            .orElseThrow(() -> new ResourceNotFoundException("User", id));
    }

    @Override
    @Transactional  // 写操作事务
    public UserResponse create(CreateUserRequest request) {
        User user = userMapper.toEntity(request);
        User saved = userRepository.save(user);
        return userMapper.toResponse(saved);
    }

    @Override
    @Transactional
    public void delete(Long id) {
        if (!userRepository.existsById(id)) {
            throw new ResourceNotFoundException("User", id);
        }
        userRepository.deleteById(id);
    }
}

Service Best Practices

服务层最佳实践

  • Interface + Impl for testability
  • @Transactional(readOnly = true)
    at class level
  • @Transactional
    for write methods
  • Throw domain exceptions, not generic ones
  • Use mappers (MapStruct) for entity ↔ DTO conversion

  • 采用接口+实现类结构提升可测试性
  • 类级别添加
    @Transactional(readOnly = true)
    注解
  • 写方法单独添加
    @Transactional
    注解
  • 抛出领域特定异常而非通用异常
  • 使用映射工具(MapStruct)实现实体类与DTO的转换

Repository Patterns

数据访问层模式

JPA Repository

JPA Repository

java
public interface UserRepository extends JpaRepository<User, Long> {

    // Derived query
    Optional<User> findByEmail(String email);

    List<User> findByActiveTrue();

    // Custom query
    @Query("SELECT u FROM User u WHERE u.department.id = :deptId")
    List<User> findByDepartmentId(@Param("deptId") Long departmentId);

    // Native query (use sparingly)
    @Query(value = "SELECT * FROM users WHERE created_at > :date",
           nativeQuery = true)
    List<User> findRecentUsers(@Param("date") LocalDate date);

    // Exists check (more efficient than findBy)
    boolean existsByEmail(String email);

    // Count
    long countByActiveTrue();
}
java
public interface UserRepository extends JpaRepository<User, Long> {

    // 派生查询
    Optional<User> findByEmail(String email);

    List<User> findByActiveTrue();

    // 自定义查询
    @Query("SELECT u FROM User u WHERE u.department.id = :deptId")
    List<User> findByDepartmentId(@Param("deptId") Long departmentId);

    // 原生查询(谨慎使用)
    @Query(value = "SELECT * FROM users WHERE created_at > :date",
           nativeQuery = true)
    List<User> findRecentUsers(@Param("date") LocalDate date);

    // 存在性校验(比findBy效率更高)
    boolean existsByEmail(String email);

    // 计数
    long countByActiveTrue();
}

Repository Best Practices

数据访问层最佳实践

  • Use derived queries when possible
  • Optional
    for single results
  • existsBy
    instead of
    findBy
    for existence checks
  • Avoid native queries unless necessary
  • Use
    @EntityGraph
    for fetch optimization

  • 优先使用派生查询
  • 单条查询结果使用
    Optional
    包裹
  • 存在性校验使用
    existsBy
    而非
    findBy
  • 非必要不使用原生查询
  • 使用
    @EntityGraph
    优化关联查询

DTO Patterns

DTO模式

Request/Response DTOs

请求/响应DTO

java
// Request DTO with validation
public record CreateUserRequest(
    @NotBlank(message = "Name is required")
    @Size(min = 2, max = 100)
    String name,

    @NotBlank
    @Email(message = "Invalid email format")
    String email,

    @NotNull
    @Min(18)
    Integer age
) {}

// Response DTO
public record UserResponse(
    Long id,
    String name,
    String email,
    LocalDateTime createdAt
) {}
java
// 带校验的请求DTO
public record CreateUserRequest(
    @NotBlank(message = "姓名不能为空")
    @Size(min = 2, max = 100)
    String name,

    @NotBlank
    @Email(message = "邮箱格式无效")
    String email,

    @NotNull
    @Min(18)
    Integer age
) {}

// 响应DTO
public record UserResponse(
    Long id,
    String name,
    String email,
    LocalDateTime createdAt
) {}

MapStruct Mapper

MapStruct映射器

java
@Mapper(componentModel = "spring")
public interface UserMapper {

    UserResponse toResponse(User entity);

    List<UserResponse> toResponseList(List<User> entities);

    @Mapping(target = "id", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    User toEntity(CreateUserRequest request);
}

java
@Mapper(componentModel = "spring")
public interface UserMapper {

    UserResponse toResponse(User entity);

    List<UserResponse> toResponseList(List<User> entities);

    @Mapping(target = "id", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    User toEntity(CreateUserRequest request);
}

Exception Handling

异常处理

Custom Exceptions

自定义异常

java
public class ResourceNotFoundException extends RuntimeException {

    public ResourceNotFoundException(String resource, Long id) {
        super(String.format("%s not found with id: %d", resource, id));
    }
}

public class BusinessException extends RuntimeException {

    private final String code;

    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }
}
java
public class ResourceNotFoundException extends RuntimeException {

    public ResourceNotFoundException(String resource, Long id) {
        super(String.format("%s不存在,id为:%d", resource, id));
    }
}

public class BusinessException extends RuntimeException {

    private final String code;

    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }
}

Global Exception Handler

全局异常处理器

java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        log.warn("Resource not found: {}", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse("NOT_FOUND", ex.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(
            MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult().getFieldErrors().stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .toList();
        return ResponseEntity.badRequest()
            .body(new ErrorResponse("VALIDATION_ERROR", errors.toString()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
        log.error("Unexpected error", ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse("INTERNAL_ERROR", "An unexpected error occurred"));
    }
}

public record ErrorResponse(String code, String message) {}

java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
        log.warn("资源未找到:{}", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse("NOT_FOUND", ex.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(
            MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult().getFieldErrors().stream()
            .map(e -> e.getField() + ": " + e.getDefaultMessage())
            .toList();
        return ResponseEntity.badRequest()
            .body(new ErrorResponse("VALIDATION_ERROR", errors.toString()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
        log.error("非预期错误", ex);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse("INTERNAL_ERROR", "发生了非预期错误"));
    }
}

public record ErrorResponse(String code, String message) {}

Configuration Patterns

配置模式

Application Properties

应用配置文件

yaml
undefined
yaml
undefined

application.yml

application.yml

spring: datasource: url: jdbc:postgresql://localhost:5432/mydb username: ${DB_USER} password: ${DB_PASSWORD} jpa: hibernate: ddl-auto: validate # Never 'create' in production! show-sql: false
app: jwt: secret: ${JWT_SECRET} expiration: 86400000
undefined
spring: datasource: url: jdbc:postgresql://localhost:5432/mydb username: ${DB_USER} password: ${DB_PASSWORD} jpa: hibernate: ddl-auto: validate # 生产环境禁止使用'create'! show-sql: false
app: jwt: secret: ${JWT_SECRET} expiration: 86400000
undefined

Configuration Properties Class

配置属性类

java
@Configuration
@ConfigurationProperties(prefix = "app.jwt")
@Validated
public class JwtProperties {

    @NotBlank
    private String secret;

    @Min(60000)
    private long expiration;

    // getters and setters
}
java
@Configuration
@ConfigurationProperties(prefix = "app.jwt")
@Validated
public class JwtProperties {

    @NotBlank
    private String secret;

    @Min(60000)
    private long expiration;

    // getters和setters
}

Profile-Specific Configuration

环境专属配置

src/main/resources/
├── application.yml           # Common config
├── application-dev.yml       # Development
├── application-test.yml      # Testing
└── application-prod.yml      # Production

src/main/resources/
├── application.yml           # 通用配置
├── application-dev.yml       # 开发环境
├── application-test.yml      # 测试环境
└── application-prod.yml      # 生产环境

Common Annotations Quick Reference

常用注解速查

AnnotationPurpose
@RestController
REST controller (combines @Controller + @ResponseBody)
@Service
Business logic component
@Repository
Data access component
@Configuration
Configuration class
@RequiredArgsConstructor
Lombok: constructor injection
@Transactional
Transaction management
@Valid
Trigger validation
@ConfigurationProperties
Bind properties to class
@Profile("dev")
Profile-specific bean
@Scheduled
Scheduled tasks

注解用途
@RestController
REST控制器(组合了@Controller + @ResponseBody)
@Service
业务逻辑组件
@Repository
数据访问组件
@Configuration
配置类
@RequiredArgsConstructor
Lombok注解:生成构造器实现依赖注入
@Transactional
事务管理
@Valid
触发参数校验
@ConfigurationProperties
将配置绑定到类
@Profile("dev")
环境专属Bean
@Scheduled
定时任务

Testing Patterns

测试模式

Controller Test (MockMvc)

控制器测试(MockMvc)

java
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldReturnUser() throws Exception {
        when(userService.findById(1L))
            .thenReturn(new UserResponse(1L, "John", "john@example.com", null));

        mockMvc.perform(get("/api/v1/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John"));
    }
}
java
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldReturnUser() throws Exception {
        when(userService.findById(1L))
            .thenReturn(new UserResponse(1L, "John", "john@example.com", null));

        mockMvc.perform(get("/api/v1/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John"));
    }
}

Service Test

服务层测试

java
@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private UserMapper userMapper;

    @InjectMocks
    private UserServiceImpl userService;

    @Test
    void shouldThrowWhenUserNotFound() {
        when(userRepository.findById(1L)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> userService.findById(1L))
            .isInstanceOf(ResourceNotFoundException.class);
    }
}
java
@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private UserMapper userMapper;

    @InjectMocks
    private UserServiceImpl userService;

    @Test
    void shouldThrowWhenUserNotFound() {
        when(userRepository.findById(1L)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> userService.findById(1L))
            .isInstanceOf(ResourceNotFoundException.class);
    }
}

Integration Test

集成测试

java
@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
class UserIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldCreateUser() throws Exception {
        mockMvc.perform(post("/api/v1/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {"name": "John", "email": "john@example.com", "age": 25}
                    """))
            .andExpect(status().isCreated());
    }
}

java
@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
class UserIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldCreateUser() throws Exception {
        mockMvc.perform(post("/api/v1/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {"name": "John", "email": "john@example.com", "age": 25}
                    """))
            .andExpect(status().isCreated());
    }
}

Quick Reference Card

快速参考卡

LayerResponsibilityAnnotations
ControllerHTTP handling, validation
@RestController
,
@Valid
ServiceBusiness logic, transactions
@Service
,
@Transactional
RepositoryData access
@Repository
, extends
JpaRepository
DTOData transferRecords with validation annotations
ConfigConfiguration
@Configuration
,
@ConfigurationProperties
ExceptionError handling
@RestControllerAdvice
层级职责常用注解
控制器HTTP请求处理、参数校验
@RestController
,
@Valid
服务层业务逻辑、事务管理
@Service
,
@Transactional
数据访问层数据操作
@Repository
, 继承
JpaRepository
DTO数据传输带校验注解的Record类
配置层配置管理
@Configuration
,
@ConfigurationProperties
异常处理错误统一处理
@RestControllerAdvice