欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > java每日精进 5.15【分页实现】

java每日精进 5.15【分页实现】

2025/5/18 13:28:38 来源:https://blog.csdn.net/weixin_51721783/article/details/147992725  浏览:    关键词:java每日精进 5.15【分页实现】

1. 什么是对象转换和数据翻译?

对象转换

对象转换是指将一种类型的对象(如数据库实体 UserDO)转换为另一种类型的对象(如前端响应对象 UserVO 或服务层 DTO)。例如,一个 UserDO 包含用户 ID、姓名和部门 ID,我们需要将其转换为 UserVO,包含 ID、姓名和部门名称。这种转换在分层架构(如 Controller、Service、DAO)中非常常见。

数据翻译

数据翻译是指将某个字段的值“翻译”为另一个值。例如,UserVO 的 deptId 字段需要读取数据库中 DeptDO 的 name 字段,设置为 UserVO 的 deptName 字段。这种操作通常涉及关联查询或手动拼接。

为什么需要这些操作?

  • 分层隔离:DO(Data Object)用于数据库操作,VO(Value Object)用于前端响应,DTO(Data Transfer Object)用于服务层传递,各自职责不同。
  • 数据格式化:前端需要友好的数据格式(如部门名称而非 ID)。
  • 性能优化:通过翻译减少复杂 SQL 联表查询。

2. 对象转换:MapStruct vs BeanUtils

项目中提供了两种对象转换工具:MapStructBeanUtils。我们先来对比它们的优缺点,然后分别展示使用方法。

2.1 MapStruct

MapStruct 是一个编译时生成映射代码的框架,通过注解(如 @Mapper 和 @Mapping)定义映射规则,生成高效的 getter/setter 调用代码。

  • 优点
    • 高性能:生成纯 Java 代码,避免反射开销。
    • 类型安全:编译时检查映射规则,减少运行时错误。
    • 灵活性:支持复杂映射、自定义逻辑。
  • 缺点
    • 需要编写注解,配置稍复杂。
    • 学习成本稍高,需了解 MapStruct 语法。
  • 适用场景:高性能要求、复杂映射场景。

引入依赖

<dependencies><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${mapstruct.version}</version></dependency></dependencies>
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<UserVO> convertList(List<UserDO> users, Map<Long, DeptDO> deptMap) {return users.stream().map(user -> convert(user, deptMap.get(user.getDeptId()))).toList();}
}
  • UserDO:数据访问层的用户对象,包含用户 ID、姓名和部门 ID
  • DeptDO:数据访问层的部门对象,包含部门 ID 和部门名称
  • UserVO:视图层的用户对象,包含用户 ID、姓名和部门名称(直接包含部门名称而非部门 ID)
单个对象转换(convert 方法)
  • @Mapper 注解:告诉 MapStruct 这是一个转换器接口,会自动生成实现类
  • INSTANCE 常量:获取 MapStruct 生成的实现类实例
  • @Mapping 注解:定义字段映射规则
    • 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 字段
列表转换(convertList 方法)
  • 这是一个默认方法(Java 8 特性),提供了批量转换的实现
  • 使用 Java Stream API 遍历 UserDO 列表
  • 对于每个 UserDO,通过 deptId 从部门映射表中获取对应的 DeptDO
  • 调用单对象转换方法 convert () 完成转换
  • 将转换后的 UserVO 收集到新列表中返回

2.2 BeanUtils

BeanUtils(项目基于 Hutool 的 BeanUtil 封装)通过反射复制字段,适合简单场景。

  • 优点
    • 简单易用:字段名一致时无需额外配置。
    • 封装增强:支持 List、Page 转换,允许 Consumer 自定义逻辑。
    • 易替换:封装一层便于切换实现(如换成 Spring 的 BeanUtils)。
  • 缺点
    • 反射导致性能略低于 MapStruct。
    • 不适合复杂字段映射。
  • 适用场景:简单映射、快速开发。
