名称: springboot-patterns 描述: Spring Boot模式包括JPA仓库、REST控制器、分层服务和配置
Spring Boot模式
分层架构
src/main/java/com/example/app/
config/ # @Configuration beans(配置Bean)
controller/ # @RestController(薄层,委托给服务)
service/ # @Service(业务逻辑)
repository/ # @Repository(通过JPA进行数据访问)
model/
entity/ # @Entity JPA类
dto/ # 请求/响应DTO
mapper/ # MapStruct或手动映射
exception/ # @ControllerAdvice,自定义异常
security/ # SecurityFilterChain,JWT过滤器
控制器处理HTTP相关事宜。服务包含业务逻辑。仓库处理持久化。
REST控制器
@RestController
@RequestMapping("/api/v1/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@GetMapping
public Page<OrderResponse> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return orderService.findAll(PageRequest.of(page, size));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public OrderResponse create(@Valid @RequestBody CreateOrderRequest request) {
return orderService.create(request);
}
@GetMapping("/{id}")
public OrderResponse getById(@PathVariable UUID id) {
return orderService.findById(id);
}
}
JPA实体和仓库
@Entity
@Table(name = "orders")
@Getter @Setter @NoArgsConstructor
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id", nullable = false)
private Customer customer;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
@Enumerated(EnumType.STRING)
private OrderStatus status = OrderStatus.PENDING;
@CreationTimestamp
private Instant createdAt;
}
public interface OrderRepository extends JpaRepository<Order, UUID> {
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.customer.id = :customerId")
List<Order> findByCustomerWithItems(@Param("customerId") UUID customerId);
@EntityGraph(attributePaths = {"customer", "items"})
Optional<Order> findWithDetailsById(UUID id);
}
服务层
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final OrderMapper orderMapper;
private final EventPublisher eventPublisher;
public OrderResponse findById(UUID id) {
Order order = orderRepository.findWithDetailsById(id)
.orElseThrow(() -> new ResourceNotFoundException("Order", id));
return orderMapper.toResponse(order);
}
@Transactional
public OrderResponse create(CreateOrderRequest request) {
Order order = orderMapper.toEntity(request);
order = orderRepository.save(order);
eventPublisher.publish(new OrderCreatedEvent(order.getId()));
return orderMapper.toResponse(order);
}
}
全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ProblemDetail handleNotFound(ResourceNotFoundException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
ProblemDetail detail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
Map<String, String> errors = ex.getFieldErrors().stream()
.collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
detail.setProperty("errors", errors);
return detail;
}
}
反模式
- 直接将仓库注入控制器(绕过服务层)
- 默认在实体关系上使用
FetchType.EAGER - 从控制器直接返回JPA实体而不是DTO
- 在只读服务方法上缺少
@Transactional(readOnly = true) - 捕获通用
Exception而不是特定类型 - 硬编码配置值而不是使用
@Value或@ConfigurationProperties
检查清单
- [ ] 控制器是薄层并委托给服务
- [ ] 所有JPA关系默认使用
FetchType.LAZY - [ ] 请求/响应使用DTO,从不使用原始实体
- [ ] 在服务级别应用
@Transactional,具有正确的读/写范围 - [ ] 请求DTO上有验证注解(
@Valid、@NotNull、@Size) - [ ] 全局异常处理返回
ProblemDetail(RFC 7807) - [ ] 使用实体图或
JOIN FETCH避免N+1查询 - [ ] 集成测试使用
@SpringBootTest与测试容器