公司有业务需求APP端坐标需要不停的上传,根据业务量,每个月可能会产生大概20G的数据,现有的解决方案是直接存储在oracle中根据每月进行分表存储。但是不可避免的随着时间的拉长,处理起来越来越不方便,于是便想到了使用es来存储,后面查询时也比较方便。
为了让操作方式与以前存储保持一致,保持增删查改分页查询等的需求。直接使用的spring-data-elasticsearch,只要继承ElasticsearchRepository,就可以实现简单的CURD操作,因为public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID>。首先发现这个没有修改的方法,即直接传入一个实体,对里面的字段就行修改,还有就是对分页的查询,以前我们使用的mybatisplus进行分页,传过来的分页参数和自带的不一致,以及以后的存储为动态索引,这个自带的只能查询当前索引。为了让开发人员更方便的使用,自己增加了这几个方法,以后他们开发过程中,需要继承ElasticsearchRepository的地方,直接继承我的这个接口就可以了。
@NoRepositoryBean
public interface MyEsRepository<T, ID extends Serializable> extends ElasticsearchRepository<T, ID> {
default Page getPage(T bean, Page page,QueryBuilder... queryBuilder){
BoolQueryBuilder qb =getBoolQueryBuilder(bean);
for(QueryBuilder queryBuilder1:queryBuilder){
qb.filter(queryBuilder1);
}
Pageable pageRequest=getPageable(page);
org.springframework.data.domain.Page<T> search = this.search(qb,pageRequest);
page.setRecords(search.getContent());
return page;
};
@SneakyThrows
default boolean update(T bean){
Class<T> tClass=this.getEntityClass();
ID id=null;
try {
BeanInfo beanInfo = Introspector.getBeanInfo(tClass);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
Field[] declaredFields = tClass.getDeclaredFields();
for(Field field:declaredFields){
Id annotation = field.getAnnotation(Id.class);
if(annotation!=null || field.getName().equalsIgnoreCase("id")){
field.setAccessible(true);
id=(ID)field.get(bean);
break;
}
}
T oldBean=this.findById(id).get();
if(oldBean==null){
this.save(bean);
}else{
for (PropertyDescriptor property : propertyDescriptors) {
Method getter = property.getReadMethod();
Object value = getter.invoke(bean);
if(value!=null &&(!property.getName().equals("class"))){
Field f = tClass.getDeclaredField(property.getName());
f.setAccessible(true);
f.set(oldBean, value);
}
}
this.save(oldBean);
}
} catch (Exception e) {
e.printStackTrace();
throw new Exception(e.getMessage());
}
return true;
}
default BoolQueryBuilder getBoolQueryBuilder(T bean){
Map<String,Object> map= BeanUtils.beanToMap(bean,false);
BoolQueryBuilder qb = QueryBuilders.boolQuery();
if(map!=null){
for (Map.Entry<String, Object> entry : map.entrySet()) {
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery(entry.getKey(),entry.getValue());
queryBuilder.minimumShouldMatch("100%");
qb.filter(queryBuilder);
}
}
return qb;
}
default Pageable getPageable(Page page){
List<OrderItem> orders = page.getOrders();
Pageable pageRequest;
if(orders.size()>0){
List<String> asc=new ArrayList();
List<String> desc=new ArrayList();
orders.forEach(item->{
if(item.isAsc()){
asc.add(item.getColumn());
}else{
desc.add(item.getColumn());
}
});
Sort sort;
if(asc.size()>0 && desc.size()>0){
sort=new Sort(Sort.Direction.ASC,asc);
Sort sort1=new Sort(Sort.Direction.DESC,desc);
sort.and(sort1);
}else if(asc.size()>0){
sort=new Sort(Sort.Direction.ASC,asc);
}else{
sort=new Sort(Sort.Direction.DESC,desc);
}
pageRequest = PageRequest.of(Long.valueOf(page.getCurrent()).intValue()-1,
Long.valueOf(page.getSize()).intValue(),sort);
}else{
pageRequest = PageRequest.of(Long.valueOf(page.getCurrent()).intValue()-1,
Long.valueOf(page.getSize()).intValue());
}
return pageRequest;
}
default Page getAllPage(T bean, Page page,int DyMonth,ElasticsearchTemplate elasticsearchTemplate, QueryBuilder... queryBuilder){
BoolQueryBuilder qb =getBoolQueryBuilder(bean);
Pageable pageRequest=getPageable(page);
Class<T> tClass=this.getEntityClass();
Document annotation = tClass.getAnnotation(Document.class);
String[] indexName = annotation.indexName().split("_");
String[] indexs=new String[DyMonth];
for(int i=0;i<DyMonth;i++){
indexs[i]=indexName[0]+"_"+ Date8Util.minusMonthstoString(i);
}
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withIndices(indexs).withQuery(qb)
.withPageable(pageRequest);
for(QueryBuilder queryBuilder1:queryBuilder){
nativeSearchQueryBuilder.withQuery(queryBuilder1);
}
List<T> esGpsStatuses = elasticsearchTemplate.queryForList(nativeSearchQueryBuilder.build(), tClass);
// org.springframework.data.domain.Page<T> search = this.search(qb,pageRequest);
page.setRecords(esGpsStatuses);
return page;
};
default String getIndex(){
Class<T> tClass=this.getEntityClass();
Document annotation = tClass.getAnnotation(Document.class);
String[] indexName = annotation.indexName().split("_");
return indexName[0];
}
}
分页查询,增加了动态参数,为了方便他们自己进行设置查询参数。
然后开始开发,首先测试排序,我们是对时间进行排序查询,发现报错all shards failed。原来排序的字段需要设置fielddata=true。经测试FieldSortBuilder idSort = SortBuilders.fieldSort("systemtime").order(SortOrder.ASC).unmappedType("Date");通过这种方式设置不管用。因为es里已经存在数据了,需要去修改mapping.或者删了索引,重新来哈。不过在建实体的时候对需要排序的字段加上@Field(fielddata=true),不过存储的时候需要记住,这个字段不能为空了,如果存储的时候存了空,后面对该字段排序查询的时候还是会报错。
动态索引,开始想在AbstractElasticsearchRepository或者ElasticsearchRepositoryFactory里面想办法,又或者直接取到对应的bean,然后定时任务去修改里面的index等等。最后发现可以直接简单的使用el表达式就可以了,哪来的那么麻烦@Document(indexName = "gps_"+"#{T(com.my.common.core.util.Date8Util).getYearMonth()}")......,不过要是没事的时候也可以去尝试一下..
然后分页查询后,我们业务需求是对某个时间段进行查询,而我们使用的是Date,开始以为和其他的一样,只要存储的时候和查询都是一样的类型, 就没问题了,然后才发现,存储没问题,但是实际上存储进去是保存timestarp。后面查询时,如果传入Date类型。
RangeQueryBuilder rangeQueryBuilder= QueryBuilders.rangeQuery("uploadtime");
if(esQuery.getUploadTimes()!=null){
rangeQueryBuilder.from(esQuery.getUploadTimes());
}
if(esQuery.getUploadTimee()!=null){
rangeQueryBuilder.to(esQuery.getUploadTimee());
}
没想到这样会报错,java.lang.NumberFormatException: For input string: 。查看源码,原来都会转换为string然后进行判断,然后把Date转换一下.getTime()解决了问题。