在很多情况下前端页面或者其他客户端和后台交互提交数据都是单条数据的更新和插入,
但是在有些场景下,基于特定的业务客户端需要一列表的方式提交数据,我们传统的解决方案是讲苦中的数据删除,然后将客户端传来的数据列表批量插入,但是这样就有很多弊
弊端,1)有些数据根本没有变动,而经历了一此删除和插入,2)增加了数据库交互次数,删除和插入会带来数据锁定,从而带来额外的性能损耗。接下来我们将根据实际案例分析来实现将提交数据列表和库中数据对比来避免上述问题
在crm2.0系统退费业务中,门店红娘主任发起退费申请,需要上传相应的pos小票,解除服务协议,委托书等图片信息,图片上传到资源服务器后会将信息存放到RefundInfoImg表
中通过退费id和退费单关联;此后需要门店财务审批和退费组核实操作,这两步骤操作人员都可以修改图片信息(发现错误后重新上传或者删除操作),审批提交的时候,前端提交
一个人imgs信息列表,如果是新上传图片没有id信息,如果是之前在库图片信息提交时会
存在id信息.
列表提交到后台,一般的解决方案是将库中改退费id对应的图片信息删除,然后将前端提交的列表保存在数据库,但是增加了数据库交互次数并且存在性能问题.
前端传来的图片列表信息在入库之前,和库中的数据对比分析得出哪些数据那要新增,哪些数据需要更新,哪些数据需要删除,然后在执行持久化操作
在工程中需要添加一下包中的几个类:
1. IComparator:比较接口
2. AbstractComparator:对比抽象类,实现了一些通用操作,一些自定义操作使用末班方法交给子类去实现
3. CompareContext:对比上下文,也可以理解为一个容器,对比的数据都是从该类实例中获取
4. CompareRule:对比规则,使用者可以根据自身需要定义特定的比较规则
5. CompareResult:比较结果,比较完成后比较器会将结果(新增信息,更新信息,删除信息)放入此类实例返回
6. UserComparetor:这是一个自定义比较器,根据需要自己实现(该案例中我们比较用户信息)
下边贴出了各个类的代码实现
IComparator:
/**
* 执行比较的接口
*
* @author Typhoon
* @date 2017-09-17 10:20 Sunday
* @since V2.0
* @param <S>
* @param <T>
*/
public abstract interface IComparator<S,T> {
/**
* 执行比较操作
*
* @param paramCompareContext
* @return
*/
public abstract CompareResult<T> compare(CompareContext paramCompareContext);
/**
* 获取默认的对比规则
*
* @return
*/
public abstract CompareRule getDefaultRule();
/**
* 创建的时候执行一些初始化操作
*
* @param paramS
* @return
*/
public abstract T onCreate(S paramS);
/**
* 更新的时候执行初始化操作
*
* @param paramS
* @param paramT
*/
public abstract void onUpdate(S paramS, T paramT);
/**
* 删除的时候执行初始化操作
*
* @param paramT
*/
public abstract void onDelete(T paramT);
}
AbstractComparator:
/**
* 对比
*
* @author Typhoon
*
* @param <S>
* @param <T>
*/
public abstract class AbstractComparator<S, T> implements IComparator<S, T> {
private Logger logger = LoggerFactory.getLogger(AbstractComparator.class);
private CompareRule defaultRule;
public AbstractComparator() {
}
public CompareRule getDefaultRule() {
return this.defaultRule;
}
public void setDefaultRule(CompareRule defaultRule) {
this.defaultRule = defaultRule;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public CompareResult<T> compare(CompareContext context) {
CompareRule rule = (context.getRule() != null) ? context.getRule() : getDefaultRule();
Assert.notNull(rule, "CompareRule can't be null.");
Object source = context.getSource();
Object target = context.getTarget();
Assert.notNull(source, "Source can't be null.");
Assert.notNull(target, "Target can't be null.");
if ((source instanceof Collection) && (target instanceof Collection)) {// 如果都是集合类型,直接比较
return doCompare((Collection) source, (Collection) target, rule);
}
Collection sourceList;
if ((!(source instanceof Collection)) && (!(target instanceof Collection))) {// 如果都不是集合类型,转换成list比较
sourceList = new ArrayList(1);// 避免初始化过多内存空间
sourceList.add(source);
Collection targetList = new ArrayList();
targetList.add(target);
return doCompare(sourceList, targetList, rule);
}
if ((!(source instanceof Collection)) && (target instanceof Collection)) {
sourceList = new ArrayList(1);
sourceList.add(source);
return doCompare(sourceList, (Collection) target, rule);
}
if ((source instanceof Collection) && (!(target instanceof Collection))) {
Collection targetList = new ArrayList();
targetList.add(target);
return doCompare((Collection) source, targetList, rule);
}
throw new IllegalArgumentException(String.format("Not support compare %s vs %s", new Object[] { source, target }));
}
public void onUpdate(S source, T target) {
}
public void onDelete(T target) {
}
/**
* 真正的执行对比操作
*
* @author Typhoon
* @date 2017-09-17 10:25 Sunday
* @since V2.0
* @param source
* @param target
* @param rule
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected CompareResult<T> doCompare(Collection<S> source, Collection<T> target, CompareRule rule) {
this.logger.info("comparing...");
long start = System.currentTimeMillis();
CompareResult<T> result = new CompareResult<>();
T tmpTarget;
Iterator i$;
if (null != source && !source.isEmpty()) {// 先遍历源数据,可以对比出需要增加和更新的内容
tmpTarget = null;
for (i=source.iterator();i.hasNext();) {
S s = (S) i$.next();
if (isNeedCreate(s, target, rule)) {// ①先检查是否有需要新增的
tmpTarget = onCreate(s);// 初始化一些属性
// 将源数据元素加入到新增列表
result.getNewList().add(tmpTarget);
} else if ((tmpTarget = getUpdateObject(s, target, rule)) != null) {// ②检查是否需要更新
// 有相等的元素
// 获取目标类的hashcode
int beforeUpdateHash = HashCodeBuilder.reflectionHashCode(tmpTarget, true);
if (rule.isAutoUpdate()) {// 如果需要自动更新值,直接将源数据值复制到目标类中
copyProperties(s, tmpTarget);
}
onUpdate(s, tmpTarget);// 触发更新的时候做额外一些业务,钩子方法
// 获取赋值后的目标数据hashcode,其实可以理解为源数据hashcode
int afterUpdateHash = HashCodeBuilder.reflectionHashCode(tmpTarget, true);
if (beforeUpdateHash != afterUpdateHash) {// 如果不一致,就放入需要更新的列表中
result.getUpdateList().add(tmpTarget);
}
}
}
}
// Iterator i$;
if ((target != null) && (target.size() > 0)) {
for (i=target.iterator();i.hasNext();) {
T t = (T) i$.next();
// 将目标列表元素和源数据列表对比,如果源数据中没有,说明该元素需要删除
if (isNeedDelete(source, t, rule)) {
onDelete(t);
result.getDeleteList().add(t);
}
}
}
long end = System.currentTimeMillis();
this.logger.info("complete..time-consuming : " + (end - start) + "ms");
return result;
}
/**
* 将源数据内容复制到目标数据中
*
* @param source
* @param target
*/
protected void copyProperties(S source, T target) {
try {
PropertyUtils.copyProperties(target, source);
} catch (Exception e) {
this.logger.error("Error occur:", e);
}
}
/**
* 根据对比规则和数据返回唯一结果
*
* @author Typhoon
* @date 2017-09-17 10:41 Sunday
* @since V2.0
* @param obj
* @param fields
* @param joinChar
* @param hash
* @return
*/
private String getCompareValue(Object obj, String[] fields, String joinChar, boolean hash) {
Assert.notNull(obj, "Object can't be null.");
Assert.notEmpty(fields, "Compare fields can't be empty.");
StringBuffer sb = new StringBuffer();
try {
// 用标记把value连起来
Object tmp = null;
for (String field : fields) {// 将对比规则中需要比较的属性和对应的值使用连接符号拼接起来,类似id_1
if ((joinChar != null) && (sb.length() > 0)) {
sb.append(joinChar);
}
tmp = PropertyUtils.getProperty(obj, field);
sb.append((tmp == null) ? "" : tmp.toString());
}
} catch (Exception e) {
this.logger.error("Error occur:", e);
}
String value = sb.toString();
return ((hash) ? Md5.getMD5ofStr(value) : value);// 将拼接结果转换成字符串后返回(唯一字符串)
}
/**
* 判断源数据和目标数据是否相等
* <p>
* 比较规则自定义
* </p>
*
* @author Typhoon
* @date 2017-09-17 10:34 Sunday
* @since V2.0
* @param source
* @param target
* @param rule
* @return
*/
private boolean equals(Object source, Object target, CompareRule rule) {
Assert.notNull(rule, "CompareRule can't be null.");
// 根据属性比较两个对象是否相等
String sValue = getCompareValue(source, rule.getSourceAttrs(), rule.getJoinChar(), rule.isHash());
String tValue = getCompareValue(target, rule.getTargetAttrs(), rule.getJoinChar(), rule.isHash());
return sValue.equals(tValue);
}
/**
* 由源数据单元素和目标列表对比,检查是否需要新增
*
* @author Typhoon
* @date 2017-09-17 10:29 Sunday
* @since V2.0
* @param s 源数据单个元素
* @param target 目标列表
* @param rule 比较规则
* @return
*/
private boolean isNeedCreate(S s, Collection<T> target, CompareRule rule) {
Iterator<T> i$;
if (null == target || target.isEmpty()) {// 目标没有数据,直接判定需要新增
return true;
}
for (i=target.iterator();i.hasNext();) {
Object t = i$.next();
if (equals(s, t, rule)) {// 如果找到目标列表中与源数据匹配的数据,说明改数据存在,不需要新增
return false;
}
}
return true;
}
/**
* 从源数据获取需要更新的元素
*
* @param s
* @param target
* @param rule
* @return
*/
private T getUpdateObject(S s, Collection<T> target, CompareRule rule) {
Iterator<T> i$;
if (null == target || target.isEmpty()) {
return null;
}
for (i=target.iterator();i.hasNext();) {
T t = i$.next();
if (equals(s, t, rule)) {// 如果有判定属性相等的内容,返回目标列表中的该元素
return t;
}
}
return null;
}
/**
* 检查是否需要删除
*
* @author Typhoon
* @date 2017-09-17 11:02 Sunday
* @since V2.0
* @param source
* @param t
* @param rule
* @return
*/
private boolean isNeedDelete(Collection<S> source, T t, CompareRule rule) {
Iterator<S> i$;
if (null == source || source.isEmpty()) {
return true;
}
for (i=source.iterator();i.hasNext();) {
Object s = i$.next();
if (equals(s, t, rule)) {
return false;
}
}
return true;
}
CompareContext:
/**
* 对比上下文(容器)
*
* @author Typhoon
* @date 2017-09-17 10:18 Sunday
* @since V2.0
*/
public class CompareContext {
/**
* 源数据
*/
private Object source;
/**
* 目标数据
*/
private Object target;
/**
* 对比规则
*/
private CompareRule rule;
public CompareContext() {
}
public CompareContext(Object source, Object target) {
this(source, target, null);
}
public CompareContext(Object source, Object target, CompareRule rule) {
this.source = source;
this.target = target;
this.rule = rule;
}
public Object getSource() {
return this.source;
}
public void setSource(Object source) {
this.source = source;
}
public Object getTarget() {
return this.target;
}
public void setTarget(Object target) {
this.target = target;
}
public CompareRule getRule() {
return this.rule;
}
public void setRule(CompareRule rule) {
this.rule = rule;
}
}
CompareRule:
/**
* 对比规则
*
* @author Typhoon
* @date 2017-09-17 11:07 Sunday
* @since V2.0
*/
public class CompareRule {
/**
* 源数据属性
*/
private String[] sourceAttrs;
/**
* 目标属性
*/
private String[] targetAttrs;
/**
* 连接字符
*/
private String joinChar;
/**
* 是否需要hash计算
*/
private boolean hash;
/**
* 是否自行更新
*/
private boolean autoUpdate;
public CompareRule() {
this.joinChar = "_";
}
public String[] getSourceAttrs() {
return this.sourceAttrs;
}
public void setSourceAttrs(String[] sourceAttrs) {
this.sourceAttrs = ((String[]) ArrayUtils.clone(sourceAttrs));
}
public String[] getTargetAttrs() {
return this.targetAttrs;
}
public void setTargetAttrs(String[] targetAttrs) {
this.targetAttrs = ((String[]) ArrayUtils.clone(targetAttrs));
}
public String getJoinChar() {
return this.joinChar;
}
public void setJoinChar(String joinChar) {
this.joinChar = joinChar;
}
public boolean isHash() {
return this.hash;
}
public void setHash(boolean hash) {
this.hash = hash;
}
public boolean isAutoUpdate() {
return this.autoUpdate;
}
public void setAutoUpdate(boolean autoUpdate) {
this.autoUpdate = autoUpdate;
}
}
CompareResult:
/**
* 对比结果容器
*
* @author Typhoon
* @date 2017-09-17 11:04 Sunday
* @since V2.0
* @param <T>
*/
public class CompareResult<T> {
/**
* 需要更新的数据列表
*/
private List<T> updateList;
/**
* 需要删除的数据列表
*/
private List<T> deleteList;
/**
* 需要新增的数据列表
*/
private List<T> newList;
public CompareResult() {
this.updateList = new ArrayList<>();
this.deleteList = new ArrayList<>();
this.newList = new ArrayList<>();
}
/**
* 总共需要改变的数量
*
* @author Typhoon
* @date 2017-09-17 11:05 Sunday
* @since V2.0
* @return
*/
public int getChangeCount() {
return (this.updateList.size() + this.deleteList.size() + this.newList.size());
}
public List<T> getUpdateList() {
return this.updateList;
}
public void setUpdateList(List<T> updateList) {
this.updateList = updateList;
}
public List<T> getDeleteList() {
return this.deleteList;
}
public void setDeleteList(List<T> deleteList) {
this.deleteList = deleteList;
}
public List<T> getNewList() {
return this.newList;
}
public void setNewList(List<T> newList) {
this.newList = newList;
}
}
UserComparator:
/**
* 用户信息比较器
*
* @author Typhoon
* @date 2017-09-17 11:12 Sunday
* @since V2.0
*/
public class UserComparetor extends AbstractComparator<User, User> {
@Override
public User onCreate(User paramS) {
paramS.setCreateTime(new Date());
return paramS;
}
}
下边是具体业务实现:
然后编写单元测试类模拟客户端提交:
/**
* 模拟用户信息对比客户端
*
* @author Typhoon
*
*/
public class UserCompareConsumer {
public static void main(String[] args) throws ParseException {
List<User> sourceList = new ArrayList<>(2);
User u = new User();
u.setName("aeolus");
sourceList.add(u);
u = new User();
u.setId(2L);
u.setName("typhoon");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
u.setCreateTime(format.parse("2017-09-17 11:14:03"));
sourceList.add(u);
new ClassPathXmlApplicationContext("spring-root.xml").start();
UserService userService = SpringContextUtil.getBean("userService", UserService.class);
CompareResult<User> result = userService.compareUserList(sourceList);
System.out.println(JSON.toJSONString(result));
}
运行程序得到如下结果:
将结果格式化:
查看数据库中的目标数据如下:
对比分析,我们已经计算出了需要新增,更新和删除的数据,接下来自己实现响应的数据持久化操作就可以了
这种方式是牺牲一定的java性能,来换取数据库操作的性能,从逻辑层面和性能层面都是划得来的。如果发现有瑕疵或者纰漏的地方,还请各位多多指正!
本文分享自 PersistentCoder 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有