四层架构:Java 后端分层设计的完整指南
适用场景:Spring Boot / Spring MVC 等 Java Web 后端
关键词:Controller · Service · Repository · Entity · 分层架构 · 职责分离
我遇到的问题
刚学 Java Web 开发时,很容易把所有逻辑堆在一个类里:查数据库、算业务、拼 JSON 全写在一起。小 demo 能跑,项目一变大就改不动、测不了、协作也痛苦。
后面又学习了三层架构(Controller → Service → Mapper → DB),但是在高复杂的业务代码中,整个项目的结构又变得很复杂了,然后便有了四层架构,可以说这是后端分层的最佳实现了.
**四层架构(Controller → Service → Repository → Mapper → Entity)**是后端里最经典、也最实用的组织方式之一。它把代码按职责切成四块,让每一层只做一件事。这篇文章从概念、职责、调用关系到常见误区,系统讲清楚「四层」到底是什么、为什么要这么分、日常开发怎么落地。
一、四层架构是什么
四层架构(Four-Layer Architecture)在 Java 后端语境下,通常指:
| 层级 | 英文名 | 核心职责 |
|---|---|---|
| 控制层 | Controller | 接收 HTTP 请求,返回响应 |
| 业务层 | Service | 实现业务规则与流程编排 |
| 数据访问层 | Repository / DAO | 读写数据库,屏蔽持久化细节 |
| 实体层 | Entity / Model | 映射数据库表或领域对象 |
┌─────────────────────────────────────────────────────────┐ │ HTTP 请求 / 响应 │ └───────────────────────────┬─────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ Controller 层 参数校验 · 路由 · 统一响应格式 │ └───────────────────────────┬─────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ Service 层 业务逻辑 · 事务 · 权限判断 · 流程编排 │ └───────────────────────────┬─────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ Repository 层 CRUD · 复杂查询 · 缓存读写 │ └───────────────────────────┬─────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────┐ │ Entity 层 与表结构对应的 Java 对象 │ └───────────────────────────┬─────────────────────────────┘ ▼ MySQL / PostgreSQL / Redis ...一句话记忆:Controller 接请求 → Service 写逻辑 → Repository 访问数据 → Entity 映射表。
二、各层职责详解
1. Controller 层(控制层)
Controller 是系统的「门面」,直接面对前端或外部调用方。
应该做的事:
- 定义 REST 接口路径(如
GET /api/users/{id}) - 接收并绑定请求参数(
@RequestBody、@PathVariable等) - 做基础的参数格式校验(非空、长度、格式)
- 调用 Service,把结果包装成统一响应(如
{ code, message, data }) - 处理 HTTP 状态码(200、400、401、404 等)
不应该做的事:
- 写 SQL 或直接操作数据库
- 实现复杂业务规则(如「订单金额满 100 减 10 且每人限一次」)
- 在 Controller 里开事务、做分布式锁
示例(伪代码):
@RestController@RequestMapping("/api/users")publicclassUserController{privatefinalUserServiceuserService;@GetMapping("/{id}")publicApiResponse<UserDTO>getUser(@PathVariableLongid){UserDTOuser=userService.getUserById(id);returnApiResponse.ok(user);}@PostMappingpublicApiResponse<UserDTO>createUser(@Valid@RequestBodyCreateUserRequestrequest){UserDTOuser=userService.createUser(request);returnApiResponse.ok(user);}}Controller 应该薄:几行代码完成「接参 → 调 Service → 返回」,业务细节全部下沉。
2. Service 层(业务逻辑层)
Service 是系统的「大脑」,承载绝大部分业务价值。
应该做的事:
- 实现业务规则与流程(注册、下单、支付、退款)
- 编排多个 Repository 或外部服务(发邮件、调支付网关)
- 管理事务边界(
@Transactional) - 做业务级校验(用户名是否重复、库存是否足够、用户是否有权限)
- 对象转换:Entity ↔ DTO(有时交给专门的 Mapper 工具)
不应该做的事:
- 解析 HTTP 请求头、Cookie(那是 Controller / Filter 的事)
- 直接写 JDBC 或拼接 SQL(交给 Repository)
- 返回 HTTP 响应结构(返回业务对象或 DTO 即可)
示例(伪代码):
@Service@RequiredArgsConstructorpublicclassOrderService{privatefinalOrderRepositoryorderRepository;privatefinalProductRepositoryproductRepository;privatefinalInventoryServiceinventoryService;@TransactionalpublicOrderDTOcreateOrder(CreateOrderRequestrequest,LonguserId){Productproduct=productRepository.findById(request.getProductId()).orElseThrow(()->newNotFoundException("商品不存在"));if(product.getStock()<request.getQuantity()){thrownewBusinessException("库存不足");}inventoryService.deductStock(product.getId(),request.getQuantity());Orderorder=newOrder();order.setUserId(userId);order.setProductId(product.getId());order.setAmount(product.getPrice()*request.getQuantity());order.setStatus("CREATED");orderRepository.save(order);returnOrderDTO.from(order);}}Service 方法通常对应一个用例(Use Case):「创建订单」「取消订单」「用户登录」—— 一个方法讲清楚一件事。
3. Repository 层(数据访问层)
Repository(或传统叫法 DAO)负责与持久化存储打交道。
应该做的事:
- 对 Entity 做增删改查(CRUD)
- 封装查询条件(按 ID、按状态、分页、排序)
- 对接 ORM 框架(JPA、MyBatis、MyBatis-Plus)或 Redis 等
- 把「怎么查库」的细节藏起来,对 Service 暴露语义清晰的方法
不应该做的事:
- 写业务规则(「库存不足不能下单」应在 Service)
- 决定事务是否提交(事务在 Service 层声明)
- 返回 HTTP 响应或 DTO(通常返回 Entity 或
Optional<Entity>)
示例(JPA 风格):
publicinterfaceOrderRepositoryextendsJpaRepository<Order,Long>{List<Order>findByUserIdAndStatus(LonguserId,Stringstatus);Optional<Order>findByOrderNo(StringorderNo);}示例(MyBatis-Plus 风格):
@Repository@RequiredArgsConstructorpublicclassOrderRepository{privatefinalOrderMappermapper;publicOptional<Order>findById(Longid){returnOptional.ofNullable(mapper.selectById(id));}publicvoidsave(Orderorder){if(order.getId()==null){mapper.insert(order);}else{mapper.updateById(order);}}}Repository 的方法名最好表达业务含义,而不是暴露 SQL 细节,例如findActiveUsersByDeptId优于selectFromUserWhereStatusEquals1。
4. Entity 层(实体层)
Entity 是数据库表在 Java 世界里的「镜像」。
应该做的事:
- 字段与表列一一对应(或通过 ORM 注解映射)
- 承载数据的 getter/setter 或 record 构造
- 可包含简单的、与自身数据强相关的行为(如
isExpired())
不应该做的事:
- 调用 Service 或 Repository(避免循环依赖)
- 写复杂业务流程
- 直接暴露给前端(敏感字段如
passwordHash不应原样返回)
示例:
@TableName("orders")publicclassOrder{@TableId(type=IdType.AUTO)privateLongid;privateLonguserId;privateLongproductId;privateBigDecimalamount;privateStringstatus;privateLocalDateTimecreatedAt;// getter / setter ...}Entity 关注的是数据长什么样,而不是业务流程怎么走。
三、一次完整请求的流转
以「用户查询自己的订单列表」为例:
前端 GET /api/orders?status=PAID │ ▼ ┌─────────────────────┐ │ OrderController │ 解析 status 参数,获取当前登录 userId └──────────┬──────────┘ │ orderService.listByUser(userId, status) ▼ ┌─────────────────────┐ │ OrderService │ 校验 userId 合法,调用 Repository 查询 └──────────┬──────────┘ │ orderRepository.findByUserIdAndStatus(...) ▼ ┌─────────────────────┐ │ OrderRepository │ 执行 SQL / Mapper 查询 └──────────┬──────────┘ │ 返回 List<Order> ▼ ┌─────────────────────┐ │ Order (Entity) │ 内存中的订单对象列表 └──────────┬──────────┘ │ Service 转为 List<OrderDTO> ▼ ┌─────────────────────┐ │ OrderController │ 包装为 ApiResponse 返回 JSON └─────────────────────┘数据方向:
- 请求向下传:Controller → Service → Repository → DB
- 结果向上返:Entity → Service(转 DTO)→ Controller → JSON
四、DTO:不算一层,但很重要
实际项目里常见dto包,它不是第五层,而是跨层传输的数据载体:
| 类型 | 用途 | 示例 |
|---|---|---|
| Request DTO | 接收前端入参 | CreateUserRequest、LoginRequest |
| Response DTO | 返回给前端的数据 | UserProfileDTO、OrderSummaryDTO |
为什么 Entity 不直接返回给前端?
- 安全:Entity 可能含密码哈希、内部状态字段
- 解耦:表结构变更不应迫使 API 契约一起变
- 聚合:一个响应可能需要多张表的数据,Entity 单表映射不够用
前端 ── Request DTO ──► Controller ──► Service │ Entity ◄──► Repository ◄──► DB │ 前端 ◄── Response DTO ◄── Controller ◄── Service五、四层 vs 三层:有什么区别
有人会把 Repository 和 Entity 合并称为「Model 层」,于是变成三层架构:
| 三层 | 四层 |
|---|---|
| Controller | Controller |
| Service | Service |
| Model(Entity + DAO 混在一起) | Repository + Entity 分开 |
四层更细的好处:
- Entity 只描述「数据是什么」
- Repository 只描述「数据怎么存取」
- 职责更清晰,大项目更好维护
小项目用三层也能跑;团队变大、表变多、查询变复杂时,拆成四层更常见。
六、常见误区
误区 1:Controller 里写业务逻辑
// ❌ 反例@PostMapping("/register")publicApiResponse<?>register(@RequestBodyRegisterRequestreq){if(userMapper.existsByEmail(req.getEmail())){returnApiResponse.fail("邮箱已注册");}Useruser=newUser();user.setEmail(req.getEmail());user.setPasswordHash(passwordEncoder.encode(req.getPassword()));userMapper.insert(user);returnApiResponse.ok();}注册流程涉及校验、加密、持久化、发欢迎邮件——这些都应放在UserService.register()里。
误区 2:Service 直接写 SQL
// ❌ 反例publicList<User>searchUsers(Stringkeyword){returnjdbcTemplate.query("SELECT * FROM users WHERE name LIKE ?",keyword);}SQL 应封装在 Repository,Service 只调用userRepository.searchByKeyword(keyword)。
误区 3:Entity 里注入 Service
// ❌ 反例 — 容易造成循环依赖publicclassOrder{@AutowiredprivateOrderServiceorderService;publicvoidcancel(){orderService.cancelOrder(this.getId());}}「取消订单」是业务行为,放在OrderService.cancelOrder(id),Entity 保持纯数据对象。
误区 4:Repository 返回 DTO
Repository 应返回 Entity(或简单值类型)。Entity → DTO 的转换通常在 Service 层完成,保持数据访问层与 API 契约解耦。
误区 5:层与层之间「跨层调用」
❌ Controller 直接调 Repository ❌ Repository 调 Service原则:上层可以调下层,下层不能调上层;同层之间谨慎互相调用。
合法调用链:Controller → Service → Repository → DB
七、分层带来的实际好处
1. 可测试性
- Service 可以用 Mock Repository 单测业务逻辑,不依赖数据库
- Repository 可以用集成测试验证 SQL 正确性
- Controller 可以用 MockMvc 测接口契约
2. 可维护性
改表结构 → 主要动 Entity + Repository
改业务规则 → 主要动 Service
改 API 格式 → 主要动 Controller + DTO
改动范围可控,不容易「改一处崩全局」。
3. 团队协作
前端对接口、后端 A 写 Service、后端 B 写 Repository,边界清晰,并行开发冲突少。
4. 技术替换
从 MyBatis 换 JPA、从 MySQL 换 PostgreSQL,往往只需改 Repository 实现,Service 和 Controller 基本不动。
八、实践建议
1. 包结构按层划分
com.example.app ├── controller ├── service │ └── impl // 可选:接口 + 实现分离 ├── repository ├── entity ├── dto │ ├── request │ └── response ├── config └── exception2. 方法命名体现层级
| 层 | 命名风格 | 示例 |
|---|---|---|
| Controller | HTTP 语义 | getUser、createOrder、deleteComment |
| Service | 业务语义 | registerUser、placeOrder、approveRefund |
| Repository | 数据语义 | findById、save、deleteByUserId |
3. 事务放在 Service
@ServicepublicclassTransferService{@Transactional// 转账涉及扣款 + 入账,必须同一事务publicvoidtransfer(LongfromId,LongtoId,BigDecimalamount){accountRepository.deduct(fromId,amount);accountRepository.credit(toId,amount);}}4. 统一异常处理
在 Controller 层之上用@RestControllerAdvice捕获BusinessException,转成统一 JSON,避免每个 Controller 里写重复的 try-catch。
5. 小项目也要分层,但可以简化
- 不必强行 Interface + Impl 两套 Service
- Repository 可以直接用 MyBatis-Plus 的
BaseMapper,不必每个表都包一层 - 但Controller 不写业务、Service 不写 SQL这条底线建议守住
九、与 DDD、微服务的关系(拓展)
四层架构是经典分层,不是唯一方案:
- DDD(领域驱动设计)会在 Service 之上再强调 Domain 层(领域模型、聚合根),Entity 升级为 Rich Domain Model
- 微服务里每个服务内部仍常用四层;服务之间通过 HTTP / MQ 通信,不再共享 Repository
学四层是地基;项目复杂后,可以在 Service 层内部再引入 DDD 战术模式,但「Controller 薄、持久化隔离」的思路不变。
十、总结
| 层 | 一句话 |
|---|---|
| Controller | 对外接口,薄层,只负责 HTTP |
| Service | 业务核心,事务与规则在这里 |
| Repository | 数据网关,屏蔽 SQL 和存储细节 |
| Entity | 表结构映射,纯数据对象 |
四层架构的本质不是「多建几个包」,而是单一职责 + 依赖方向清晰:
表现与业务分离,业务与持久化分离,API 契约与数据库结构分离。
刚开始会觉得多写几个类麻烦;项目代码过万行、多人协作、需求频繁变更时,这种结构会显著降低心智负担.