一个完整的Spring Boot分层架构代码示例
- UserDTO:前端传参对象
- UserVO:返回给前端的对象
- UserEntity:数据库实体
- UserMapper:用于 DTO/VO 与 Entity 之间转换
- UserService / UserServiceImpl:业务逻辑层
- UserRepository:数据访问层(JPA)
- UserController:接口控制层
在这个示例代码中:
- 使用 DTO 解耦前端请求结构与数据库结构;
- 使用 VO 精简和安全化返回前端的数据;
- Mapper 保持各层之间的“清洁边界”;
- 各层职责单一清晰:Service 聚焦业务,Repository 聚焦数据操作,Controller 聚焦交互;
- 高度可扩展和可维护的代码结构
项目结构
src
├── controller
│ └── UserController.java
├── dto
│ └── UserDTO.java
├── vo
│ └── UserVO.java
├── entity
│ └── UserEntity.java
├── mapper
│ └── UserMapper.java
├── repository
│ └── UserRepository.java
├── service
│ ├── UserService.java
│ └── impl
│ └── UserServiceImpl.java
UserDTO.java
public class UserDTO {
private String username;
private String email;
// Getters and Setters
}
UserVO.java
public class UserVO {
private Long id;
private String username;
private String status;
// Getters and Setters
}
UserEntity.java
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
private String status; // e.g. ACTIVE / INACTIVE
// Getters and Setters
}
UserMapper.java
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface UserMapper {
UserEntity dtoToEntity(UserDTO dto);
@Mapping(source = "id", target = "id")
@Mapping(source = "username", target = "username")
@Mapping(source = "status", target = "status")
UserVO entityToVO(UserEntity entity);
}
因为字段名一致,所以UserVOentityToVO(UserEntity entity);上面其实是不需要写@Mapping的。
UserRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<UserEntity, Long> {
boolean existsByUsername(String username);
}
这里的existsByUsername 是一个知识点:Spring Data JPA 的“方法名派生查询”功能 - 知乎
UserService.java
import java.util.List;
public interface UserService {
UserEntity createUser(UserEntity user);
List<UserEntity> getAllUsers();
}
UserServiceImpl.java
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserEntity createUser(UserEntity user) {
user.setStatus("ACTIVE");
return userRepository.save(user);
}
@Override
public List<UserEntity> getAllUsers() {
return userRepository.findAll();
}
}
不建议在private final UserRepository userRepository上方加上@Autowired,为了便捷不去写构造函数。
UserController.java
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
private final UserMapper userMapper;
public UserController(UserService userService, UserMapper userMapper) {
this.userService = userService;
this.userMapper = userMapper;
}
@PostMapping
public UserVO createUser(@RequestBody UserDTO userDTO) {
UserEntity entity = userMapper.dtoToEntity(userDTO);
UserEntity saved = userService.createUser(entity);
return userMapper.entityToVO(saved);
}
@GetMapping
public List<UserVO> getAllUsers() {
// 获取所有用户实体
List<UserEntity> userEntities = userService.getAllUsers();
// 创建返回结果列表
List<UserVO> userVOs = new ArrayList<>();
// 遍历转换每个实体为VO
for (UserEntity entity : userEntities) {
UserVO vo = userMapper.entityToVO(entity);
userVOs.add(vo);
}
return userVOs;
}
}
示例请求与返回
创建用户(POST /api/users):
- 请求体:
Request Body:
{
"username": "alice",
"email": "alice@example.com"
}
- 响应体:
Response:
{
"id": 1,
"username": "alice",
"status": "ACTIVE"
}
获取所有用户(GET /api/users):
- GET请求无请求体
- 响应体:
[
{
"id": 1,
"username": "alice",
"status": "ACTIVE"
},
{
"id": 2,
"username": "bob",
"status": "INACTIVE"
},
// 更多用户...
]
数据流转路径
在上述代码中,数据流转路径为:
sequenceDiagram
participant Frontend as 前端
participant Controller as Controller 控制层
participant Mapper as Mapper 映射器
participant Service as Service 业务层
participant Repository as Repository 数据访问层
participant DB as 数据库
%% 前端请求提交
Frontend->>Controller: 1. 提交 JSON 请求(UserDTO)
%% 控制器调用 Mapper
Controller->>Mapper: 2. DTO → Entity
Mapper-->>Controller: 3. 返回 Entity
%% 控制器调用服务层
Controller->>Service: 4. 调用 createUser(Entity)
%% 服务层、数据访问层处理
Service->>Repository: 5. save(Entity)
Repository->>DB: 6. 执行 INSERT
DB-->>Repository: 7. 返回保存结果
Repository-->>Service: 8. 返回 Entity
Service-->>Controller: 9. 返回 Entity
%% Controller 映射 VO
Controller->>Mapper: 10. Entity → VO
Mapper-->>Controller: 11. 返回 UserVO
%% 响应给前端
Controller-->>Frontend: 12. 响应 UserVO
mermaid语言画的图
- 请求路径: 前端 → Controller(DTO) → Mapper → Service(Entity) → Repository → DB
- 响应路径: DB → Repository(Entity) → Service → Controller → Mapper(VO) → 前端
- 转换节点:
入口:DTO → Entity (通过Mapper)
出口:Entity → VO (通过Mapper)
使用DTO/VO可以解耦,具体体现在哪些方面?
解耦体现的关键价值
层次 | 解耦点 | 说明 |
数据 → 请求 | DTO | 防止前端影响数据库字段 |
数据 → 响应 | VO | 防止数据库字段暴露 |
Controller → Service | 使用结构体封装 | 参数/返回值清晰,接口定义独立 |
数据库表 → 系统结构 | Entity 封装底层结构 | 改表不改业务模型 |
使用DTO/VO与不使用DTO/VO的对比
不使用 DTO / VO(紧耦合):
public class User {
private Long id;
private String username;
private String password; // 不应该返回给前端
private Timestamp createdAt;
}
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userRepository.findById(id).orElse(null);
}
出现的问题:
- 前端能拿到 password;
- 表结构改了(比如换成 hashedPassword),前端也会被影响;
- Entity 里如果加入展示逻辑(如时间格式化),违反职责单一。
使用 DTO + VO(松耦合):
// 数据库映射用 Entity
public class UserEntity {
private Long id;
private String username;
private String password;
private Timestamp createdAt;
}
// 接收前端注册数据
public class UserRegisterDTO {
private String username;
private String password;
}
// 返回前端展示数据
public class UserVO {
private String username;
private String createdTime; // 格式化后的时间
}
好处:
- 前端永远不会知道你数据库里字段长啥样;
- Entity 可以自由改结构,VO 只要字段名一致就不会受影响;
- 可以根据前端需求增加字段而不动数据库结构。
DTO / VO 使用建议
总结:除了 GET,一般所有修改类请求(POST / PUT / PATCH)都应该使用 DTO 入参、VO 出参,目的是“结构清晰 + 安全解耦”。