四层架构: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")
public class UserController {

    private final UserService userService;

    @GetMapping("/{id}")
    public ApiResponse<UserDTO> getUser(@PathVariable Long id) {
        UserDTO user = userService.getUserById(id);
        return ApiResponse.ok(user);
    }

    @PostMapping
    public ApiResponse<UserDTO> createUser(@Valid @RequestBody CreateUserRequest request) {
        UserDTO user = userService.createUser(request);
        return ApiResponse.ok(user);
    }
}

Controller 应该:几行代码完成「接参 → 调 Service → 返回」,业务细节全部下沉。


2. Service 层(业务逻辑层)

Service 是系统的「大脑」,承载绝大部分业务价值。

应该做的事:

  • 实现业务规则与流程(注册、下单、支付、退款)
  • 编排多个 Repository 或外部服务(发邮件、调支付网关)
  • 管理事务边界(@Transactional
  • 做业务级校验(用户名是否重复、库存是否足够、用户是否有权限)
  • 对象转换:Entity ↔ DTO(有时交给专门的 Mapper 工具)

不应该做的事:

  • 解析 HTTP 请求头、Cookie(那是 Controller / Filter 的事)
  • 直接写 JDBC 或拼接 SQL(交给 Repository)
  • 返回 HTTP 响应结构(返回业务对象或 DTO 即可)

示例(伪代码):

@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;
    private final InventoryService inventoryService;

    @Transactional
    public OrderDTO createOrder(CreateOrderRequest request, Long userId) {
        Product product = productRepository.findById(request.getProductId())
                .orElseThrow(() -> new NotFoundException("商品不存在"));

        if (product.getStock() < request.getQuantity()) {
            throw new BusinessException("库存不足");
        }

        inventoryService.deductStock(product.getId(), request.getQuantity());

        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(product.getId());
        order.setAmount(product.getPrice() * request.getQuantity());
        order.setStatus("CREATED");

        orderRepository.save(order);
        return OrderDTO.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 风格):

public interface OrderRepository extends JpaRepository<Order, Long> {

    List<Order> findByUserIdAndStatus(Long userId, String status);

    Optional<Order> findByOrderNo(String orderNo);
}

示例(MyBatis-Plus 风格):

@Repository
@RequiredArgsConstructor
public class OrderRepository {

    private final OrderMapper mapper;

    public Optional<Order> findById(Long id) {
        return Optional.ofNullable(mapper.selectById(id));
    }

    public void save(Order order) {
        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")
public class Order {

    @TableId(type = IdType.AUTO)
    private Long id;

    private Long userId;
    private Long productId;
    private BigDecimal amount;
    private String status;
    private LocalDateTime createdAt;

    // 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 接收前端入参 CreateUserRequestLoginRequest
Response DTO 返回给前端的数据 UserProfileDTOOrderSummaryDTO

为什么 Entity 不直接返回给前端?

  1. 安全:Entity 可能含密码哈希、内部状态字段
  2. 解耦:表结构变更不应迫使 API 契约一起变
  3. 聚合:一个响应可能需要多张表的数据,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")
public ApiResponse<?> register(@RequestBody RegisterRequest req) {
    if (userMapper.existsByEmail(req.getEmail())) {
        return ApiResponse.fail("邮箱已注册");
    }
    User user = new User();
    user.setEmail(req.getEmail());
    user.setPasswordHash(passwordEncoder.encode(req.getPassword()));
    userMapper.insert(user);
    return ApiResponse.ok();
}

注册流程涉及校验、加密、持久化、发欢迎邮件——这些都应放在 UserService.register() 里。


误区 2:Service 直接写 SQL

// ❌ 反例
public List<User> searchUsers(String keyword) {
    return jdbcTemplate.query(
        "SELECT * FROM users WHERE name LIKE ?", keyword);
}

SQL 应封装在 Repository,Service 只调用 userRepository.searchByKeyword(keyword)


误区 3:Entity 里注入 Service

// ❌ 反例 — 容易造成循环依赖
public class Order {
    @Autowired
    private OrderService orderService;

    public void cancel() {
        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
└── exception

2. 方法命名体现层级

命名风格 示例
Controller HTTP 语义 getUsercreateOrderdeleteComment
Service 业务语义 registerUserplaceOrderapproveRefund
Repository 数据语义 findByIdsavedeleteByUserId

3. 事务放在 Service

@Service
public class TransferService {

    @Transactional  // 转账涉及扣款 + 入账,必须同一事务
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        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 契约与数据库结构分离。

刚开始会觉得多写几个类麻烦;项目代码过万行、多人协作、需求频繁变更时,这种结构会显著降低心智负担.


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