• 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

    参考:

  • 相关阅读:
    深入浅出:了解前后端分离优势、前后端接口联调以及优化问题
    深入浅出:了解JavaScript中的call,apply,bind的差别
    Vue2.0 搭建Vue脚手架(vue-cli)
    深入浅出:promise的各种用法
    深入浅出:了解常见的设计模式(闭包、垃圾回收机制)
    sql server xml 功能
    sqlite 用法
    PowerDesigner使用
    asp.net 开发注意的几点
    vue template
  • 原文地址:https://www.cnblogs.com/linyufeng/p/13045134.html
Copyright © 2020-2023  润新知