Spring Boot 开发规范:拒绝代码沼泽,打造可维护的优雅项目
用过 Spring Boot 的开发者大概都有过这样的体验:项目初期代码简洁优雅,迭代几个版本、新增几个功能后,逐渐变得混乱不堪——Controller 臃肿不堪、Service 职责混乱、数据库访问随处可见,性能也跟着一落千丈,最终沦为难以维护的“代码沼泽”。
其实 Spring Boot 本身足够强大,问题从来不在框架,而在我们的开发方式。今天就来梳理一套实用的 Spring Boot 开发规范,避开常见反模式,让项目长期保持清晰架构、良好性能和可扩展性。
一、为什么会陷入“代码沼泽”?
很多团队在项目初期,为了追求开发速度,往往会忽略架构设计,陷入这样的误区:
-
为了快速交付功能,直接在 Controller 中写所有逻辑;
-
Repository 层接口被到处调用,数据访问逻辑分散;
-
没有统一的分层架构,代码想到哪写到哪。
短期来看,这种方式确实能加快开发进度,但长期来看,代价惨重:代码可维护性急剧下降,新增功能要到处改代码;性能出现瓶颈,排查问题无从下手;代码难以单元测试,迭代风险越来越高;架构逐渐腐化,最后只能推倒重构。
二、Spring Boot 推荐架构:分层清晰,职责单一
一个健康的 Spring Boot 项目,核心是“分层架构”,每一层都有明确的职责,互不越界。推荐的项目目录结构如下(以 com.goldpac.icoderoad 包为例):
/src/main/java/com/goldpac/icoderoad
├── controller # 处理HTTP请求,接收参数、返回响应
├── service # 核心业务逻辑层,处理业务规则
├── repository # 数据访问层,封装数据库操作
├── domain # 实体类,对应数据库表结构
├── dto # 数据传输对象,用于前后端数据交互
├── config # 配置类,全局配置、Bean注册等
├── exception # 异常相关,全局异常处理、自定义异常
└── util # 工具类,通用工具方法封装
记住一个核心原则:每一层只做自己的事,不跨层调用、不越权处理逻辑。
三、10个常见反模式及解决方案(附代码示例)
下面梳理项目中最常出现的10个反模式,每个都附上问题表现、分析和规范的代码示例,直接套用即可。
反模式1:Controller 写满业务逻辑
问题表现:Controller 既处理 HTTP 请求,又做参数校验、业务判断、数据库访问,集多种职责于一身。
@PostMapping("/users")
public ResponseEntity<?> create(@RequestBody UserDTO dto) {
// 违规:Controller 直接做参数校验、数据库操作
if (dto.getAge() < 18) return ResponseEntity.badRequest().body("用户必须年满18周岁");
User user = new User();
user.setName(dto.getName());
user.setAge(dto.getAge());
userRepository.save(user); // 直接调用Repository,跨层违规
return ResponseEntity.ok("用户创建成功");
}
问题分析:违反单一职责原则,Controller 变得越来越胖,难以单元测试,业务逻辑分散,后续修改一处逻辑要动多个地方。
解决方案:Controller 只负责 HTTP 层的逻辑(接收请求、返回响应),所有业务逻辑移至 Service 层。
规范示例:
// Controller 层(仅处理HTTP请求,不包含任何业务逻辑)
package com.icoderoad.controller;
import com.icoderoad.dto.UserDTO;
import com.icoderoad.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class UserController {
// 构造函数注入Service(遵循反模式3规范)
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity<String> create(@RequestBody UserDTO dto) {
// 仅接收请求,调用Service处理业务,返回响应
userService.createUser(dto);
return ResponseEntity.ok("用户创建成功");
}
}
// Service 层(仅处理业务逻辑,通过Repository访问数据)
package com.icoderoad.service;
import com.icoderoad.dto.UserDTO;
import com.icoderoad.domain.User;
import com.icoderoad.repository.UserRepository;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 构造函数注入Repository(遵循反模式3规范)
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 专注业务逻辑:参数校验、实体转换、调用Repository
public void createUser(UserDTO dto) {
// 业务规则校验(原Controller中的违规逻辑移至此)
if (dto.getAge() < 18) {
throw new IllegalArgumentException("用户必须年满18周岁");
}
// 实体转换(DTO转Entity,避免直接暴露实体)
User user = new User();
user.setName(dto.getName());
user.setAge(dto.getAge());
// 调用Repository访问数据库(不直接操作SQL)
userRepository.save(user);
}
}
反模式2:Service 直接写 SQL
问题表现:在 Service 层直接使用 JdbcTemplate 写 SQL,数据访问逻辑和业务逻辑混在一起。
@Service
public class UserService {
// 违规:Service层直接使用JdbcTemplate写SQL,数据访问与业务逻辑混合
@Autowired
private JdbcTemplate jdbcTemplate;
public List<UserDTO> listUsers() {
// SQL硬编码在Service中,难以维护和复用
String sql = "SELECT id, name, age FROM users WHERE age >= 18";
return jdbcTemplate.query(sql, (rs, rowNum) -> {
UserDTO dto = new UserDTO();
dto.setName(rs.getString("name"));
dto.setAge(rs.getInt("age"));
return dto;
});
}
}
问题分析:SQL 语句分散在 Service 层,难以维护和复用;Service 层职责混乱,既要处理业务,又要关心数据库访问细节。
解决方案:使用 Repository 层专门封装数据访问逻辑,Service 层通过调用 Repository 接口获取数据,不直接操作 SQL。
规范示例:
// Repository 层(仅负责数据访问,封装所有数据库操作)
package com.icoderoad.repository;
import com.icoderoad.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
// 继承JpaRepository,自带CRUD方法,无需手动写基础SQL
public interface UserRepository extends JpaRepository<User, Long> {
// 如需自定义查询,在Repository层统一封装(而非Service层)
@Query("SELECT u FROM User u WHERE u.age >= :age")
List<User> findByAgeGreaterThanEqual(@Param("age") Integer age);
}
// 对应Service层规范写法(仅处理业务,不操作SQL)
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<UserDTO> listUsers() {
// 业务逻辑:仅定义规则,调用Repository获取数据
List<User> users = userRepository.findByAgeGreaterThanEqual(18);
// 实体转DTO(遵循反模式4规范)
return users.stream()
.map(u -> new UserDTO(u.getName(), u.getAge()))
.toList();
}
}
反模式3:过度使用 @Autowired
问题表现:使用 @Autowired 注解隐式注入依赖,代码中随处可见。
// 违规:过度使用@Autowired隐式注入,依赖关系不明确
@RestController
@RequestMapping("/users")
public class UserController {
// 隐式注入,无构造函数,不利于测试和维护
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository; // 违规:Controller直接依赖Repository
@GetMapping
public List<UserDTO> list() {
return userService.listUsers();
}
}
问题分析:隐式依赖不利于单元测试(无法手动注入mock对象);不符合不可变原则,依赖对象可能被篡改;代码可读性差,难以快速找到依赖关系。
解决方案:使用构造函数注入,明确依赖关系,同时保证依赖对象不可变。
规范示例:
// 规范:构造函数注入,明确依赖关系,保证依赖不可变
@RestController
@RequestMapping("/users")
public class UserController {
// final修饰,保证依赖对象不可篡改
private final UserService userService;
// 构造函数注入(Spring Boot 4.3+可省略@Autowired)
// 仅依赖Service,不直接依赖Repository(遵循分层架构)
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<UserDTO> list() {
return userService.listUsers();
}
}
// Service层同理,构造函数注入Repository
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
反模式4:实体直接暴露给前端
问题表现:Controller 直接返回数据库实体(Entity)给前端,暴露数据库结构。
// 违规:Controller直接返回数据库实体(Entity),暴露数据库结构
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<User> list() {
// 直接返回Entity,暴露数据库表所有字段(如id、createTime等敏感/无用字段)
return userRepository.findAll();
}
}
问题分析:暴露数据库表结构,存在安全隐患(比如敏感字段泄露);一旦修改实体类字段,会直接影响前端接口,破坏接口兼容性;前端不需要的字段也会被返回,浪费带宽。
解决方案:使用 DTO(Data Transfer Object,数据传输对象)进行前后端数据交互,只返回前端需要的字段。
规范示例:
// 规范:DTO类(仅包含前端需要的字段,隐藏数据库结构)
package com.icoderoad.dto;
import lombok.Data; // 实际开发可使用lombok简化getter/setter
@Data
public class UserDTO {
// 仅暴露前端需要的字段,不包含数据库无关字段(如id、createTime)
private String name;
private Integer age;
// 如需扩展,仅添加前端需要的字段,不影响数据库实体
}
// 规范:Service层负责Entity与DTO的转换,不暴露实体给前端
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<UserDTO> listUsers() {
// 1. 从Repository获取实体(仅Service层可访问Entity)
List<User> users = userRepository.findAll();
// 2. 实体转DTO,只返回前端需要的字段
return users.stream()
.map(user -> {
UserDTO dto = new UserDTO();
dto.setName(user.getName());
dto.setAge(user.getAge());
return dto;
})
.toList();
}
}
// Controller层仅返回DTO
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<UserDTO> list() {
return userService.listUsers(); // 返回DTO,隐藏数据库结构
}
}
反模式5:滥用 @Transactional
问题表现:在方法上随意添加 @Transactional 注解,将远程调用、非数据库操作也包裹在事务中。
// 违规:事务包裹远程调用,导致锁时间过长
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentApi paymentApi; // 远程调用接口
public OrderService(OrderRepository orderRepository, PaymentApi paymentApi) {
this.orderRepository = orderRepository;
this.paymentApi = paymentApi;
}
// 违规:事务包含远程调用(paymentApi.call())
@Transactional
public void createOrder(OrderDTO dto) {
// 远程调用:耗时不确定,导致事务锁持有时间过长
paymentApi.call(dto.getPaymentId());
// 数据库操作:本应是事务唯一包含的内容
Order order = new Order();
order.setOrderNo(dto.getOrderNo());
orderRepository.save(order);
}
}
问题分析:远程调用耗时不确定,会导致事务锁时间过长,影响数据库性能;非数据库操作不需要事务,添加注解会增加不必要的开销。
解决方案:只在包含数据库写入操作(新增、修改、删除)的方法上开启事务,且事务范围尽量最小化,避免包含远程调用、IO操作等。
反模式6:N+1 查询问题
问题表现:查询主表数据后,循环查询关联表数据,导致大量冗余查询。
// 违规:N+1查询问题,循环查询关联数据
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<UserDTO> listUsersWithOrders() {
// 1次查询:获取所有用户(N为用户数量)
List<User> users = userRepository.findAll();
// N次查询:循环获取每个用户的订单,性能极差
users.forEach(user -> {
// 每次调用getOrders()都会触发一次数据库查询
user.getOrders().size();
});
// 实体转DTO返回
return users.stream()
.map(user -> new UserDTO(user.getName(), user.getAge()))
.toList();
}
}
问题分析:上述代码会执行 1 次查询获取所有用户,然后执行 N 次查询获取每个用户的订单,查询次数随数据量增加而增多,严重影响性能。
解决方案:使用 JOIN FETCH 关联查询,一次性获取主表和关联表数据,避免冗余查询。
规范示例:
// 规范:Repository层使用JOIN FETCH,一次性获取主表+关联表数据
package com.icoderoad.repository;
import com.icoderoad.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
// JOIN FETCH 关联查询,1次查询获取所有用户及对应订单
@Query("select u from User u join fetch u.orders")
List<User> findAllWithOrders();
}
// Service层调用,避免N+1查询
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public List<UserDTO> listUsersWithOrders() {
// 仅1次查询,获取所有用户及订单
List<User> users = userRepository.findAllWithOrders();
return users.stream()
.map(user -> new UserDTO(user.getName(), user.getAge()))
.toList();
}
}
反模式7:没有统一异常处理
问题表现:在 Controller、Service 中到处使用 try-catch 处理异常,异常处理逻辑分散。
// 违规:异常处理分散,每个接口都写try-catch,格式不统一
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity<?> create(@RequestBody UserDTO dto) {
try {
userService.createUser(dto);
return ResponseEntity.ok("创建成功");
} catch (IllegalArgumentException e) {
// 每个接口都要单独处理异常,代码冗余
return ResponseEntity.badRequest().body(e.getMessage());
} catch (Exception e) {
return ResponseEntity.internalServerError().body("系统异常");
}
}
@GetMapping("/{id}")
public ResponseEntity<?> getById(@PathVariable Long id) {
try {
UserDTO dto = userService.getById(id);
return ResponseEntity.ok(dto);
} catch (Exception e) {
// 异常响应格式不统一,前端对接困难
return ResponseEntity.status(500).body("查询失败");
}
}
}
问题分析:代码冗余,相同的异常处理逻辑重复出现;无法统一管理异常响应格式,前端对接困难;异常排查不便,没有统一的异常日志。
解决方案:使用 @RestControllerAdvice 实现全局异常处理,统一捕获所有异常,规范响应格式。
规范示例:
// 规范:全局异常处理,统一捕获、统一响应格式
package com.icoderoad.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
// 全局异常处理器,所有Controller的异常都会被捕获
@RestControllerAdvice
public class GlobalExceptionHandler {
// 自定义异常:参数非法(如年龄小于18)
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorDTO> handleIllegalArgException(IllegalArgumentException e) {
ErrorDTO error = new ErrorDTO(
HttpStatus.BAD_REQUEST.value(),
"参数异常",
e.getMessage()
);
return ResponseEntity.badRequest().body(error);
}
// 通用异常:系统异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDTO> handleException(Exception e) {
ErrorDTO error = new ErrorDTO(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"系统异常",
"服务器内部错误,请联系管理员"
);
// 打印异常堆栈,方便排查(遵循反模式10规范)
log.error("系统异常:", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
// 统一异常响应DTO(规范返回格式)
public static class ErrorDTO {
private Integer code;
private String message;
private String detail;
// 构造函数、getter/setter
public ErrorDTO(Integer code, String message, String detail) {
this.code = code;
this.message = message;
this.detail = detail;
}
}
}
反模式8:配置写死在代码里
问题表现:将配置信息(如接口地址、参数阈值)硬编码在代码中,无法灵活调整。
// 违规:配置硬编码,无法根据环境切换,修改需重新部署
@Service
public class PaymentService {
// 硬编码接口地址,测试/生产环境无法灵活调整
private final String PAYMENT_API_URL = "http://localhost:8080/api/payment";
// 硬编码参数阈值,修改需改代码
private final Integer MAX_RETRY_COUNT = 3;
public void callPaymentApi(String orderNo) {
// 直接使用硬编码配置,扩展性差
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForObject(PAYMENT_API_URL + "/notify", orderNo, String.class);
}
}
问题分析:配置硬编码,修改配置需要修改代码、重新部署;无法根据开发、测试、生产等不同环境调整配置,灵活性极差。
解决方案:使用 application.yml 或 application.properties 配置文件管理所有配置,通过 @Value 或 @ConfigurationProperties 读取配置。
规范示例:
# 规范:配置文件管理(application.yml),按环境区分配置
# 开发环境:application-dev.yml
# 测试环境:application-test.yml
# 生产环境:application-prod.yml
app:
payment:
api-url: http://localhost:8080/api/payment # 接口地址,可按环境修改
retry:
max-count: 3 # 重试阈值,灵活调整
page:
max-size: 100 # 分页最大条数
// 规范:读取配置文件,不硬编码,支持多环境切换
package com.icoderoad.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
// 方式1:@Value 读取单个配置(适合少量配置)
@Value("${app.payment.api-url}")
private String paymentApiUrl;
// 方式2:@ConfigurationProperties 批量读取(推荐,适合多个相关配置)
private final AppConfig appConfig;
public PaymentService(AppConfig appConfig) {
this.appConfig = appConfig;
}
public void callPaymentApi(String orderNo) {
// 使用配置文件中的配置,无需修改代码
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForObject(paymentApiUrl + "/notify", orderNo, String.class);
// 使用批量配置
System.out.println("最大重试次数:" + appConfig.getRetry().getMaxCount());
}
// 批量配置类(与配置文件层级对应)
@Configuration
@ConfigurationProperties(prefix = "app")
public static class AppConfig {
private PaymentConfig payment;
private RetryConfig retry;
private PageConfig page;
// 内部静态类,对应配置层级
public static class PaymentConfig {
private String apiUrl;
// getter/setter
}
public static class RetryConfig {
private Integer maxCount;
// getter/setter
}
public static class PageConfig {
private Integer maxSize;
// getter/setter
}
// 外部类getter/setter
}
}
反模式9:没有缓存策略
问题表现:对于高频访问、变化频率低的数据(如字典、商品列表),每次请求都直接查询数据库。
// 违规:高频访问接口无缓存,每次都查询数据库,压力过大
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// 商品列表为高频访问、低频修改数据,无缓存会频繁查询数据库
public List<ProductDTO> listProducts() {
// 每次请求都查询数据库,高并发下性能瓶颈
List<Product> products = productRepository.findAll();
return products.stream()
.map(p -> new ProductDTO(p.getName(), p.getPrice()))
.toList();
}
}
问题分析:高并发场景下,大量请求直接访问数据库,会导致数据库压力过大,接口响应变慢,甚至出现数据库瓶颈。
解决方案:使用 Spring Cache 框架,对高频访问的数据进行缓存,减少数据库查询次数。
规范示例:
// 规范:使用Spring Cache缓存高频访问数据,减少数据库压力
package com.icoderoad;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
// 1. 启动类添加@EnableCaching,开启缓存功能
@SpringBootApplication
@EnableCaching
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
// 2. Service层添加缓存注解,控制缓存逻辑
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// @Cacheable:查询时先查缓存,无缓存再查数据库,结果存入缓存
@Cacheable(value = "productList", key = "'allProducts'")
public List<ProductDTO> listProducts() {
// 仅第一次请求会查询数据库,后续请求直接返回缓存
List<Product> products = productRepository.findAll();
return products.stream()
.map(p -> new ProductDTO(p.getName(), p.getPrice()))
.toList();
}
// @CacheEvict:修改/新增数据后,清除对应缓存,避免缓存脏数据
@CacheEvict(value = "productList", key = "'allProducts'")
public void addProduct(ProductDTO dto) {
Product product = new Product();
product.setName(dto.getName());
product.setPrice(dto.getPrice());
productRepository.save(product);
}
}
反模式10:日志随便打印
问题表现:使用 System.out.println() 打印日志,日志级别混乱,没有统一规范。
// 违规:日志打印不规范,使用System.out,无级别、无上下文
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUser(UserDTO dto) {
// 不规范1:使用System.out,无法控制日志级别
System.out.println("用户创建开始");
try {
User user = new User();
user.setName(dto.getName());
user.setAge(dto.getAge());
userRepository.save(user);
// 不规范2:日志无上下文,无法定位具体用户
System.out.println("用户创建成功");
} catch (Exception e) {
// 不规范3:不打印异常堆栈,无法排查问题
System.out.println("用户创建失败");
}
}
}
问题分析:日志输出方式不统一,难以管理;无法控制日志级别(如开发环境打印 debug 日志,生产环境只打印 info 日志);日志没有上下文信息,排查问题困难。
解决方案:使用 SLF4J + Logback (Spring Boot 默认日志框架),统一日志打印规范,按级别输出日志。
规范示例:
// 规范:使用SLF4J+Logback,统一日志规范,按级别输出
package com.icoderoad.service;
import com.icoderoad.domain.User;
import com.icoderoad.dto.UserDTO;
import com.icoderoad.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 1. 定义日志对象,参数为当前类(便于定位日志来源)
private static final Logger log = LoggerFactory.getLogger(UserService.class);
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void createUser(UserDTO dto) {
// 2. 按级别输出日志,info级别记录业务流程,携带上下文(用户名)
log.info("用户创建开始,用户名:{},年龄:{}", dto.getName(), dto.getAge());
try {
User user = new User();
user.setName(dto.getName());
user.setAge(dto.getAge());
userRepository.save(user);
log.info("用户创建成功,用户名:{}", dto.getName());
} catch (Exception e) {
// 3. error级别记录异常,打印堆栈(便于排查),携带上下文
log.error("用户创建失败,用户名:{},异常信息:{}", dto.getName(), e.getMessage(), e);
}
}
}
// 补充:日志级别规范(从低到高)
// debug:开发调试用,生产环境关闭
// info:业务流程关键节点,如"用户创建开始"
// warn:警告信息,如"参数非必填但为空"
// error:异常信息,如"用户创建失败",必须打印堆栈
四、核心原则总结
其实 Spring Boot 开发规范,本质上是围绕“单一职责”“分层架构”“可维护性”这三个核心展开的。记住以下5个原则,就能避开大部分坑,让项目长期保持优雅:
-
Controller 只处理 HTTP:接收请求、校验参数、返回响应,不写任何业务逻辑;
-
Service 只写业务逻辑:专注于业务规则处理,通过 Repository 访问数据,不直接操作数据库;
-
Repository 只做数据访问:封装数据库操作,提供统一的数据访问接口,不包含业务逻辑;
-
DTO 不暴露 Entity:用 DTO 做前后端数据传输,保护数据库结构,保证接口兼容性;
-
事务只包裹数据库操作:最小化事务范围,避免包含远程调用、IO 操作,减少锁时间。
Spring Boot 的核心优势是“简化开发、提高效率”,但高效不等于随意。遵循规范,才能避免项目从“优雅”沦为“沼泽”,让每一次迭代都更轻松,每一行代码都更可维护。
希望这套规范能帮到正在开发 Spring Boot 项目的你,也欢迎在评论区补充你项目中遇到的反模式和解决方案~
(注:文档部分内容可能由 AI 生成)
更多推荐




所有评论(0)