对象转换是指将一种类型的对象(如数据库实体 UserDO)转换为另一种类型的对象(如前端响应对象 UserVO 或服务层 DTO)。例如,一个 UserDO 包含用户 ID、姓名和部门 ID,我们需要将其转换为 UserVO,包含 ID、姓名和部门名称。这种转换在分层架构(如 Controller、Service、DAO)中非常常见。
数据翻译是指将某个字段的值“翻译”为另一个值。例如,UserVO 的 deptId 字段需要读取数据库中 DeptDO 的 name 字段,设置为 UserVO 的 deptName 字段。这种操作通常涉及关联查询或手动拼接。
项目中提供了两种对象转换工具:MapStruct 和 BeanUtils。我们先来对比它们的优缺点,然后分别展示使用方法。
MapStruct 是一个编译时生成映射代码的框架,通过注解(如 @Mapper 和 @Mapping)定义映射规则,生成高效的 getter/setter 调用代码。
引入依赖
org.mapstruct
mapstruct
${mapstruct.version}
public class UserDO {
private Long id;
private String name;
private Long deptId;
public UserDO() {}
public UserDO(Long id, String name, Long deptId) {
this.id = id;
this.name = name;
this.deptId = deptId;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Long getDeptId() { return deptId; }
public void setDeptId(Long deptId) { this.deptId = deptId; }
}
public class DeptDO {
private Long id;
private String name;
public DeptDO() {}
public DeptDO(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
public class UserVO {
private Long id;
private String name;
private String deptName;
public UserVO() {}
public UserVO(Long id, String name, String deptName) {
this.id = id;
this.name = name;
this.deptName = deptName;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDeptName() { return deptName; }
public void setDeptName(String deptName) { this.deptName = deptName; }
}
@Mapper
public interface UserConvert {
UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
@Mapping(source = "user.id", target = "id")
@Mapping(source = "user.name", target = "name")
@Mapping(source = "dept.name", target = "deptName")
UserVO convert(UserDO user, DeptDO dept);
default List convertList(List users, Map deptMap) {
return users.stream()
.map(user -> convert(user, deptMap.get(user.getDeptId())))
.toList();
}
}
source = "user.id", target = "id"
:将 user 对象的 id 字段映射到 VO 的 id 字段source = "user.name", target = "name"
:将 user 对象的 name 字段映射到 VO 的 name 字段source = "dept.name", target = "deptName"
:将 dept 对象的 name 字段映射到 VO 的 deptName 字段BeanUtils(项目基于 Hutool 的 BeanUtil 封装)通过反射复制字段,适合简单场景。
public class UserConvertBeanUtils {
/**
* 将用户数据对象(UserDO)和部门数据对象(DeptDO)转换为用户视图对象(UserVO)
*
* 转换逻辑:
* 1. 先通过 BeanUtil 将 UserDO 自动转换为 UserVO(自动映射 id、name 字段)
* 2. 手动将 DeptDO 的 name 字段赋值给 UserVO 的 deptName 字段
*
* @param user 用户数据对象(包含 id、name、deptId)
* @param dept 部门数据对象(包含 id、name,可为 null)
* @return 转换后的用户视图对象(UserVO),若 dept 为 null 则 deptName 为 null
*/
public static UserVO convert(UserDO user, DeptDO dept) {
// 1. 自动转换 UserDO -> UserVO(映射 id、name 字段)
UserVO userVO = BeanUtil.toBean(user, UserVO.class);
// 2. 手动填充部门名称(若部门对象不为 null)
if (dept != null) {
userVO.setDeptName(dept.getName()); // 将 DeptDO 的 name 赋值给 UserVO 的 deptName
}
return userVO;
}
/**
* 扩展转换方法:支持在转换后对 UserVO 进行自定义处理
*
* 转换逻辑:
* 1. 继承 convert() 方法的逻辑(自动映射 + 手动填充部门名称)
* 2. 通过 Consumer 对转换后的 UserVO 进行额外处理(如属性校验、补充数据等)
*
* @param user 用户数据对象
* @param dept 部门数据对象(可为 null)
* @param consumer 自定义处理器,用于对 UserVO 进行后处理(如 set 其他属性)
* @return 处理后的用户视图对象
*/
public static UserVO convertWithConsumer(UserDO user, DeptDO dept, Consumer consumer) {
// 先执行基础转换(自动映射 + 部门名称填充)
UserVO userVO = convert(user, dept);
// 调用自定义处理器(若 consumer 不为 null)
if (consumer != null) {
consumer.accept(userVO); // 将 UserVO 传入处理器进行额外处理
}
return userVO;
}
/**
* 将用户数据对象列表转换为用户视图对象列表
*
* 转换逻辑:
* 1. 遍历用户列表,逐个调用 convert() 方法进行转换
* 2. 通过部门 ID(deptId)从部门映射表(deptMap)中获取对应的 DeptDO 对象
*
* @param users 用户数据对象列表(不可为 null)
* @param deptMap 部门映射表(key=部门 ID,value=DeptDO 对象)
* @return 转换后的用户视图对象列表
*/
public static List convertList(List users, Map deptMap) {
return users.stream() // 将列表转换为流
.map(user ->
// 对每个用户对象,根据 deptId 从 deptMap 中获取部门对象,再调用 convert() 转换
convert(user, deptMap.get(user.getDeptId()))
)
.collect(Collectors.toList()); // 收集结果为列表
}
}
输入:
UserDO user = new UserDO(1L, "Alice", 10L);
DeptDO dept = new DeptDO(10L, "工程部");
Map deptMap = new HashMap<>();
deptMap.put(10L, new DeptDO(10L, "工程部"));
List users = Arrays.asList(user);
代码:
// 简单场景
UserVO userVO = UserConvertBeanUtils.convert(user, dept);
// 复杂场景(添加额外字段)
UserVO userVOWithConsumer = UserConvertBeanUtils.convertWithConsumer(user, dept, vo -> vo.setDeptName("自定义-" + vo.getDeptName()));
// 列表转换
List userVOs = UserConvertBeanUtils.convertList(users, deptMap);
输出:
// userVO: {id=1, name="Alice", deptName="工程部"}
// userVOWithConsumer: {id=1, name="Alice", deptName="自定义-工程部"}
// userVOs: [{id=1, name="Alice", deptName="工程部"}]
性能对比:MapStruct 性能优于 BeanUtils,但相比数据库操作的耗时,差距可忽略。因此,简单场景推荐 BeanUtils,复杂场景推荐 MapStruct。
数据翻译是将一个字段(如 deptId)转换为另一个字段(如 deptName)。项目提供三种方案:
推荐:优先使用 Java 拼接(方案二)或 easy-trans(方案三),因为:
public class OperateLogRespVO implements VO {
private Long id; // 操作日志ID
private Long userId; // 用户ID,关联AdminUserDO的id字段
// @Trans注解:声明该字段需要从其他对象转换而来
@Trans(type = "SIMPLE", target = AdminUserDO.class, fields = "nickname", ref = "userNickname")
private String userNickname; // 存储从AdminUserDO映射过来的nickname字段
// getter和setter方法
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getUserNickname() { return userNickname; }
public void setUserNickname(String userNickname) { this.userNickname = userNickname; }
}
public class AdminUserDO {
private Long id; // 用户ID
private String nickname; // 用户昵称
// 无参构造函数
public AdminUserDO() {}
// 全参构造函数
public AdminUserDO(Long id, String nickname) {
this.id = id;
this.nickname = nickname;
}
// getter和setter方法
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getNickname() { return nickname; }
public void setNickname(String nickname) { this.nickname = nickname; }
}
输入:
OperateLogRespVO logVO = new OperateLogRespVO();
logVO.setId(1L);
logVO.setUserId(1L);
// 假设数据库中 AdminUserDO(1L, "Alice") 存在
代码:
// Spring MVC 自动翻译(easy-trans 全局配置)
return logVO;
输出:
// logVO: {id=1, userId=1, userNickname="Alice"}
实现原理
场景:在 yudao-module-crm 模块的 CrmProductRespVO 中,将 ownerUserId 翻译为 AdminUserDO 的 nickname。
/**
* CRM产品响应视图对象
* 用于封装产品信息并返回给前端,包含产品基本信息和关联的所有者用户昵称
*/
public class CrmProductRespVO implements VO {
private Long id; // 产品ID,唯一标识一个产品
private Long ownerUserId; // 产品所有者的用户ID,关联到AdminUserDO的id字段
/**
* 产品所有者的昵称,通过@Trans注解自动映射
* 映射规则:
* - type="SIMPLE":简单类型映射
* - targetClassName:指定目标数据对象类的全限定名
* - fields="nickname":从AdminUserDO中获取nickname字段的值
* - ref="ownerNickname":将获取的值映射到当前类的ownerNickname字段
*/
@Trans(type = "SIMPLE", targetClassName = "com.example.model.AdminUserDO", fields = "nickname", ref = "ownerNickname")
private String ownerNickname; // 存储从AdminUserDO映射过来的nickname字段值
// 以下是各字段的Getter和Setter方法
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getOwnerUserId() { return ownerUserId; }
public void setOwnerUserId(Long ownerUserId) { this.ownerUserId = ownerUserId; }
public String getOwnerNickname() { return ownerNickname; }
public void setOwnerNickname(String ownerNickname) { this.ownerNickname = ownerNickname; }
}
输入:
OperateLogRespVO logVO = new OperateLogRespVO();
logVO.setId(1L);
logVO.setUserId(1L);
// 假设数据库中 AdminUserDO(1L, "Alice") 存在
代码:
// Spring MVC 自动翻译(easy-trans 全局配置)
return logVO;
输出:
// logVO: {id=1, userId=1, userNickname="Alice"}
场景:导出 UserVO 列表到 Excel,翻译 deptId 为 deptName。
/**
* 用户数据导出工具类
* 负责生成用户数据并进行数据转换,用于Excel导出
*/
public class UserExcelExport {
/**
* 导出用户列表数据
*
* 1. 创建用户数据
* 2. 调用TranslateUtils进行数据转换(将部门ID转换为部门名称)
* 3. 返回转换后的用户列表,用于Excel导出
*
* @return 转换后的用户视图对象列表
*/
public List exportUsers() {
// 创建单个用户数据(实际场景可能从数据库查询)
UserVO user = new UserVO();
user.setId(1L); // 设置用户ID
user.setName("Alice"); // 设置用户名
user.setDeptId(10L); // 设置部门ID(关联DeptDO的ID)
// 构建用户列表
List users = Arrays.asList(user);
// 调用工具类进行数据转换
// 此方法会根据@Trans注解,将deptId转换为对应的部门名称deptName
TranslateUtils.translate(users);
// 返回转换后的用户列表,此时列表中的deptName已被填充
return users;
}
}
/####################################################################/
/**
* 用户视图对象
* 用于前端展示或数据导出,包含部门名称(通过@Trans注解自动映射)
*/
public class UserVO implements VO {
private Long id; // 用户ID
private String name; // 用户名称
private Long deptId; // 部门ID(关联DeptDO的ID)
/**
* 部门名称(通过@Trans注解自动映射)
*
* type="SIMPLE": 简单类型转换
* target=DeptDO.class: 目标数据对象类
* fields="name": 从DeptDO中获取name字段
* ref="deptName": 将值映射到当前类的deptName字段
*
* TranslateUtils会根据此注解,
* 通过deptId查找对应的DeptDO对象,
* 并将其name字段值赋给当前对象的deptName字段
*/
@Trans(type = "SIMPLE", target = DeptDO.class, fields = "name", ref = "deptName")
private String deptName; // 部门名称(通过@Trans自动映射)
// 以下是各字段的Getter和Setter方法
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Long getDeptId() { return deptId; }
public void setDeptId(Long deptId) { this.deptId = deptId; }
public String getDeptName() { return deptName; }
public void setDeptName(String deptName) { this.deptName = deptName; }
}
/**
* VO 数据翻译 Utils
*/
public class TranslateUtils {
private static TransService transService;
public static void init(TransService transService) {
TranslateUtils.transService = transService;
}
/**
* 数据翻译
*
* 使用场景:无法使用 @TransMethodResult 注解的场景,只能通过手动触发翻译
*
* @param data 数据
* @return 翻译结果
*/
public static List translate(List data) {
if (CollUtil.isNotEmpty((data))) {
transService.transBatch(data);
}
return data;
}
}
@IgnoreTrans
public List getLargeData() {
// 手动翻译或避免翻译
return users;
}
@Mapper
public interface UserConvert {
@Mapping(target = "deptName", ignore = true)
UserVO convert(UserDO user, DeptDO dept);
@AfterMapping
default void afterConvert(@MappingTarget UserVO userVO, DeptDO dept) {
if (dept != null) {
userVO.setDeptName("自定义-" + dept.getName());
}
}
}
BeanUtils Consumer:通过 Consumer 添加动态逻辑:
UserVO userVO = UserConvertBeanUtils.convertWithConsumer(user, dept, vo -> {
vo.setDeptName(vo.getDeptName() + "-增强");
});
Map deptMap = deptService.getDeptMap();
List userVOs = users.stream()
.map(user -> UserConvertBeanUtils.convert(user, deptMap.get(user.getDeptId())))
.collect(Collectors.toList());
List> batches = ListUtils.partition(users, 1000);
batches.forEach(TranslateUtils::translate);