Javers JPA与DTO映射:findShadows结果如何转DTO并保留审计数据
Javers Shadow查询结果Entity转DTO的最佳实践
不要尝试直接替换Shadow内部持有的实体对象,也没必要自定义Shadow的扩展实现。Shadow是Javers的内置不可变值对象,内部和快照存储、提交元数据逻辑深度耦合,硬改内部值或者自己实现Shadow接口的维护成本极高,后续版本升级很容易出兼容问题。
最稳妥的方案是自定义独立的审计返回结构,完全和Javers内部实现解耦,步骤如下:
- 定义通用的审计快照包装类,把业务DTO和审计字段分开承载
public class AuditResult<T> { // 业务DTO内容 private T data; // 审计通用字段 private String commitId; private String operator; private LocalDateTime operateTime; private Long version; private String operationType; // 自行补充构造方法、getter、setter }
- 遍历
findShadows的返回结果,通过Javers公开的API提取元数据,同时做业务层的Entity转DTO映射
List<Shadow<YourEntity>> shadowList = javers.findShadows(query); List<AuditResult<YourDTO>> auditResultList = shadowList.stream() .map(shadow -> { AuditResult<YourDTO> item = new AuditResult<>(); // 用你项目现有映射逻辑(MapStruct/BeanUtils/手动映射都可以)把实体转成业务DTO YourEntity entity = shadow.get(); item.setData(yourEntityMapper.toDTO(entity)); // 从Shadow公开的方法里提取审计元数据,不需要反射拆内部字段 CommitMetadata commitInfo = shadow.getCommitMetadata(); item.setCommitId(commitInfo.getId().toString()); item.setOperator(commitInfo.getAuthor()); item.setOperateTime(commitInfo.getCommitDate()); item.setVersion(shadow.getSnapshot().getVersion()); item.setOperationType(shadow.getSnapshot().getType().name()); return item; }) .toList();
几个避坑点
- 不要用反射修改Shadow实例的内部字段:Shadow默认是不可变的,部分实现类的核心字段是final修饰的,而且Javers内部对快照做了缓存,强行修改会污染同查询条件下的缓存结果。
- 不需要为了取变更字段拆解Shadow结构:如果需要展示具体字段的修改前后值,直接调用
javers.findChanges(query)获取变更列表,和上述结果按commitId、version关联即可。 - Shadow返回的实体是Javers从自身快照存储反序列化生成的,不是JPA/ORM托管的实体对象,不存在懒加载问题,直接做字段映射就行,不需要额外做数据库查询补全字段。
如果你的场景强依赖Shadow接口的部分特殊方法,再考虑自定义Shadow包装类,但90%以上的业务场景用上述独立包装类的方案就足够,灵活度最高,也不会受Javers版本迭代的影响。
内容的提问来源于stack exchange,提问作者Avec
相关产品推荐
相关产品推荐