public class UserConvertBeanUtils {/*** 将用户数据对象(UserDO)和部门数据对象(DeptDO)转换为用户视图对象(UserVO)* <p>* 转换逻辑:* 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 进行自定义处理* <p>* 转换逻辑:* 1. 继承 convert() 方法的逻辑(自动映射 + 手动填充部门名称)* 2. 通过 Consumer 对转换后的 UserVO 进行额外处理(如属性校验、补充数据等)** @param user     用户数据对象* @param dept     部门数据对象(可为 null)* @param consumer 自定义处理器,用于对 UserVO 进行后处理(如 set 其他属性)* @return 处理后的用户视图对象*/public static UserVO convertWithConsumer(UserDO user, DeptDO dept, Consumer<UserVO> consumer) {// 先执行基础转换(自动映射 + 部门名称填充)UserVO userVO = convert(user, dept);// 调用自定义处理器(若 consumer 不为 null)if (consumer != null) {consumer.accept(userVO); // 将 UserVO 传入处理器进行额外处理}return userVO;}/*** 将用户数据对象列表转换为用户视图对象列表* <p>* 转换逻辑:* 1. 遍历用户列表,逐个调用 convert() 方法进行转换* 2. 通过部门 ID(deptId)从部门映射表(deptMap)中获取对应的 DeptDO 对象** @param users    用户数据对象列表(不可为 null)* @param deptMap  部门映射表(key=部门 ID,value=DeptDO 对象)* @return 转换后的用户视图对象列表*/public static List<UserVO> convertList(List<UserDO> users, Map<Long, DeptDO> 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<Long, DeptDO> deptMap = new HashMap<>();
deptMap.put(10L, new DeptDO(10L, "工程部"));
List<UserDO> users = Arrays.asList(user);
代码:
// 简单场景
UserVO userVO = UserConvertBeanUtils.convert(user, dept);
// 复杂场景(添加额外字段)
UserVO userVOWithConsumer = UserConvertBeanUtils.convertWithConsumer(user, dept, vo -> vo.setDeptName("自定义-" + vo.getDeptName()));
// 列表转换
List<UserVO> userVOs = UserConvertBeanUtils.convertList(users, deptMap);
输出:
// userVO: {id=1, name="Alice", deptName="工程部"}
// userVOWithConsumer: {id=1, name="Alice", deptName="自定义-工程部"}
// userVOs: [{id=1, name="Alice", deptName="工程部"}]
  • BeanUtil.toBean 使用反射复制字段,适合字段名一致的场景。
  • Consumer 提供灵活性,允许自定义字段处理。
  • convertList 通过 Stream API 实现列表转换。

性能对比:MapStruct 性能优于 BeanUtils,但相比数据库操作的耗时,差距可忽略。因此,简单场景推荐 BeanUtils,复杂场景推荐 MapStruct。

3. 数据翻译:SQL 联表 vs Java 拼接 vs easy-trans

数据翻译是将一个字段(如 deptId)转换为另一个字段(如 deptName)。项目提供三种方案:

  1. SQL 联表查询:通过 MyBatis 的关联查询直接获取目标字段(如 DeptDO.name)。
  2. Java 拼接:多次单表查询(如先查 UserDO,再查 DeptDO),在 Java 代码中拼接。
  3. easy-trans 框架:通过注解(如 @Trans)自动翻译字段。

推荐:优先使用 Java 拼接(方案二)或 easy-trans(方案三),因为:

  • 减少数据库压力:避免复杂的 SQL 联表查询。
  • 易维护:Java 代码逻辑清晰,SQL 改动成本高。
  • 灵活性:easy-trans 提供注解式翻译,简化开发。

3.1easy-trans

public class OperateLogRespVO implements VO {private Long id; // 操作日志IDprivate 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; // 用户IDprivate 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"}

实现原理

