zoukankan      html  css  js  c++  java
  • elasticsearch系列(三): SpringBoot整合

    环境:

    • SpringBoot 2.1.4.RELEASE
    • Elasticsearch 6.6.2
    • spring-boot-starter-data-elasticsearch

    pom引用

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    

    application配置

    # Elasticsearch配置
    spring.data.elasticsearch.cluster-name=elasticsearch-cluster
    # 单节点
    spring.data.elasticsearch.cluster-nodes=192.168.183.220:9300
    # 多节点
    #spring.data.elasticsearch.cluster-nodes=192.168.183.220:9300,192.168.183.220:9301,192.168.183.220:9302
    # 引用spring-boot-starter-actuator的话
    # 关闭对es健康检查,不然启动报错
    management.health.elasticsearch.enabled=false
    # 或者设置rest访问地址
    # spring.elasticsearch.rest.uris=http://192.168.183.220:9200
    

    索引配置Bean

    package com.lyf.domain.elastic;
    
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.data.annotation.Id;
    import org.springframework.data.elasticsearch.annotations.Document;
    import org.springframework.data.elasticsearch.annotations.Field;
    import org.springframework.data.elasticsearch.annotations.FieldType;
    
    @Data
    @NoArgsConstructor
    @Document(indexName = "goods",type = "_doc", shards = 5, replicas = 0)
    public class GoodsDoc {
    
        @Id
        private Long id;
    
        @Field(type = FieldType.Text, analyzer = "ik_max_word")
        private String title; //标题
    
        @Field(type = FieldType.Keyword)
        private String category;// 分类
    
        @Field(type = FieldType.Keyword)
        private String brand; // 品牌
    
        @Field(type = FieldType.Double)
        private Double price; // 价格
    
        @Field(index = false, type = FieldType.Keyword)
        private String images; // 图片地址
    
        public GoodsDoc(Long id, String title, String category, String brand, Double price, String images) {
            this.id = id;
            this.title = title;
            this.category = category;
            this.brand = brand;
            this.price = price;
            this.images = images;
        }
    }
    

    shards分片replicas副本数自行控制

    ElasticsearchTemplate使用

    可以便捷:

    • 创建索引index
    • 创建映射mapping
    • 删除索引index
    • 设置别名alias
    • 等等..

    项目在启动时,会自动创建Bean里面的定义的索引,基本使用如下:

    package com.lyf.service;
    
    @Service
    public class ElasticService {
    
        @Autowired
        private ElasticsearchTemplate elasticsearchTemplate;
    
        public void init(){
            elasticsearchTemplate.createIndex(GoodsDoc.class);
            elasticsearchTemplate.putMapping(GoodsDoc.class);
        }
    
        public void delIndex(){
            elasticsearchTemplate.deleteIndex(GoodsDoc.class);
        }
    
        public void alias(String index, String alias){
            AliasQuery aliasQuery = new AliasQuery();
            aliasQuery.setIndexName(index);
            aliasQuery.setAliasName(alias);
            elasticsearchTemplate.addAlias(aliasQuery);
        }
    }
    

    持久层操作

    对es做增删改查

    定义dao, 继承ElasticsearchRepository方便语义化操作

    package com.lyf.dao;
    
    import com.lyf.domain.elastic.GoodsDoc;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
    
    import java.util.List;
    
    @Mapper
    public interface ElasticRepository extends ElasticsearchRepository<GoodsDoc, Long> {
    }
    

    定义service操作

    package com.lyf.service;
    
    @Service
    public class ElasticService {
    
        @Autowired
        private ElasticRepository elasticRepository;
    
        public void add(GoodsDoc goodsDoc){
            elasticRepository.save(goodsDoc);
        }
    
        public void addBatch(List<GoodsDoc> goodsDocList){
            elasticRepository.saveAll(goodsDocList);
        }
    
        public void del(Long id){
            elasticRepository.deleteById(id);
        }
    
        public GoodsDoc find(Long id){
            return elasticRepository.findById(id).get();
        }
    
        public List<GoodsDoc> all(String name, int sort){
            Direction direction = sort == 0 ? Sort.Direction.ASC : Sort.Direction.DESC;
            Iterable<GoodsDoc> all = elasticRepository.findAll(Sort.by(direction,name));
            return copyIterator(all);
        }
    
        // 高级查询
        // 分页+排序
        public Map pageQuery(String name, String value, Integer page, Integer size, int sort){
            // 构建查询条件
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            // 添加基本的分词查询
            queryBuilder.withQuery(QueryBuilders.termQuery(name, value));
            // 排序
            queryBuilder.withSort(SortBuilders.fieldSort("price").order(sort==0 ? SortOrder.ASC : SortOrder.DESC));
            // 设置分页参数 es默认分页从0开始
            queryBuilder.withPageable(PageRequest.of(page-1, size));
            // 执行搜索,获取结果
            Page<GoodsDoc> search = elasticRepository.search(queryBuilder.build());
            Map result = new HashMap();
            result.put("total", search.getTotalElements());
            result.put("pages", search.getTotalPages());
            result.put("list", copyIterator(search.getContent()));
            return result;
        }
    
    }
    

    copyIterator方法,把Iterable对象变成List

    public static <T> List<T> copyIterator(Iterable<T> iter) {
            List<T> copy = new ArrayList<T>();
            iter.forEach(item->copy.add(item));
            return copy;
        }
    

    语义化方法

    Spring Data 的另一个强大功能,是根据方法名称自动实现功能。
    比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。
    当然,方法名称要符合一定的约定:

    Keyword Sample Elasticsearch Query String
    And findByNameAndPrice {"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
    Or findByNameOrPrice {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
    Is findByName {"bool" : {"must" : {"field" : {"name" : "?"}}}}
    Not findByNameNot {"bool" : {"must_not" : {"field" : {"name" : "?"}}}}
    Between findByPriceBetween {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
    LessThanEqual findByPriceLessThan {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
    GreaterThanEqual findByPriceGreaterThan {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
    Before findByPriceBefore {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
    After findByPriceAfter {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
    Like findByNameLike {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
    StartingWith findByNameStartingWith {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
    EndingWith findByNameEndingWith {"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}
    Contains/Containing findByNameContaining {"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}}
    In findByNameIn(Collection<String>names) {"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}
    NotIn findByNameNotIn(Collection<String>names) {"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}
    Near findByStoreNear Not Supported Yet !
    True findByAvailableTrue {"bool" : {"must" : {"field" : {"available" : true}}}}
    False findByAvailableFalse {"bool" : {"must" : {"field" : {"available" : false}}}}
    OrderBy findByAvailableTrueOrderByNameDesc {"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}}

    dao新增方法

    /**
     * 根据价格区间查询
     * @param price1
     * @param price2
     * @return
     */
    List<Item> findByPriceBetween(double price1, double price2);
    

    service使用

    public List<GoodsDoc> queryByPriceBetween(Double price1, Double price2){
    	return elasticRepository.findByPriceBetween(price1, price2);
    }
    

    定义controller

    package com.lyf.controller;
    
    import com.lyf.domain.elastic.GoodsDoc;
    import com.lyf.util.Result;
    import com.lyf.model.ResultInfo;
    import com.lyf.service.ElasticService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("es")
    public class ElasticController {
    
        @Autowired
        private ElasticService elasticService;
    
        @RequestMapping("init")
        public ResultInfo init(Integer id){
            elasticService.init();
            return Result.success();
        }
    
        @RequestMapping("rm")
        public ResultInfo delIndex(){
            elasticService.delIndex();
            return Result.success();
        }
    
        @RequestMapping("alias")
        public ResultInfo alias(String index, String alias){
            elasticService.alias(index, alias);
            return Result.success();
        }
    
        @RequestMapping("add")
        public ResultInfo add(GoodsDoc goodsDoc){
            elasticService.add(goodsDoc);
            return Result.success();
        }
    
        @RequestMapping("addBatch")
        public ResultInfo addBatch(@RequestBody List<GoodsDoc> goodsDocList){
            elasticService.addBatch(goodsDocList);
            return Result.success();
        }
    
        @RequestMapping("del")
        public ResultInfo del(Long id){
            elasticService.del(id);
            return Result.success();
        }
    
        @RequestMapping("find")
        public ResultInfo find(Long id){
            return Result.success( elasticService.find(id));
        }
    
        @RequestMapping("all")
        public ResultInfo all(String name, @RequestParam(defaultValue = "0") int sort){
            return Result.success( elasticService.all(name, sort));
        }
    
        @RequestMapping("queryByPriceBetween")
        public ResultInfo queryByPriceBetween(Double price1, Double price2){
            return Result.success( elasticService.queryByPriceBetween(price1, price2));
        }
    
        @RequestMapping("page")
        public ResultInfo page(String name, String key,
                               @RequestParam(defaultValue = "1") Integer page,
                               @RequestParam(defaultValue = "10") Integer size,
                               @RequestParam(defaultValue = "0") int sort){
            return Result.success( elasticService.pageQuery(name, key, page, size, sort));
        }
    }
    
    

    ResultInfo.java

    package com.lyf.model;
    
    import com.lyf.base.Constant.CodeEnum;
    
    import java.io.Serializable;
    
    public class ResultInfo implements Serializable {
    
        private static final long serialVersionUID = -6660878670189339288L;
        private Integer code = CodeEnum.SUCCESS.getCode();
        private String msg = CodeEnum.SUCCESS.getMsg();
        private Object result; // 返回结果
    
        public ResultInfo() {
        }
    
        public ResultInfo(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    
        public ResultInfo(String msg) {
            this.msg = msg;
        }
    
        public ResultInfo(Integer code) {
            this.code = code;
        }
    
    
        public ResultInfo(Integer code, String msg, Object result) {
            this.code = code;
            this.msg = msg;
            this.result = result;
        }
    
        public ResultInfo(Object result) {
            this.result = result;
        }
        public ResultInfo(String msg, Object result) {
            this.msg = msg;
            this.result = result;
        }
    
        public ResultInfo(Integer code, Object result) {
            this.code = code;
            this.result = result;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public Object getResult() {
            return result;
        }
    
        public void setResult(Object result) {
            this.result = result;
        }
    }
    
    

    Result.java工具类

    package com.lyf.util;
    
    import com.lyf.model.ResultInfo;
    
    public class Result {
        public static ResultInfo success(Integer code, String msg, Object result){
            ResultInfo resultInfo = new ResultInfo();
            resultInfo.setCode(code);
            resultInfo.setMsg(msg);
            resultInfo.setResult(result);
            return resultInfo;
        }
    
        public static ResultInfo success(String msg, Object result){
            ResultInfo resultInfo = new ResultInfo();
            resultInfo.setMsg(msg);
            resultInfo.setResult(result);
            return resultInfo;
        }
    
        public static ResultInfo success(Object result){
            ResultInfo resultInfo = new ResultInfo();
            resultInfo.setResult(result);
            return resultInfo;
        }
    
        public static ResultInfo success(Integer code, String msg){
            ResultInfo resultInfo = new ResultInfo();
            resultInfo.setCode(code);
            resultInfo.setMsg(msg);
            return resultInfo;
        }
    
        public static ResultInfo success(String msg){
            ResultInfo resultInfo = new ResultInfo();
            resultInfo.setMsg(msg);
            return resultInfo;
        }
    
        public static ResultInfo success(){
            ResultInfo resultInfo = new ResultInfo();
            return resultInfo;
        }
    
        public static ResultInfo error(Integer code, String msg){
            ResultInfo resultInfo = new ResultInfo();
            resultInfo.setCode(code);
            resultInfo.setMsg(msg);
            return resultInfo;
        }
    }
    
    

    测试请求如下

    • 初始化索引
    GET http://localhost:8765/es/init
    
    • 添加数据
    GET http://localhost:8765/es/add?id=1&title=小米手机7&category=手机&brand=小米&price=3499.00&images=http://image.leyou.com/13123.jpg
    
    GET http://localhost:8765/es/add?id=2&title=小米手机8&category=手机&brand=小米&price=3299.00&images=http://image.leyou.com/13124.jpg
    
    GET http://localhost:8765/es/add?id=3&title=华为手机8&category=手机&brand=华为&price=4299.00&images=http://image.leyou.com/321.jpg
    
    • 查询数据
    GET http://localhost:8765/es/find?id=1
    
    • 按价格倒序返回所有数据
    GET http://localhost:8765/es/all?name=price&sort=1
    
    • 返回指定价格区间数据
    GET http://localhost:8765/es/queryByPriceBetween?price1=3000&price2=4000
    
    • 返回分页数据
    GET http://localhost:8765/es/page?name=title&key=小米&sort=1
    
    • 删除索引
    GET http://localhost:8765/es/rm
    
    • 批量添加数据
    POST http://localhost:8765/es/addBatch
    Content-Type: application/json
    [
        {
          "id": 1,
          "title": "小米手机7",
          "category": "手机",
          "brand": "小米",
          "price": 3499.0,
          "images": "http://image.leyou.com/13123.jpg"
        },
        {
          "id": 2,
          "title": "小米手机8",
          "category": "手机",
          "brand": "小米",
          "price": 3299.0,
          "images": "http://image.leyou.com/13124.jpg"
        },
        {
          "id": 3,
          "title": "华为手机8",
          "category": "手机",
          "brand": "华为",
          "price": 4299.0,
          "images": "http://image.leyou.com/321.jpg"
        }
      ]
    
    • 索引起别名
    GET http://localhost:8765/es/alias?index=goods&alias=abc
    

    搭建过程和集成springboot中遇到的问题和bug,看下一篇博文 bug收集

    minimum_should_match 最少值匹配
    https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-minimum-should-match.html

    参考:

  • 相关阅读:
    VysorPro助手
    Play 2D games on Pixel running Android Nougat (N7.1.2) with Daydream View VR headset
    Play 2D games on Nexus 6P running Android N7.1.1 with Daydream View VR headset
    Native SBS for Android
    ADB和Fastboot最新版的谷歌官方下载链接
    How do I install Daydream on my phone?
    Daydream Controller手柄数据的解析
    蓝牙BLE传输性能及延迟分析
    VR(虚拟现实)开发资源汇总
    Android(Java)控制GPIO的方法及耗时分析
  • 原文地址:https://www.cnblogs.com/linyufeng/p/13045134.html
Copyright © 2011-2022 走看看