背景
做一个从mongoDB中实时查询并转化为月份进行统计功能,本地测试速度正常,没有什么影响,部署到线上,自己测试了下功能发现整体流程下来花费8秒时间.
定位问题
起初以为是mongoDB查询的问题,有过这种类似的经历,准备着手去解决mongoDB慢查询问题,但是后面使用本地环境连接线上mongoDB定位问题的时候,发现查询时间很短只有100ms,可以接受.排除了这个问题
测试代码:
long fir = System.currentTimeMillis();
List<DvsCarOnline> onlineList = dvsCarOnlineService.listCarByMonthAndOrgName(startDate,endDate,orgName,upAreaCodes);
long sec = System.currentTimeMillis();
// ......
long three = System.currentTimeMillis();
log.error("总组装耗时"+(three-fir));
log.error("第一段查询"+(sec-fir));
使用 System.currentTimeMillis();来确认消耗的时间
而在整体的时间中,发现组装数据的时长最长,尤其集中在第一段查询中,占据了99%的时间,但是是很不应该的事情,组装数据是很快的不会出现这种问题,除非是for循环嵌套,导致复杂度上升,而其中有这样一个代码片段
for(DvsCarOnline dvs:onlineList){
DvsCarInstallVo dvsCarInstallVo=new DvsCarInstallVo();
Beans.copyPropertiesNotNull(dvsCarInstallVo,dvs);
// ......
}
使用了项目中封装的工具类
Beans.copyPropertiesNotNull();
查看源码后,这下能确认,是因为这个工具类方法引出的问题.
解决方法
这里当时偷了个懒,使用Beans去赋值属性,结果出现了大问题,改成给Vo赋值的格式解决问题
for(DvsCarOnline dvs:onlineList){
DvsCarInstallVo dvsCarInstallVo=new DvsCarInstallVo();
/*
* 弃用Beans.copy 浪费时间太
* Beans.copyPropertiesNotNull(dvsCarInstallVo,dvs);*/
dvsCarInstallVo.setCreateDate(dvs.getCreateDate());
dvsCarInstallVo.setFleetName(dvs.getFleetName());
dvsCarInstallVo.setLastStatus(dvs.getLastStatus());
dvsCarInstallVo.setOrgName(dvs.getOrgName());
dvsCarInstallVo.setPlateNo(dvs.getPlateNo());
dvsCarInstallVoList.add(dvsCarInstallVo);
//.......
}
最后时间稳定为
复盘
为什么会出现这种问题呢,就要从Beans里去找出问题了
工具类的方法
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;
/**
* 复制source中不能为空的对象
*/
public static void copyPropertiesNotNull(Object target, Object source) {
try {
NullAwareBeanUtilsBean.getInstance().copyProperties(target, source);
} catch (Exception e) {
throw Exceptions.unchecked(e);
}
}
使用的是apache的BeanUtils工具
再点入方法copyProperties(target,source);
public void copyProperties(final Object dest, final Object orig)
throws IllegalAccessException, InvocationTargetException {
// Validate existence of the specified beans
if (dest == null) {
throw new IllegalArgumentException
("No destination bean specified");
}
if (orig == null) {
throw new IllegalArgumentException("No origin bean specified");
}
if (log.isDebugEnabled()) {
log.debug("BeanUtils.copyProperties(" + dest + ", " +
orig + ")");
}
// Copy the properties, converting as necessary
if (orig instanceof DynaBean) {
final DynaProperty[] origDescriptors =
((DynaBean) orig).getDynaClass().getDynaProperties();
for (DynaProperty origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
// Need to check isReadable() for WrapDynaBean
// (see Jira issue# BEANUTILS-61)
if (getPropertyUtils().isReadable(orig, name) &&
getPropertyUtils().isWriteable(dest, name)) {
final Object value = ((DynaBean) orig).get(name);
copyProperty(dest, name, value);
}
}
} else if (orig instanceof Map) {
@SuppressWarnings("unchecked")
final
// Map properties are always of type <String, Object>
Map<String, Object> propMap = (Map<String, Object>) orig;
for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
final String name = entry.getKey();
if (getPropertyUtils().isWriteable(dest, name)) {
copyProperty(dest, name, entry.getValue());
}
}
} else /* if (orig is a standard JavaBean) */ {
final PropertyDescriptor[] origDescriptors =
getPropertyUtils().getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
if ("class".equals(name)) {
continue; // No point in trying to set an object's class
}
if (getPropertyUtils().isReadable(orig, name) &&
getPropertyUtils().isWriteable(dest, name)) {
try {
final Object value =
getPropertyUtils().getSimpleProperty(orig, name);
copyProperty(dest, name, value);
} catch (final NoSuchMethodException e) {
// Should not happen
}
}
}
}
}
不提代码长度和功能,进入代码上看,可以看到 方法复制值的时候,使用了for循环去执行复制的过程,而我复制的值恰好横向字段特别多,这样就浪费了很长的时间,同时又存在在for循环中,List的size()越大,速度越慢.导致最终结果还不如一个set()方法实在.
总结
这个东西使用的时候需要注意,我自己是看公司项目很多地方有在使用,自己也就在不清楚用途的情况下去使用,这点很糟糕, 也就是只看目标不思考过程和前提.这块谨记教训吧,后续要仔细去确认情况再去使用.后面就保证不会再出现这个问题
后来者也谨记教训了~~~~