我们来用一个通俗的比喻帮你理解后端分层:把整个后端应用想象成一家餐厅。
- 控制层(Controller) -- 餐厅的服务员 。
负责接待客人(前端请求),记录客人要点什么菜(参数),然后把菜单交给后厨,最后把做好的菜端给客人(返回响应)。 - 服务层(Service) -- 厨师 。
负责具体的做菜流程(业务逻辑),比如这道菜需要哪些食材、烹饪顺序、加什么调料。厨师不会自己去买菜,而是通知采购员。 - 数据访问层(Repository/DAO) -- 采购员 。
负责去仓库(数据库)取食材、存储食材、更新食材。它只关心怎么和数据库打交道,不关心菜怎么做。 - 模型/实体层(Model/Entity) -- 菜单/菜品 。
定义了每道菜的组成(数据结构),比如菜名、价格、口味等。它贯穿各层,让数据在不同的角色之间传递时保持格式统一。
1. 各层详细说明
控制层(Controller)
- 作用:接收 HTTP 请求,解析参数,调用 Service 层的方法,并将 Service 返回的结果封装成 HTTP 响应。
- 典型注解 :
@RestController、@RequestMapping - 职责 :参数校验、响应格式转换、路由映射。不包含业务逻辑。
服务层(Service)
- 作用:实现核心业务逻辑。比如计算订单总价、检查库存、调用多个 Repository 完成一个复杂事务。
- 典型注解 :
@Service - 职责 :处理事务(
@Transactional),调用数据访问层,组装业务对象。不直接处理 HTTP 细节。
数据访问层(Repository / DAO)
- 作用:封装对数据库的 CRUD 操作。通过 JPA、MyBatis 等技术将对象映射到数据库表。
- 典型注解 :
@Repository - 职责 :执行 SQL、查询结果、持久化数据。不包含业务逻辑。
模型层(Model / Entity / DTO)
- Entity :与数据库表一一对应的 Java 对象,通常放在
entity包。 - DTO(Data Transfer Object):在层之间传输数据的对象,比如 Controller 返回给前端的结构,常与 Entity 分开避免暴露数据库字段。
- VO(View Object):专门用于前端展示的对象,与 DTO 类似但更偏视图层。
辅助角色:Maven 与 Spring Boot
- Maven:项目构建工具。帮你管理 jar 包依赖(如 Spring、数据库驱动),并负责编译、打包成可运行的 jar/war。
- Spring Boot:基于 Spring 的快速开发框架。它内置了 Tomcat 服务器,通过"自动配置"让你不用手动配置大量 XML,只需少量注解就能跑起一个项目。
2. 数据流转与关系图
下面是一个典型的请求处理流程,用 Mermaid 流程图 展示。
(如果你在支持 Mermaid 的编辑器中查看,可以直接渲染;不支持时也可以看下方的文字描述)
前端发送 HTTP 请求
控制层 Controller
调用 Service 层
服务层 Service
调用 Repository 层
数据访问层 Repository
数据库
返回 HTTP 响应给前端
文字流程说明:
- 前端(浏览器/App)发起 HTTP 请求(如
POST /api/order)。 - 请求被 Controller 拦截,根据 URL 路由到对应的方法。
- Controller 解析请求参数,调用 Service 层的某个方法。
- Service 层执行业务逻辑:可能调用多个 Repository 方法,并利用 Entity 对象传递数据。
- Repository 使用 JPA 或 MyBatis 将操作翻译成 SQL,与数据库交互。
- 数据库返回结果,Repository 将结果封装成 Entity 对象返回给 Service。
- Service 处理完毕后,可能将 Entity 转换为 DTO 返回给 Controller。
- Controller 将 DTO 封装成 JSON 格式的 HTTP 响应,返回给前端。
3. 为什么这样分层?
- 解耦:每一层只关心自己的职责,修改数据库实现不影响业务层,修改业务逻辑不影响接口。
- 可测试:可以对 Service 层单独做单元测试,Mock Repository 即可,无需启动数据库和 Web 服务器。
- 维护性:代码结构清晰,新人能快速定位到某段逻辑的位置。
- 复用性:Service 可以被多个 Controller 复用,Repository 可以被多个 Service 复用。
4. 代码示例(Spring Boot 风格)
Entity(实体)
java
@Entity
public class Order {
@Id
private Long id;
private String product;
private Integer quantity;
// getter/setter...
}
Repository
java
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByProduct(String product);
}
Service
java
@Service
@Transactional
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order createOrder(OrderDto dto) {
// 业务逻辑:比如检查库存
Order order = new Order();
order.setProduct(dto.getProduct());
order.setQuantity(dto.getQuantity());
return orderRepository.save(order);
}
}
Controller
java
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> create(@RequestBody OrderDto dto) {
Order order = orderService.createOrder(dto);
return ResponseEntity.ok(order);
}
}
5. 常见问题
-
Controller 能不能直接调用 Repository?
可以,但不推荐。因为业务逻辑会散落在 Controller 中,一旦需要被另一个接口复用,就会造成重复代码。Service 层专门收容业务逻辑。
-
DTO 和 Entity 什么时候用?
一般 Entity 用于数据库映射,DTO 用于对外传输(避免暴露数据库字段,或组合多个 Entity 的数据)。Controller 接收前端参数时常用 DTO,返回给前端时也常用 DTO。
-
Maven 在这里起什么作用?
Maven 通过
pom.xml声明项目依赖(比如 spring-boot-starter-web、spring-boot-starter-data-jpa),它会自动下载这些 jar 包并加入 classpath。你只需要写代码,打包时用mvn package就能生成可运行的 jar。