  • OperateLogRespVO 实现 VO 接口,启用 easy-trans 翻译。
  • @Trans(type = "SIMPLE", target = AdminUserDO.class, fields = "nickname", ref = "userNickname"):
    • type = "SIMPLE":使用 MyBatis Plus 查询 AdminUserDO。
    • target:指定目标实体类。
    • fields:读取 nickname 字段。
    • ref:设置到 userNickname 字段。
  • easy-trans 自动根据 userId 查询 AdminUserDO,填充 userNickname。

3.2 场景二:跨模块翻译(easy-trans)

场景:在 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"}

3.3 场景三:Excel 导出翻译(easy-trans)

场景:导出 UserVO 列表到 Excel,翻译 deptId 为 deptName。

/*** 用户数据导出工具类* 负责生成用户数据并进行数据转换,用于Excel导出*/
public class UserExcelExport {/*** 导出用户列表数据* * 1. 创建用户数据* 2. 调用TranslateUtils进行数据转换(将部门ID转换为部门名称)* 3. 返回转换后的用户列表,用于Excel导出* * @return 转换后的用户视图对象列表*/public List<UserVO> exportUsers() {// 创建单个用户数据(实际场景可能从数据库查询)UserVO user = new UserVO();user.setId(1L);         // 设置用户IDuser.setName("Alice");  // 设置用户名user.setDeptId(10L);    // 设置部门ID(关联DeptDO的ID)// 构建用户列表List<UserVO> users = Arrays.asList(user);// 调用工具类进行数据转换// 此方法会根据@Trans注解,将deptId转换为对应的部门名称deptNameTranslateUtils.translate(users);// 返回转换后的用户列表,此时列表中的deptName已被填充return users;}
}
/####################################################################//*** 用户视图对象* 用于前端展示或数据导出,包含部门名称(通过@Trans注解自动映射)*/
public class UserVO implements VO {private Long id;         // 用户IDprivate 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 <T extends VO> List<T> translate(List<T> data) {if (CollUtil.isNotEmpty((data))) {transService.transBatch(data);}return data;}}

4. 扩展:实用技巧与注意事项

4.1 优化性能

  • MapStruct:优先用于高并发场景,生成代码无反射开销。
  • BeanUtils:适合快速开发,但避免在高频接口中使用。
  • easy-trans:全局翻译(easy-trans.is-enable-global=true)方便但可能影响性能,建议在数据量大或树形结构时使用 @IgnoreTrans 注解:
    @IgnoreTrans
    public List<UserVO> getLargeData() {// 手动翻译或避免翻译return users;
    }

4.2 复杂逻辑处理

  • MapStruct 自定义逻辑:使用 @AfterMapping 或 default 方法处理复杂映射:
    @Mapper
    public interface UserConvert {@Mapping(target = "deptName", ignore = true)UserVO convert(UserDO user, DeptDO dept);@AfterMappingdefault 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() + "-增强");
    });

5.3 跨模块翻译优化

  • 缓存:跨模块查询(如 AdminUserDO)可能涉及多次数据库访问,建议缓存 DeptDO 或 AdminUserDO:
    Map<Long, DeptDO> deptMap = deptService.getDeptMap();
    List<UserVO> userVOs = users.stream().map(user -> UserConvertBeanUtils.convert(user, deptMap.get(user.getDeptId()))).collect(Collectors.toList());

5.4 Excel 导出优化

  • 批量翻译:TranslateUtils.translate 支持批量处理,但大数据量时建议分批:
    List<List<UserVO>> batches = ListUtils.partition(users, 1000);
    batches.forEach(TranslateUtils::translate);

5. 总结

  • 对象转换
    • MapStruct:高性能,适合复杂映射,需配置 @Mapping。
    • BeanUtils:简单易用,适合字段名一致的场景,支持 Consumer 扩展。
  • 数据翻译
    • SQL 联表:适合简单场景,但可能增加数据库压力。
    • Java 拼接:灵活,推荐多次单表查询后拼接。
    • easy-trans:注解式翻译,模块内用 target,跨模块用 targetClassName,Excel 导出用 TranslateUtils。
  • 注意事项
    • 性能敏感场景用 MapStruct 或禁用全局翻译。
    • 复杂逻辑通过 default 方法或 Consumer 处理。
    • 跨模块翻译和 Excel 导出可结合缓存优化性能。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词