背景
项目后台有一个修改告警规则的功能,对应的前端部分页面如下。
上面这个页面数据传到后台,对应的数据结构为:
一个AlarmRule里有多个AlarmTarget,一个AlarmTarget里有多个AlarmCondition。
上面这个页面数据传到后台,对应的数据结构为:
一个AlarmRule里有多个AlarmHandleFlow,一个AlarmHandleFlow里有多个AlarmHandleRule。
页面里其他地方也有类似的数据结构,此处就不全部展示了。目前导致的问题是:
由于界面涉及大量有父子关系的数据结构,当调用后台修改接口时,研发人员不好判断哪些数据是新增的、哪些是修改的、哪些是被删除的,导致目前修改接口的实现逻辑是先删除所有旧的数据,再新增新的数据。
这样做也不是不可行,只是有点“小题大作”:
如果我在页面只改了某一个元素,后台也要“大动干戈”地影响所有没有被修改的数据。
所以,需要引入一种优化方案,让后台不仅能动态获取增删改的变化实体,而且只修改有变化的实体数据。
数据结构设计
对于每种数据来说,都可能会涉及增、删、改,所以后台用泛型类ChangedEntityDTO
表示
public class ChangedEntityDTO<T> {
private List<T> updateList;
private List<T> insertList;
private List<T> deletedList;
}
其中updateList、insertList、deletedList
集合类型的成员,分别表示修改、新增、删除的数据。
接口设计
1.获取ChangedEntityDTO的接口
定义ChangedEntityDTO
结构之后,现在就需要考虑如何在后台获取ChangedEntityDTO
类和其中的变化元素。
- 首先,设计一个泛型接口来抽象父和子
public interface ChangedEntityService<PARENT, CHILDREN> {
ChangedEntityDTO<CHILDREN> getChangedEntity(PARENT parent);
}
接口说明如下:
其中
PARENT
表示父,CHILDREN
表示子。比如PARENT
代表AlarmRule,CHILDREN
代表AlarmTarget,以此类推。
getChangedEntity方法根据PARENT
类型参数返回ChangedEntityDTO<CHILDREN>
类型。
- 然后,项目原先的业务层接口需要继承
ChangedEntityService
接口
以原先的AlarmTargetService
接口为例,如下
public interface AlarmTargetService extends ChangedEntityService<AlarmRule, AlarmTarget>
- 最后,实现其中的
getChangedEntity
方法
这是整个设计的核心逻辑,即需要知道哪些是新增的、哪些是修改的、哪些是删除的。还是以AlarmTargetService
为例子,具体代码如下
@Override
public ChangedEntityDTO<AlarmTarget> getChangedEntity(AlarmRule alarmRule) {
AlarmTarget filter = new AlarmTarget();
filter.setAlarmRuleId(alarmRule.getAlarmRuleId());
List<AlarmTarget> oldList = alarmTargetDao.selectAlarmTargetList(filter);
List<AlarmTarget> newList = alarmRule.getAlarmTargetList();
//防止原引用修改
List<AlarmTarget> needInsertList = new ArrayList<>(newList);
List<AlarmTarget> needUpdateList = new ArrayList<>(newList);
List<AlarmTarget> needDeletedList = new ArrayList<>(oldList);
//获取数据库已存在的alarmTarget对象的主键alarmTargetId
List<Integer> oldIds = oldList.stream().map(AlarmTarget::getAlarmTargetId).collect(Collectors.toList());
//获取页面传过来的alarmTarget对象的主键alarmTargetId
List<Integer> newIds = newList.stream().map(AlarmTarget::getAlarmTargetId).collect(Collectors.toList());
//获取需要新增的对象
needInsertList.removeIf(alarmTarget -> oldIds.stream().anyMatch(id->id.equals(alarmTarget.getAlarmTargetId())));
log.info("需要新增的AlarmTarget对象={}", needInsertList);
//获取需要删除的对象
needDeletedList.removeIf(alarmTarget -> newIds.stream().anyMatch(id->id !=null && id.equals(alarmTarget.getAlarmTargetId())));
log.info("需要删除的AlarmTarget对象={}", needDeletedList);
//获取需要修改的对象
needUpdateList = needUpdateList.stream().filter(alarmTarget -> oldIds.stream().anyMatch(id->id.equals(alarmTarget.getAlarmTargetId()))).collect(Collectors.toList());
log.info("需要修改的AlarmTarget对象={}", needUpdateList);
//构造ChangedEntityDTO
ChangedEntityDTO<AlarmTarget> dto = new ChangedEntityDTO<>();
dto.setDeletedList(needDeletedList);
dto.setInsertList(needInsertList);
dto.setUpdateList(needUpdateList);
return dto;
}
此时,我们就获得了ChangedEntityDTO<AlarmTarget>
。
2.处理ChangedEntityDTO的接口
通过上面的接口,我们已经获取到ChangedEntityDTO<AlarmTarget>
和其中的变化元素了,现在就需要分别处理这里面的增删改实体,实现具体的业务逻辑。
- 首先,设计包含增、删、改功能的接口
public interface ChangedEntityHandlerService<PARENT, ENTITY> {
void handDeleted(List<ENTITY> entities);
void handUpdate(PARENT parent, List<ENTITY> entities) throws Exception;
void handInsert(PARENT parent, List<ENTITY> entities) throws Exception;
}
接口说明如下:
同前面接口一样,
PARENT
表示父,ENTITY
表示子。三个方法的集合类型入参,分别对应ChangedEntityDTO
里的deletedList
、updateList、
insertList。
- 然后,项目原先的业务层接口需要继承
ChangedEntityHandlerService
接口
比如,AlarmTargetService
也继承ChangedEntityHandlerService
接口
public interface AlarmTargetService extends ChangedEntityService<AlarmRule, AlarmTarget>, ChangedEntityHandlerService<AlarmRule, AlarmTarget>
- 最后,分别实现其中的增删改方法
比如,handDeleted会从数据库删除元素,handUpdate修改数据库元素,handInsert会往数据库插入元素。因为与具体业务相关,具体代码就不再展示。