业务项目中经常有跨表对象或者跨服务的对象,对象之间使用Id关联,但是返回到调用方时,又需要根据id补充完整的关联对象。这种模式非常常用,所以写了一个工具类,简化了这个步骤
场景描述
问题中有分类信息,但是默认保存在库表中的只有分类Id(categoryId
),但是接口返回给调用方的时候,需要补充完整的Category
信息
Question.java
@Data
public class Question {
private Integer id;
/**
* 分类ID.
*/
private Integer categoryId;
private Category category;
/**
* 问题.
*/
private String question;
}
Category.java
@Data
public class Category {
private Integer id;
private String name;
}
默认情况下,从数据库中查询问题列表时,只有
categoryId
,没有Category
。Category
需要根据id再进行远程调用获取(有的是可以通过联表查询)
- 使用方式
SupplementUtil<Question, Integer, Category> supplement = SupplementUtil.<Question, Integer, Category>builder()
.idProviderForTarget(Question::getCategoryId)
.idProviderForObj(Category::getId)
.objSetter(Question::setCategory)
.build();
supplement.supplementWithConverter(questions, categoryManager::getByList);
使用这种方式可以减少很多胶水代码
SupplementUtil定义
SupplementUtil.java
/**
* 对象补充器.
* 通过Target中的ID,填充ID对应的对象Obj到Target
*
* @param <Target> 被补充的目标对象
* @param <Id> 补充对象的ID
* @param <Obj> 补充的对象
* @author tenmao
* @since 2019/11/13
*/
@Builder
public class SupplementUtil<Target, Id, Obj> {
/**
* Target中获取Obj的Id的方法.
*/
@NonNull
private Function<Target, Id> idProviderForTarget;
/**
* Obj中获取Id的方法.
*/
@NonNull
private Function<Obj, Id> idProviderForObj;
/**
* Target中设置Obj的方法.
*/
@NonNull
private BiConsumer<Target, Obj> objSetter;
/**
* 是否要求ID对应的Obj一定存在.
*/
private boolean requireExists = true;
/**
* 如果ID赌赢的Obj不存在,则使用该默认值.
*/
private Function<Id, Obj> defaultObj = null;
/**
* 使用ID到Obj的转换器来补充目标对象Targets
*
* @param targets 目标对象
* @param converter ID到Obj的转换器
*/
public void supplementWithConverter(List<Target> targets, Function<List<Id>, List<Obj>> converter) {
//获取所有Id
List<Id> ids = targets.stream().map(idProviderForTarget).filter(Objects::nonNull).collect(Collectors.toList());
if (ids.isEmpty()) {
return;
}
//获取Id列表对应的Obj列表,并转换为Map
Map<Id, Obj> objMap = converter.apply(ids).stream().collect(Collectors.toMap(idProviderForObj, Function.identity()));
supplementWithMap(targets, objMap);
}
/**
* 使用ID到Obj的Map来补充目标对象Targets
*
* @param targets 目标对象
* @param objMap ID到Obj的Map
*/
public void supplementWithMap(List<Target> targets, Map<Id, Obj> objMap) {
batchSupplement(targets, idProviderForTarget, objSetter, objMap, requireExists, defaultObj);
}
/**
* 批量根据ID扩充对象信息.
*
* @param targets 扩充对象的目标对象
* @param idProvider ID提供方法
* @param objSetter 对象设置器
* @param objMap ID到对象的Map
* @param requireExists 是否要求必须(如果是,但是没有存在,则会抛出异常{@link IllegalArgumentException})
* @param defaultObj 如果没有匹配到则使用默认值
* @param <Target> 扩充对象的目标对象
* @param <Id> ID
* @param <Obj> ID对应的对象
*/
public static <Target, Id, Obj> void batchSupplement(List<Target> targets,
Function<Target, Id> idProvider,
BiConsumer<Target, Obj> objSetter,
Map<Id, Obj> objMap,
boolean requireExists,
Function<Id, Obj> defaultObj) {
if (targets == null || idProvider == null || objSetter == null || objMap == null) {
throw new IllegalArgumentException("parameter for supplement should not be empty");
}
for (Target target : targets) {
Id id = idProvider.apply(target);
if (id != null) {
Obj o = objMap.get(id);
if (o == null && requireExists) {
throw new IllegalArgumentException(String.format("id:%s cannot convert to object", id));
}
objSetter.accept(target, o != null ? o : defaultObj.apply(id));
}
}
}
}
网友评论