5, springboot-es实战实例

需求

我们大致能完成多条件组合查询功能, 有点类似jd商品的搜索功能。

需要完成的功能:

  • 根据时间查询(范围)

  • 精准查询

  • 模糊查询

准备数据

将我们需要的数据存储到es中, 我们使用爬虫来完成这一步。

接口地址数据来源: https://api.66mz8.com/api/music.163.php?format=json

跑完该接口只有1百多条数据 尴尬了...

{
    "code": 200,
    "name": "城南花已开",
    "artists_name": "三亩地",
    "music_url": "http:\/\/music.163.com\/song\/media\/outer\/url?id=468176711.mp3",
    "music_pic": "http:\/\/p3.music.126.net\/i-7ktILRPImJ0NwiH8DABg==\/109951162885959979.jpg",
    "avatarurl": "https:\/\/p2.music.126.net\/uarVFKgUlrI9Z1nr-50cAw==\/109951162843608471.jpg",
    "nickname": "城南花已开",
    "comments": "谢谢私信给我朋友们,真心谢谢你们!我好坚持加油的!"
}

上面的数据我们使用 name,artists_name, nickname, comments我们再随机生成时间, 范围在5年以内。

代码见 CrawlerComments.java , 大概跑了1百多条就没有数据了...

简单页面

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
</head>

<body>
    <div id="content">
    <div>
        <form>
        <span>歌曲名称:</span><input type="text" name="name" v-model="name"> &nbsp;&nbsp;&nbsp;
        <span>歌手:</span><input type="text" name="artists_name" v-model="artists_name"> &nbsp;&nbsp;&nbsp;
        <span>用户名:</span><input type="text" name="nickname"  v-model="nickname"> &nbsp;&nbsp;&nbsp;
        <span>关键字:</span><input type="text" name="kw" v-model="kw"> &nbsp;&nbsp;&nbsp;
        <span>开始时间:</span><input type="text" name="start-time" v-model="starttime"> &nbsp;&nbsp;&nbsp;
        <span>结束时间:</span><input type="text" name="end-time" v-model="endtime">&nbsp;&nbsp;&nbsp;
        <span>页码:</span><input style="width: 20px;" type="text" name="pagenum" v-model="pagenum">&nbsp;
        <input type="button" @click="search" value="搜索">
        <input type="reset" value="重置">
    </form>
    </div>

    <div>
       <table>
           <tr>
               <th>评论时间</th>
               <th>歌手</th>
               <th>歌曲名</th>
               <th>评论人</th>
               <th>评论内容</th>
           </tr>

           <tr v-for="(item, index) in resultData" :key="index">
               <td style="width: 170px;">{{item.time}}</td>
               <td>{{item.artists_name}}</td>
               <td>{{item.name}}</td>
               <td>{{item.nickname}}</td>
               <td>{{item.comment}}</td>
           </tr>
       </table>
    </div>
</div>
</body>
<script>
    var vm = new Vue({
        el:'#content',
        data:{
            name: "",
            artists_name: "",
            nickname: "",
            kw: "",
            starttime: "",
            endtime : "",
            pagenum: 1,
            resultData:[]
        },
        methods:{
            search: function() {
                var data = {
                    "name":this.name,
                    "artists_name":this.artists_name,
                    "nickname":this.nickname,
                    "kw":this.kw,
                    "starttime":this.starttime,
                    "endtime":this.endtime 
                };
                this.$http.post("http://127.0.0.1:8080/comments/search?pagenum="+this.pagenum, data).then(
                    function(result){
                        console.log(123);
                        this.resultData = result.body.content;
                    },
                    function(error){
                        this.resultData = error.content;
                    }
            )
            }
        }
    });
</script>

</html>

业务代码

  • controller

