springboot + solr,Solr支持json
Solr是基于Lucene的全文搜索引擎,服务部署依赖web容器,如tomcat。Solr支持json, xml, csv等数据格式,相比于查询性能更高的ElasticSearch
Solr更适用于传统搜索应用服务。
本篇简述springboot集成solr (单机版,暂不用solrCloud)
准备工作:
1. maven添加solr依赖
<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-solr</artifactId></dependency>
2. solr 配置
2.1 yml
spring:data:solr:host: http://192.168.2.9:8983/solr
2.2 SolrTemplate配置
@Configurationpublic class SolrConfig {@Autowiredprivate SolrClient solrClient;@Beanpublic SolrTemplate getSolrTemplate(){return new SolrTemplate(solrClient);}}
2.3 SolrDocument
import lombok.Data;import org.apache.solr.client.solrj.beans.Field;import org.springframework.data.solr.core.mapping.Indexed;import org.springframework.data.solr.core.mapping.SolrDocument;import java.io.Serializable;import java.util.List;@SolrDocument(collection = "goods_core")@Datapublic class SearchGoods implements Serializable {@Indexed@Field("id")private String id; // goodsId@Fieldprivate String name;@Fieldprivate String detail;/*** copyField复值域: name, detail* <field name="goodsSearchWord" type="text_ik" multiValued="true" indexed="true" stored="true"/>* <field name="name" type="string" indexed="false" stored="true"/>* <field name="detail" type="string" indexed="false" stored="true"/>** <copyField source="name" dest="goodsSearchWord" maxChars="256"/>* <copyField source="detail" dest="goodsSearchWord" maxChars="256"/>*/@Fieldprivate List<String> goodsSearchWord;@Indexed@Fieldprivate Integer shopId;@Indexed@Fieldprivate String position; // latitude,longitude@Fieldprivate Integer salePrice; // 零售价(分)@Fieldprivate Integer activePrice; // 活动价(分)@Fieldprivate Double inventory; // 实时总库存@Fieldprivate Double saleCount; // 已售数量/*** 注意solr中最小的整型为pint, byte及short都要转换成int类型*/@Indexed@Fieldprivate Integer saleType; // 营销类型@Fieldprivate Long createTime; // 创建时间 单位毫秒@Fieldprivate Long updateTime; // 更新时间 单位毫秒}
3. solr 封装
3.1 solr service
import org.springframework.data.domain.Page;import org.springframework.data.domain.Pageable;import org.springframework.data.solr.core.query.HighlightQuery;import org.springframework.data.solr.core.query.Query;import org.springframework.data.solr.core.query.result.HighlightPage;import org.springframework.data.solr.core.query.result.ScoredPage;import java.io.Serializable;import java.util.Collection;import java.util.List;public interface SolrService<T, ID extends Serializable> {void save(T t, String solrCore);void save(Collection<T> beans, String solrCore);T searchById(ID id, String solrCore);List<T> searchByIds(List<ID> ids, String solrCore);Page<T> query(Query query, String solrCore);ScoredPage<T> pageQuery(Query query, Pageable pageable, String solrCore);HighlightPage<T> queryForHighlightPage(HighlightQuery query, Pageable pageable, String solrCore);void deleteById(ID id, String solrCore);void deleteByIds(Collection<String> ids, String solrCore);void deleteAll(String solrCore);interface SolrCollection {String GOODS_CORE = "goods_core";}}
3.2 solr abstract serviceImpl
import org.apache.commons.collections.CollectionUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.Page;import org.springframework.data.domain.Pageable;import org.springframework.data.solr.core.SolrTemplate;import org.springframework.data.solr.core.query.HighlightQuery;import org.springframework.data.solr.core.query.Query;import org.springframework.data.solr.core.query.SimpleQuery;import org.springframework.data.solr.core.query.result.HighlightPage;import org.springframework.data.solr.core.query.result.ScoredPage;import java.io.Serializable;import java.lang.reflect.ParameterizedType;import java.util.Collection;import java.util.List;public abstract class SolrServiceImpl<T, ID extends Serializable> implements SolrService<T, ID> {@Autowiredprivate SolrTemplate solrTemplate;private Class<T> clazz;@SuppressWarnings("unchecked")public SolrServiceImpl() {ParameterizedType parameterizedType = ((ParameterizedType) getClass().getGenericSuperclass());clazz = (Class<T>) parameterizedType.getActualTypeArguments()[0];}@Overridepublic void save(T t, String solrCore) {solrTemplate.saveBean(solrCore, t);solrTemplate.commit(solrCore);}@Overridepublic void save(Collection<T> beans, String solrCore) {if(CollectionUtils.isNotEmpty(beans)){solrTemplate.saveBeans(solrCore, beans);// 提交时报 mime type 错误,需要指定solrCoresolrTemplate.commit(solrCore);}}@Overridepublic T searchById(ID id, String solrCore) {return solrTemplate.getById(solrCore, id, clazz).orElse(null);}@Overridepublic List<T> searchByIds(List<ID> ids, String solrCore) {return (List<T>)solrTemplate.getByIds(solrCore, ids, clazz);}@Overridepublic Page<T> query(Query query, String solrCore) {return solrTemplate.query(solrCore, query, clazz);}@Overridepublic ScoredPage<T> pageQuery(Query query, Pageable pageable, String solrCore) {this.buildPageAndSort(query, pageable);return solrTemplate.queryForPage(solrCore, query, clazz);}@Overridepublic HighlightPage<T> queryForHighlightPage(HighlightQuery query, Pageable pageable, String solrCore) {this.buildPageAndSort(query, pageable);return solrTemplate.queryForHighlightPage(solrCore, query, clazz);}@Overridepublic void deleteById(ID id, String solrCore) {solrTemplate.deleteByIds(solrCore, id.toString());solrTemplate.commit(solrCore);}@Overridepublic void deleteByIds(Collection<String> ids, String solrCore) {if(CollectionUtils.isNotEmpty(ids)){solrTemplate.deleteByIds(solrCore, ids);solrTemplate.commit(solrCore);}}@Overridepublic void deleteAll(String solrCore) {Query query = new SimpleQuery("*:*");solrTemplate.delete(solrCore, query);solrTemplate.commit(solrCore);}private void buildPageAndSort(Query query, Pageable pageable){query.setOffset(pageable.getOffset()); // 开始索引(默认0) (pageNum-1) * pageSizequery.setRows(pageable.getPageSize()); // 每页记录数query.addSort(pageable.getSort());}}
3.3 search service
可结合使用SolrClient 或 SolrTemplate
import lombok.extern.slf4j.Slf4j;import org.apache.commons.collections.CollectionUtils;import org.apache.commons.lang3.StringUtils;import org.apache.solr.client.solrj.SolrClient;import org.apache.solr.client.solrj.SolrQuery;import org.apache.solr.client.solrj.response.Group;import org.apache.solr.client.solrj.response.GroupCommand;import org.apache.solr.client.solrj.response.GroupResponse;import org.apache.solr.client.solrj.response.QueryResponse;import org.apache.solr.common.SolrDocument;import org.apache.solr.common.SolrDocumentList;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.domain.Page;import org.springframework.data.solr.core.query.Criteria;import org.springframework.data.solr.core.query.SimpleQuery;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.util.*;import java.util.function.Function;import java.util.stream.Collectors;@Slf4j@Servicepublic class SearchGoodsServiceImpl extends SolrServiceImpl<SearchGoods, String> implements SearchGoodsService {@Autowiredprivate SolrClient solrClient;@Overridepublic void saveGoodsToSolr(Goods goods){SearchGoods searchGoods = new SearchGoods(goods);super.save(searchGoods, SolrCollection.GOODS_CORE);}@Overridepublic SearchGoods searchByGoodsId(String goodsId) {if(StringUtils.isNotEmpty(goodsId)){return super.searchById(goodsId, SolrCollection.GOODS_CORE);}return null;}@Overridepublic List<SearchGoods> list(GeoSearchRequest request, Byte saleType) throws Exception {SolrQuery solrQuery = new SolrQuery("*:*");if(saleType != null && saleType > 0){solrQuery.addFilterQuery("saleType:" + saleType);}if(!StringUtils.isAnyEmpty(request.getLatitude(), request.getLongitude())){SolrQueryUtil.buildGeoQuery(solrQuery, request);}// 按店铺分组SolrQueryUtil.buildGroupQuery(solrQuery);// 排序规则solrQuery.addSort("activePrice", SolrQuery.ORDER.asc);solrQuery.addSort("salePrice", SolrQuery.ORDER.asc);solrQuery.addSort("updateTime", SolrQuery.ORDER.desc);solrQuery.addSort("saleCount", SolrQuery.ORDER.desc);solrQuery.addSort("id", SolrQuery.ORDER.desc);// 分页查询SolrQueryUtil.buildPageQuery(solrQuery, request);QueryResponse response = solrClient.query(SolrCollection.GOODS_CORE, solrQuery);List<List<SearchGoods>> goodsGroupList = this.parseGoodsGroupDocumentsByGroup(response);return GroupList.stream().flatMap(Collection::stream).collect(Collectors.toList());}/*** 分组聚合文档解析* @param response* @return*/private List<List<SearchGoods>> parseGoodsGroupDocumentsByGroup(QueryResponse response){List<List<SearchGoods>> goodsList = new ArrayList<>();// 注意:聚合分组后,此处不能再用response.getResults()接收结果GroupResponse groupResponse = response.getGroupResponse();List<GroupCommand> commands = groupResponse.getValues();if(commands != null) {for(GroupCommand command : commands) {for(Group group : command.getValues()) {SolrDocumentList solrDocuments = group.getResult();List<SearchGoods> searchGoodsList = this.parseGoodsDocuments(solrDocuments);if(CollectionUtils.isNotEmpty(searchGoodsList)){goodsList.add(searchGoodsList);}}}}return goodsList;}/*** 搜索文档解析* @param solrDocuments* @return*/private List<SearchGoods> parseGoodsDocuments(SolrDocumentList solrDocuments){List<SearchGoods> goodsList = new ArrayList<>();for(SolrDocument doc : solrDocuments) {SearchGoods searchGoods = new SearchGoods();searchGoods.setId((String)doc.getFieldValue("id"));searchGoods.setName((String)doc.getFieldValue("name"));searchGoods.setDetail((String)doc.getFieldValue("detail"));searchGoods.setShopId((Integer) doc.getFieldValue("shopId"));searchGoods.setSalePrice((Integer) doc.getFieldValue("salePrice"));searchGoods.setActivePrice((Integer) doc.getFieldValue("activePrice"));searchGoods.setInventory((Double) doc.getFieldValue("inventory"));searchGoods.setSaleCount((Double) doc.getFieldValue("saleCount"));searchGoods.setSaleType((Integer)doc.getFieldValue("saleType"));searchGoods.setCreateTime((Long) doc.getFieldValue("createTime"));searchGoods.setUpdateTime((Long) doc.getFieldValue("updateTime"));goodsList.add(searchGoods);}return goodsList;}}
3.4 solr query 工具类
public final class SolrQueryUtil {/*** 构建基于LBS搜索条件* @param solrParam* @param request* @return*/public static SolrQuery buildGeoQuery(SolrQuery solrParam, GeoClassifySearchRequest request){// 基于LBS搜索if(StringUtils.isAnyEmpty(request.getLatitude(), request.getLongitude())){throw new CommonException("未提供当前地理位置经纬度,无法搜索");}solrParam.addFilterQuery("{!geofilt}"); // 距离过滤函数solrParam.set("pt", request.getLatitude() + "," + request.getLongitude()); // 当前纬度,经度solrParam.set("sfield", "position"); // 经纬度的字段solrParam.set("d", request.getDistance()); // 就近 d km的所有数据solrParam.set("score", "distance"); // 距离solrParam.addSort("geodist()", SolrQuery.ORDER.asc); // 根据距离排序:由近到远solrParam.set("fl", "*,_dist_:geodist(),score"); // 查询的结果中添加距离和scorereturn solrParam;}/*** 构建全量聚合搜索条件* @param solrParam* @return*/public static SolrQuery buildGroupQuery(SolrQuery solrParam){// 聚合搜索solrParam.set("group", true); // 是否分组solrParam.set("group.field", "shopId"); // 分组的域solrParam.set("group.limit", "1"); // 每组显示的个数,默认为1solrParam.set("group.ngroups", true); // 是否计算所得分组个数;注意:当每个分组显示数目大于1个时,不能用分组数量来计算总页码solrParam.addSort("activePrice", SolrQuery.ORDER.asc);solrParam.addSort("salePrice", SolrQuery.ORDER.asc);return solrParam;}/*** 构建分页条件* @param solrParam* @param request* @return*/public static SolrQuery buildPageQuery(SolrQuery solrParam, PagerRequest request){solrParam.setStart((request.getPageNum() - 1) * request.getPageSize()); // 起始索引值,默认0solrParam.setRows(request.getPageSize()); // 显示几条数据return solrParam;}/*** These characters are part of the query syntax and must be escaped* @param s* @return*/public static String escapeQueryChars(String s) {StringBuilder sb = new StringBuilder();for(int i = 0; i < s.length(); i++){char c = s.charAt(i);if(c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':'|| c == '^' || c == '[' || c == ']' || c == '\"' || c == '{' || c == '}' || c == '~'|| c == '*' || c == '?' || c == '|' || c == '&' || c == ';' || c == '/'|| Character.isWhitespace(c)){sb.append('\\');}sb.append(c);}return sb.toString();}}
本站文章为和通数据库网友分享或者投稿,欢迎任何形式的转载,但请务必注明出处.
同时文章内容如有侵犯了您的权益,请联系QQ:970679559,我们会在尽快处理。