赞
踩
这篇接着上一篇(https://blog.csdn.net/Ocean111best/article/details/86092862),本来准备上一篇写完,但是发现还是太多,故再写一篇。
发现刚开始写博客时,千言万语,最后确不知道怎么说,只有直接简单粗暴地贴代码了,希望以后坚持多写,慢慢地有所提高。
一、首先配置整个项目的 application.properties(当时把 properties 文件和 yml 文件混着配置,被一个大神取笑了):
之所以要配置 Mysql ,是因为精确搜索中,需要选择省份对应的市,再选择市对应的区,这个数据没有放到 ES 中,因为 ES6.5 中每个索引只能有一个 type,如果放到 ES 中需要再弄一个索引,不想这么麻烦,于是把省市区的数据放到了 Mysql 数据库中(貌似这样好像更麻烦,以后会再改)
二、ES 的配置
配置 ES ,使 Springboot 可以操作它。这个地方注意,ES6.5 的配置和 ES5.6 的配置不一样,暂时只知道 ES5.6 的配置,还不太知道 ES6.5 的配置,如下图:
- /**
- * es配置文件
- * es注入主要依靠一个 TransportClient
- */
- @Configuration
- public class myconfig {
- @Value("${elasticsearch.host}")
- private String esHost;
-
- @Value("${elasticsearch.port}")
- private int esPort;
-
- @Value("${elasticsearch.cluster.name}")
- private String esName;
-
- @Bean
- public TransportClient esClient() throws UnknownHostException {
- Settings settings = Settings.builder()
- .put("cluster.name", this.esName)
- .put("client.transport.sniff", true)
- .build();
-
- InetSocketTransportAddress master = new InetSocketTransportAddress(
- InetAddress.getByName(esHost), esPort
- );
-
- TransportClient client = new PreBuiltTransportClient(settings)
- .addTransportAddress(master);
-
- return client;
- }
- }
三、通过搜索框搜索
在搜索框中输入城市、年份、指标的任意组合和任意顺序,都要搜索出结果,这个是模糊查询,且是并查询,如输入"北京市 2015 人口",则得到北京市2015年与人口相关的指标(如年平均人口、总人口等)的所有数据;
controller层中,如下所示,主要是调用 service 层,业务逻辑主要在 service 中
- /**
- * 通过 ES 查询(通过搜索框查询)
- */
- @ApiOperation(value = "通过搜索框查询详细信息",notes = "通过搜索框查询详细信息(keywords不为空,其他为空)")
- @RequestMapping(value = "yearbook/search",method = RequestMethod.POST,produces = { MediaType.APPLICATION_JSON_VALUE },consumes = MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity query(@RequestBody YearbookSearch yearbookSearch){
- List<YearbookResult> result = yearbookService.query(yearbookSearch);
-
- return new ResponseEntity(result, HttpStatus.OK);
- }
在 service 层中,由于在网上看到的操作很多都是或操作,但是在搜索框中搜索的是并操作。之后查找了很久的资料,发现可以设置如下参数即可达到搜索的是并操作的目标:
.operator(Operator.AND).type("cross_fields")
则在 service 里面可以进行如下操作:
- /**
- * 通过 ES 查询(通过搜索框查询)
- */
- @Override
- public List<YearbookResult> query(YearbookSearch yearbookSearch){
- SearchResponse response = searchResponse(yearbookSearch);
- List<YearbookResult> result=new ArrayList<YearbookResult>();
-
- //将搜索到的数据放入List
- for(SearchHit hit:response.getHits()){
- YearbookResult pointMap = new YearbookResult();
- pointMap.setCitytr(String.valueOf(hit.getSource().get(YearbookIndexKey.CITYTR)));
- pointMap.setDistinct(String.valueOf(hit.getSource().get(YearbookIndexKey.DISTINCT)));
- pointMap.setCountytr(String.valueOf(hit.getSource().get(YearbookIndexKey.COUNTYTR)));
- pointMap.setProvincetr(String.valueOf(hit.getSource().get(YearbookIndexKey.PROVINCETR)));
- pointMap.setStatisticNum(String.valueOf(hit.getSource().get(YearbookIndexKey.STATISTIC_NUM)));
- pointMap.setUnit(String.valueOf(hit.getSource().get(YearbookIndexKey.UNIT)));
- pointMap.setIndexName(String.valueOf(hit.getSource().get(YearbookIndexKey.INDEX_NAME)));
- pointMap.setYear(String.valueOf(hit.getSource().get(YearbookIndexKey.YEAR)));
- pointMap.setFrom(String.valueOf(hit.getSource().get(YearbookIndexKey.FROM)));
- result.add(pointMap);
- }
- return result;
-
- }
由于之后通过 城市、年份 搜索指标列表也是用这个接口,于是可以将相同部分抽离出来,放到如下这个方法中:
- @Override
- public List<YearbookResult> queryByYearCityIndex(YearbookSearch yearbookSearch){
- StringBuffer CityArr = new StringBuffer();
- StringBuffer YearArr = new StringBuffer();
- StringBuffer IndexArr = new StringBuffer();
-
- String[] Cityarr = yearbookSearch.getCitytr().split("\\s+");
- for (int i=0;i<Cityarr.length;i++){
- if(i>0){
- CityArr.append(",");
- }
- CityArr.append("'").append(Cityarr[i]).append("'");
- }
-
- String[] Yeararr = yearbookSearch.getYear().split("\\s+");
- for (int j=0;j<Yeararr.length;j++){
- if(j>0){
- YearArr.append(",");
- }
- YearArr.append("'").append(Yeararr[j]).append("'");
- }
-
- String[] Indexarr = yearbookSearch.getIndex_name().split("\\s+");
- for (int k=0;k<Indexarr.length;k++){
- if(k>0){
- IndexArr.append(",");
- }
- IndexArr.append("'").append(Indexarr[k]).append("'");
- }
-
- String whereExpress = "citytr in(" + CityArr +")" +
- " and " + "year in(" + YearArr +")" +
- " and " + "index_name in(" + IndexArr +")";
- SearchResponse response = searchBySql(whereExpress);
-
- List<YearbookResult> result=new ArrayList<YearbookResult>();
- //将搜索到的数据放入List
- for(SearchHit hit:response.getHits()){
- YearbookResult pointMap = new YearbookResult();
- pointMap.setCitytr(String.valueOf(hit.getSource().get(YearbookIndexKey.CITYTR)));
- pointMap.setDistinct(String.valueOf(hit.getSource().get(YearbookIndexKey.DISTINCT)));
- pointMap.setCountytr(String.valueOf(hit.getSource().get(YearbookIndexKey.COUNTYTR)));
- pointMap.setProvincetr(String.valueOf(hit.getSource().get(YearbookIndexKey.PROVINCETR)));
- pointMap.setStatisticNum(String.valueOf(hit.getSource().get(YearbookIndexKey.STATISTIC_NUM)));
- pointMap.setUnit(String.valueOf(hit.getSource().get(YearbookIndexKey.UNIT)));
- pointMap.setIndexName(String.valueOf(hit.getSource().get(YearbookIndexKey.INDEX_NAME)));
- pointMap.setYear(String.valueOf(hit.getSource().get(YearbookIndexKey.YEAR)));
- pointMap.setFrom(String.valueOf(hit.getSource().get(YearbookIndexKey.FROM)));
- result.add(pointMap);
- }
- return result;
-
- }
-
- //es搜索方法
- public SearchResponse searchResponse(YearbookSearch yearbookSearch){
- BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
-
- //通过城市查询
- if(yearbookSearch.getCitytr()!=null){
- boolQuery.must(
- QueryBuilders.matchQuery(YearbookIndexKey.CITYTR,
- yearbookSearch.getCitytr()
- )
- );
- }
-
- //通过年份查询
- if(yearbookSearch.getYear()!=null){
- boolQuery.must(
- QueryBuilders.matchQuery(YearbookIndexKey.YEAR,yearbookSearch.getYear())
- );
- }
-
- //通过指标值查询
- if(yearbookSearch.getIndex_name()!=null){
- boolQuery.must(
- QueryBuilders.matchQuery(YearbookIndexKey.INDEX_NAME,yearbookSearch.getIndex_name())
- );
-
- }
-
- //通过搜索框关键字查询
- if(yearbookSearch.getKeywords() != null && !yearbookSearch.getKeywords().isEmpty()){
-
- boolQuery.must(
- QueryBuilders.multiMatchQuery(yearbookSearch.getKeywords(),
- YearbookIndexKey.CITYTR,
- YearbookIndexKey.INDEX_NAME,
- YearbookIndexKey.YEAR,
- YearbookIndexKey.CODE,
- YearbookIndexKey.PROVINCETR,
- YearbookIndexKey.STATISTIC_NUM,
- YearbookIndexKey.TAG,
- YearbookIndexKey.UNIT
- ).operator(Operator.AND).type("cross_fields"));
-
- }
-
- SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME)
- .setTypes(INDEX_TYPE)
- .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) //当数据量足够多时可以用 QUERY_THEN_FETCH
- .setQuery(boolQuery)
- .setFrom(yearbookSearch.getStart())
- .setSize(20000);
- logger.debug(requestBuilder.toString());
- SearchResponse response = requestBuilder.get();
- return response;
-
- }
四、精确操作
需要通过 城市、年份、指标 精确搜索出数据,而之前为了可以通过搜索框搜索出结果,已经设置指标为可以分词了,如果通过 ES 直接搜索的话将会先进行分词再搜索,不会是完全精确查询得到的结果。于是查找了很多资料,决定用 elasticsearch-sql 进行搜索,elasticsearch-sql 官网没有给出 Java API 手册,网上也很少有 elasticsearch-sql 的 Java 相关资料,但是好在找到了一些。在 service 层中,首先将城市、年份、指标值各放到一个数组(因为可以多选),然后拼接 sql 语句,如下图所示:
- /**
- * 精确查找
- * 通过 ES 查询(通过城市、年份、指标查询,不通过搜索框)
- */
- @Override
- public List<YearbookResult> queryByYearCityIndex(YearbookSearch yearbookSearch){
- StringBuffer CityArr = new StringBuffer();
- StringBuffer YearArr = new StringBuffer();
- StringBuffer IndexArr = new StringBuffer();
-
- String[] Cityarr = yearbookSearch.getCitytr().split("\\s+");
- for (int i=0;i<Cityarr.length;i++){
- if(i>0){
- CityArr.append(",");
- }
- CityArr.append("'").append(Cityarr[i]).append("'");
- }
-
- String[] Yeararr = yearbookSearch.getYear().split("\\s+");
- for (int j=0;j<Yeararr.length;j++){
- if(j>0){
- YearArr.append(",");
- }
- YearArr.append("'").append(Yeararr[j]).append("'");
- }
-
- String[] Indexarr = yearbookSearch.getIndex_name().split("\\s+");
- for (int k=0;k<Indexarr.length;k++){
- if(k>0){
- IndexArr.append(",");
- }
- IndexArr.append("'").append(Indexarr[k]).append("'");
- }
- //拼接 sql 语句
- String whereExpress = "citytr in(" + CityArr +")" +
- " and " + "year in(" + YearArr +")" +
- " and " + "index_name in(" + IndexArr +")";
- SearchResponse response = searchBySql(whereExpress);
-
- List<YearbookResult> result=new ArrayList<YearbookResult>();
- //将搜索到的数据放入List
- for(SearchHit hit:response.getHits()){
- YearbookResult pointMap = new YearbookResult();
- pointMap.setCitytr(String.valueOf(hit.getSource().get(YearbookIndexKey.CITYTR)));
- pointMap.setDistinct(String.valueOf(hit.getSource().get(YearbookIndexKey.DISTINCT)));
- pointMap.setCountytr(String.valueOf(hit.getSource().get(YearbookIndexKey.COUNTYTR)));
- pointMap.setProvincetr(String.valueOf(hit.getSource().get(YearbookIndexKey.PROVINCETR)));
- pointMap.setStatisticNum(String.valueOf(hit.getSource().get(YearbookIndexKey.STATISTIC_NUM)));
- pointMap.setUnit(String.valueOf(hit.getSource().get(YearbookIndexKey.UNIT)));
- pointMap.setIndexName(String.valueOf(hit.getSource().get(YearbookIndexKey.INDEX_NAME)));
- pointMap.setYear(String.valueOf(hit.getSource().get(YearbookIndexKey.YEAR)));
- pointMap.setFrom(String.valueOf(hit.getSource().get(YearbookIndexKey.FROM)));
- result.add(pointMap);
- }
- return result;
-
- }
然后将拼接后的 sql 语句传入查询方法中,如下所示:
- /**
- *sql查询
- * whereExpress 查询条件
- * whereExpress-查询条件:(f1=2 and f2=1) or (f3=1 and f4=1)
- */
- public SearchResponse searchBySql(String whereExpress){
- try{
- // 转换Elasticsearch格式的查询条件
- QueryBuilder queryBuilder = createQueryBuilderByWhere(whereExpress);
- //查询具体信息
- SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME)
- .setTypes(INDEX_TYPE)
- .setQuery(queryBuilder)
- .setFrom(0)
- .setSize(20000);
- SearchResponse response = requestBuilder.get();
- logger.debug(requestBuilder.toString());
- return response;
-
- }catch (Exception e){
- logger.warn("EsQueryUtil.seatchTotalByApi-Exception{}", e);
- }
- return null;
- }
调用 sql 转化的公共方法,根据表达式组装 ES 的 query 查询语句,这一步可以真正查询出数据,如下图所示:
- /**
- * sql转化公共方法--主要针对mysql
- * 根据表达式组装 ES 的 query 查询语句
- * whereExpress-查询条件:(f1=2 and f2=1) or (f3=1 and f4=1)
- */
- public static QueryBuilder createQueryBuilderByWhere(String whereExpress){
- BoolQueryBuilder boolQuery = null;
- try{
- String sql = "select * from " + INDEX_NAME;
- String whereTemp = "";
- if(!StringUtils.isEmpty(whereExpress)){
- whereTemp = " where " + whereExpress;
- }
- SQLQueryExpr sqlExpr = (SQLQueryExpr)toSqlExpr(sql + whereTemp);
- SqlParser sqlParser = new SqlParser();
- MySqlSelectQueryBlock query = (MySqlSelectQueryBlock) sqlExpr.getSubQuery().getQuery();
- WhereParser whereParser = new WhereParser(sqlParser,query);
- Where where = whereParser.findWhere();
- if(where!=null){
- boolQuery = QueryMaker.explan(where);
- }
-
- }catch (SqlParseException e){
- logger.warn("EsQueryUtil.createQueryBuilderByExpress-Exception", e);
- }
- return boolQuery;
-
- }
-
- /**
- * 验证sql
- * @param sql sql查询语句
- * @return
- */
- private static SQLExpr toSqlExpr(String sql){
- SQLExprParser parser = new ElasticSqlExprParser(sql);
- SQLExpr expr = parser.expr();
-
- if (parser.getLexer().token() != Token.EOF) {
- throw new ParserException("illegal sql expr : " + sql);
- }
- return expr;
-
- }
至此, Springboot 调用 elasticsearch-sql 进行数据的精确查询成功!
其中为了使页面返回的数据尽可能地多,在页面要设置最大返回值max_result_window;我是直接在 elasticsearch-head-master 里面设置的。先关掉索引,然后在复合查询里面,输入以下信息,注意这里是 PUT 请求方式,且 yearbook 指的是我的索引名称:
这个项目搞了一个多月(还在搞),遇到了很多问题,也在慢慢地迭代中,期间遇到问题就想着如何解决问题,茶饭不思,也遇到很多简单的问题没想明白,但是大神一分钟就给解决完了,这个真的需要很多经验。
写这个项目的博客中,也写了很多天,很多地方写得不太好,以后会再完善完善。写博客,写文章真的是一项很重要的技能,希望自己以后坚持!!!
未来会继续使用 ElasticSearch 技术和 Springboot 技术,加油!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。