2个接口, 一个多条件查询, 一个是供上面存储数据的接口

  @Autowired
    private CommentsService commentsService;


    @RequestMapping("/search")
    public Object search(@RequestBody CommentsSearchParam searchParam, @Param("pagenum") int pagenum){
        Page<Comments> search = commentsService.search(searchParam, pagenum);
        return search;
    }


    @RequestMapping("/save")
    public String save(@RequestBody Comments comments){
        boolean exist = commentsService.exist(comments.getCommentid());
        if(!exist){
            commentsService.save(comments);
        }else{
            System.out.println("已存在:" + JSON.toJSONString(comments));
        }

        return "ok";
    }

  • service
@Override
    public Page<Comments> search(CommentsSearchParam param, int pagenum) {
        if(pagenum < 1){
            pagenum = 1;
        }
        pagenum = pagenum -1;
        PageRequest pageable = PageRequest.of(pagenum, 20, Sort.by(Sort.Order.asc("time")));
        // 使用不太正确,详情见下面查询详解
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        if(StringUtils.isNotEmpty(param.getName())){
            String name = param.getName();
            boolQueryBuilder.must(new TermQueryBuilder("name", name));
        }

        if(StringUtils.isNotEmpty(param.getNickname())){
            boolQueryBuilder.must(new TermQueryBuilder("nickname", param.getNickname()));
        }

        if(StringUtils.isNotEmpty(param.getArtists_name())){
            boolQueryBuilder.must(new TermQueryBuilder("artists_name", param.getArtists_name()));
        }
        if(StringUtils.isNotEmpty(param.getKw())){
            boolQueryBuilder.must(new MatchQueryBuilder("comment", param.getKw()));
        }

        if(StringUtils.isNotEmpty(param.getStarttime()) && StringUtils.isNotEmpty(param.getEndtime())){
            RangeQueryBuilder time = new RangeQueryBuilder("time");
            time.from(param.getStarttime());
            time.to(param.getEndtime());
            boolQueryBuilder.must(time);
        }
        Page<Comments> search = commentsRepository.search(boolQueryBuilder, pageable);
        return search;
    }
  • CommentsRepository
@Component
public interface CommentsRepository extends ElasticsearchRepository<Comments, String>{

}

springboot data es项目地址

springboot data es

其他查询方式

NativeSearchQueryBuilder

好像我们上面的查询方式有点不太正确, 网上多用NativeSearchQueryBuilder, 然后with带其他条件, 使用Querybuilders 直接生成其他query, 方便简单

 @Test
public void  nativeQuery(){
    PageRequest pageable = PageRequest.of(0, 20);

    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
    nativeSearchQueryBuilder
            .withQuery(QueryBuilders.boolQuery()
                    .must(QueryBuilders.termsQuery("artists_name", "赵紫骅", "华晨宇"))
                    .must(QueryBuilders.matchQuery("comment", "自己")))
            .withSort(SortBuilders.fieldSort("time").order(SortOrder.ASC))
            .withPageable(pageable);
	// repository 和 template 都可以查询
    Page<Comments> search = commentsRepository.search(nativeSearchQueryBuilder.build());
    System.out.println(JSON.toJSONString(search));
	
    Page<Comments> comments = template.queryForPage(nativeSearchQueryBuilder.build(), Comments.class);

    System.out.println(search.getContent().size() == comments.getContent().size());
}

注意: 多个withQuery会被下一个覆盖掉, 多条件使用BooleanQuery

public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) {
   this.queryBuilder = queryBuilder;
   return this;
}

也就是你用多个withQuery, 生效的永远都是最后一个, 其他的都被覆盖掉了

使用注解方式查询

/*
从kibana中复制过来的
*/
@Query("{\"term\":{\"nickname\":{\"value\":\"?0\"}}}")
Page<Comments> findByNickname(String nickname, Pageable pageable);

注意: 这边并没有把query这一层复制过来(找了小半天的原因...)

{
"query": {
"term": {
"nickname": {
"value": "遇见橙色温暖"
}
}
}
}

@Test
public void queryByNicknameTest(){
    PageRequest pageable = PageRequest.of(0, 20);
    Page<Comments> comments = commentsRepository.findByNickname("城南花已开", pageable);
    System.out.println(JSON.toJSONString(comments));
}