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

参考:

posted @ 2020-06-04 18:00  林宇风  阅读(665)  评论(0编辑  收藏  举报