简介

Controller层:入参是DTO

DTO的角色:DTO(Data Transfer Object)是数据传输对象,专为外部输入设计。它把前端传来的零散参数封装成一个对象,便于校验(比如用@Valid注解)和扩展。如果不用DTO,多个参数散落在方法签名里,代码可读性下降。

Service层:入参DTO,加工成BO

BO的角色:BO(Business Object)是业务对象,承载业务逻辑加工后的数据。这里从UserPO(数据库映射对象)转成UserBO,并在BO中添加了registerDays字段。BO不一定是“领域对象”(Domain Object,领域驱动设计DDD中的概念),而是Service层内部对业务数据的封装。

DAO层:入参是PO

PO的角色:PO(Persistent Object)是持久化对象,与数据库表结构一一对应,由MyBatis自动映射查询结果。Mapper的返回值是UserPO。

返回前端:VO出场

VO的角色:VO(Value Object)是视图对象,专为前端定制。如果直接返回UserBO,可能暴露多余字段(比如id),用VO可以精确控制输出。

自动转换策略

1.BeanUtils(Spring自带) Spring提供的BeanUtils.copyProperties可以快速复制属性

优点:简单,字段名一致时直接用。

缺点:不支持复杂转换(比如字段名不同、类型不一致),需要额外处理。

2.MapStruct(推荐) MapStruct是一个编译时生成转换代码的库,性能高且类型安全。

1️⃣引入依赖后:

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
<scope>provided</scope>
</dependency>

2️⃣定义转换接口:

1
2
3
4
5
6
7
8
9
@Mapper(componentModel = "spring")
public interface UserConverter {
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);

@Mapping(source = "registerTime", target = "registerTime") // 可自定义映射
UserBO toBO(UserPO userPO);

UserVO toVO(UserBO userBO);
}

3️⃣在Service中使用:

1
2
3
4
5
6
public UserVO getUserInfo(UserQueryDTO queryDTO) {
UserPO userPO = userMapper.selectById(queryDTO.getUserId());
UserBO userBO = UserConverter.INSTANCE.toBO(userPO);
userBO.setRegisterDays(calculateRegisterDays(userPO.getRegisterTime()));
return UserConverter.INSTANCE.toVO(userBO);
}

优点:自动生成转换代码,支持复杂映射(字段名不同、类型转换),运行时零开销。
缺点:需要学习配置,初期有一定上手成本。

总结

  • DTO:传输层,解耦外部输入。
  • BO:业务层,承载逻辑加工(非必须,视复杂度而定)。
  • PO:持久层,绑定数据库。
  • VO:视图层,适配输出。