Java 的Service/Mapper/Entity/Controller各层是如何工作的新 手教程
这是一个非常核心的Java后端开发问题。我们将对 MVC + 三层架构(或广义上的 N 层架构)中的 Controller、Service、Mapper(或DAO)、Entity 进行全方位的分析与解析。
这种分层架构是现代Java Web应用(尤其是Spring框架家族)的基石,其核心思想是 “分离关注点”,让每个层次各司其职,从而提升代码的可维护性、可测试性和可扩展性。
1. 各层级的职责与定位
我们先从最底层的数据模型开始,向上逐层解析。
1.1 Entity (实体层 / 模型层)
-
职责:纯粹的数据载体。它的唯一职责是表示一个业务对象(如用户、订单)及其属性,并与数据库表结构建立映射关系。
-
工作内容:
-
使用 JPA 注解(如
@Entity,@Table,@Id,@Column)或 MyBatis 等方式,定义对象与数据库表之间的映射。 -
包含属性(字段)、以及它们的 getter 和 setter 方法。
-
通常不包含任何业务逻辑。
-
-
技术实现:JPA (Hibernate), MyBatis Plus 等。
-
示例:
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
// ... getters and setters
}
1.2 Mapper (映射层 / 数据访问层 - DAO)
-
职责:负责与数据库进行所有交互。它是唯一可以直接操作数据库的层次,对上层隐藏数据库操作的细节。
-
工作内容:
-
执行 CRUD(增删改查)操作。
-
执行复杂的查询,如多表关联查询。
-
定义 SQL 语句。在 MyBatis 中,这通常通过接口和 XML 文件或注解来实现。
-
-
技术实现:MyBatis, MyBatis-Plus, Spring Data JPA (Repository)。
-
示例 (MyBatis Mapper 接口):
@Mapper // MyBatis 注解,标识这是一个Mapper接口
public interface UserMapper {
User selectById(Long id);
List<User> selectAll();
int insert(User user);
int update(User user);
int deleteById(Long id);
}
1.3 Service (服务层 / 业务逻辑层)
-
职责:是整个系统的业务核心。它负责处理复杂的业务逻辑,协调多个 Mapper 调用,确保业务的完整性和一致性。
-
工作内容:
-
业务流程编排:一个业务用例(如“用户下单”)可能涉及检查库存、创建订单、扣减库存、发送通知等多个步骤,这些步骤都在 Service 层编排。
-
事务管理:通常在这一层使用
@Transactional注解来声明事务边界,确保一系列数据库操作要么全部成功,要么全部失败。 -
数据转换与封装:将 Mapper 返回的多个 Entity 或数据组合成前端需要的数据结构(DTO/VO)。
-
权限校验、参数校验(虽然参数校验也常在Controller层做初步校验)。
-
-
技术实现:Spring
@Service注解。 -
示例:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 业务逻辑:获取用户信息,并可能包含一些计算或规则
public UserDTO getUserDetail(Long userId) {
User user = userMapper.selectById(userId);
if (user == null) {
throw new UserNotFoundException("User not found");
}
// 将Entity转换为DTO,避免暴露不必要的信息或循环引用
UserDTO dto = new UserDTO();
dto.setId(user.getId());
dto.setUsername(user.getUsername());
// ... 可能还会调用其他Mapper获取更多信息
return dto;
}
// 带有事务的业务逻辑
@Transactional
public void createUser(CreateUserRequest request) {
// 检查用户名是否重复
if (userMapper.existsByUsername(request.getUsername())) {
throw new BusinessException("Username already exists");
}
// 创建用户实体并保存
User user = new User();
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
userMapper.insert(user);
// ... 可能还会发送消息到消息队列等
}
}
1.4 Controller (控制层 / 表现层)
-
职责:处理HTTP请求和响应。它是系统的入口和出口,负责与客户端(如浏览器、App)进行交互。
-
工作内容:
-
接收请求:解析 HTTP 请求参数(如路径变量
@PathVariable、查询参数@RequestParam、请求体@RequestBody)。 -
调用服务:将解析后的参数传递给对应的 Service 方法进行处理。
-
返回响应:将 Service 处理的结果封装成 JSON/XML 等格式,并通过 HTTP 响应返回给客户端。
-
异常处理:捕获业务逻辑抛出的异常,并将其转换为合适的 HTTP 状态码和错误信息(通常结合
@ControllerAdvice或@ExceptionHandler)。
-
-
技术实现:Spring MVC
@RestController,@Controller。 -
示例:
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
UserDTO user = userService.getUserDetail(id);
return ResponseEntity.ok(user); // HTTP 200
}
@PostMapping
public ResponseEntity<Void> createUser(@Valid @RequestBody CreateUserRequest request) {
userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).build(); // HTTP 201
}
}
2. 协作流程与数据流转
我们以一个典型的 “根据ID查询用户信息” 的 GET 请求为例,来描述各层如何协作:
-
HTTP 请求到达:
-
客户端发起
GET /api/users/123请求。
-
-
Controller 层 (接收与分发):
-
Spring MVC 的路由机制将请求匹配到
UserController的getUser方法。 -
@PathVariable注解将 URL 中的123提取出来,赋值给id参数。 -
Controller 调用
userService.getUserDetail(123)。
-
-
Service 层 (处理业务逻辑):
-
UserService接收到id=123。 -
它调用
userMapper.selectById(123)来获取数据。 -
在此过程中,它可以添加业务逻辑,如权限判断、数据加工、调用其他服务等。
-
-
Mapper 层 (数据访问):
-
UserMapper根据接收到的id=123,执行对应的 SQL 语句(例如SELECT * FROM user WHERE id = 123)。 -
MyBatis 将查询结果集自动映射成一个
UserEntity 对象。 -
将这个
User对象返回给 Service 层。
-
-
Service 层 (返回结果):
-
Service 层接收到
UserEntity。 -
它可能会对这个 Entity 进行加工,或者将其转换为更面向客户端的 DTO(如
UserDTO),以避免直接暴露数据库结构或敏感信息。 -
将
UserDTO返回给 Controller。
-
-
Controller 层 (封装响应):
-
Controller 接收到
UserDTO。 -
它使用
ResponseEntity.ok(user)将 DTO 对象包装成 HTTP 200 响应,并序列化为 JSON 格式。
-
-
HTTP 响应返回:
-
客户端收到 JSON 格式的用户数据。
-
数据流转图:
Client
│ (HTTP Request)
▼
Controller ─── (参数/DTO) ───> Service ─── (Entity/参数) ───> Mapper
│ │
│ ▼
│ Database
│
▼ (HTTP Response)
Client
3. 分层架构的优势
-
高内聚,低耦合:
-
高内聚:每一层只关注自己的核心职责(如Mapper只关注数据访问)。
-
低耦合:层与层之间通过接口或明确的模型进行交互,修改一层(如将MyBatis换成JPA)不会严重影响其他层。
-
-
易于测试:
-
Controller测试:可以使用
@WebMvcTest进行切片测试,只验证HTTP映射和参数解析。 -
Service测试:可以使用
@SpringBootTest或@ExtendWith(MockitoExtension.class),并 Mock(模拟) 掉Mapper的依赖,专注于业务逻辑的测试。 -
Mapper测试:可以使用
@MybatisTest或内存数据库(如H2)进行集成测试,验证SQL的正确性。
-
-
易于维护和扩展:
-
当业务规则变化时,通常只需要修改 Service 层。
-
当需要添加新的数据源或更换持久化框架时,只需要修改 Mapper 层。
-
当API接口变更时,通常只需要修改 Controller 层。
-
-
分工协作明确:
-
团队中可以有人专注于数据库优化(Mapper),有人专注于业务实现(Service),有人专注于接口设计(Controller)。
-
4. 常见问题与最佳实践
-
不要越级调用:严禁 Controller 直接调用 Mapper,这会导致业务逻辑泄露到Controller中,破坏分层架构。
-
使用DTO/VO进行层间数据传输:避免直接在各层传递 Entity 对象,尤其是在 Controller 和客户端之间。这可以:
-
隐藏数据库结构,增强安全性。
-
避免无限递归(如JSON序列化时的双向关联)。
-
定制化数据,只为前端提供它需要的字段。
-
-
Entity 应该是纯粹的:不要在 Entity 中加入业务逻辑,它最好只是一个“贫血模型”。业务逻辑应放在 Service 中。
-
事务边界应在Service层:因为一个业务用例通常包含多个数据操作,事务应该在进入Service方法时开启,在方法结束时提交或回滚。
总结
| 层级 | 核心职责 | 类比 | 常用注解 |
|---|---|---|---|
| Controller | 处理HTTP请求/响应,路由 | 餐厅服务员:接待顾客、点餐、上菜 | @RestController, @RequestMapping |
| Service | 实现核心业务逻辑,事务管理 | 厨师:根据订单烹饪菜肴 | @Service, @Transactional |
| Mapper/DAO | 封装所有数据库操作 | 配菜员/仓库管理员:提供食材 | @Mapper (MyBatis), @Repository (JPA) |
| Entity | 映射数据库表结构,承载数据 | 食材本身 | @Entity, @Table |
这种清晰的分层协作模式,是构建健壮、可维护的Java后端应用的黄金标准。
更多推荐


所有评论(0)