Elasticsearch安装及SpringBoot整合ElasticSearch

一、Elasticsearch基本概念

Elasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与MySQL类似的

1)、对比关系

索引库(indices)->Databases 数据库

类型(type)->Table 数据表

文档(Document)->Row 行

字段(Field)->Columns 列

2)、详细说明

概念 说明
索引库(indices) indices是index的复数,代表许多的索引
类型(type) 类型是模拟mysql中的table概念,一个索引库下可以有不同类型的索引,比如商品索引,订单索引,其数据格式不同。在Elasticsearch6.0中默认只能支持一个索引一个type
文档(document) 存入索引库原始的数据。比如每一条商品信息,就是一个文档
字段(field) 文档中的属性
映射配置(mappings) 字段的数据类型、属性、是否索引、是否存储等特性

在Elasticsearch中有一些集群相关的概念:

索引集(Indices,index的复数):逻辑上的完整索引

分片(shard):数据拆分后的各个部分

副本(replica):每个分片的复制

Elasticsearch本身就是分布式的,因此即便你只有一个节点,Elasticsearch默认也会对数据进行分片和副本操作,当向集群添加新数据时,数据也会在新加入的节点中进行平衡

二、Window上安装Elasticsearch

地址:https://www.elastic.co/cn/downloads/past-releases

本次使用的Elasticsearch版本为6.2.4
在这里插入图片描述Elasticsearch安装及SpringBoot整合ElasticSearch_第1张图片
下载后解压,进入elasticsearch-6.2.4\bin,双击elasticsearch.bat文件启动
Elasticsearch安装及SpringBoot整合ElasticSearch_第2张图片
访问http://127.0.0.1:9200/,看到如下效果证明启动成功
Elasticsearch安装及SpringBoot整合ElasticSearch_第3张图片

三、Elasticsearch安装Head插件

1)、Head插件介绍

Elasticsearch-head将是一款专门针对于Elasticsearch的客户端工具

Elasticsearch-head配置包,下载地址:https://github.com/mobz/elasticsearch-head

2)、Elasticsearch5以上版本安装head需要安装node和grunt

安装node:https://nodejs.org/en/download/进行下载安装

安装grunt:

# 安装命令
npm install -g grunt-cli
# 查看安装版本号,检查是否安装成功
grunt -version

3)、配置运行

进入Elasticsearch安装目录下的config目录,修改elasticsearch.yml文件.在文件的末尾加入以下代码,并重启Elasticsearch

http.cors.enabled: true 
http.cors.allow-origin: "*"
node.master: true
node.data: true

从GitHub中将Elasticsearch-head插件下载下来,修改目录下的Gruntfile.js文件,添加hostname: '*',修改后如下

		connect: {
			server: {
				options: {
					hostname: '*',
					port: 9100,
					base: '.',
					keepalive: true
				}
			}
		}

在elasticsearch-head-master目录下开启cmd窗口,依次执行如下命令:

# 安装
npm install
# 运行head插件
grunt server
或
npm run start

打开浏览器访问:http://127.0.0.1:9100
Elasticsearch安装及SpringBoot整合ElasticSearch_第4张图片

四、安装Ik分词器

1)、下载

下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases

注意:Elasticsearch和IK分词器必须版本统一
Elasticsearch安装及SpringBoot整合ElasticSearch_第5张图片

2)、安装

解压到到Elasticsearch的plugins目录下,改名为ik

目录结构如下:
Elasticsearch安装及SpringBoot整合ElasticSearch_第6张图片
重启Elasticsearch,启动日志中打印如下日志证明Ik分词器安装成功
Elasticsearch安装及SpringBoot整合ElasticSearch_第7张图片

3)、IK扩展词和停用词

修改IKAnalyzer.cfg.xml配置文件
Elasticsearch安装及SpringBoot整合ElasticSearch_第8张图片

<properties>
	<comment>IK Analyzer 扩展配置comment>
	
	<entry key="ext_dict">entry>
	 
	<entry key="ext_stopwords">entry>
	
	
	
	
properties>

五、项目基础配置

1)、添加依赖

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-elasticsearchartifactId>
        dependency>

版本对应关系:

Spring Data Elasticsearch Elasticsearch
3.2.x 6.7.2
3.1.x 6.2.2
3.0.x 5.5.0
2.1.x 2.4.0
2.0.x 2.2.0
1.3.x 1.5.2

详细查看https://github.com/spring-projects/spring-data-elasticsearch

本次使用的是SpringBoot2.1.5.RELEASE版本,对应spring-data-elasticsearch的默认版本为3.1.8.RELEASE

2)、application.properties配置文件

#集群名和配置文件elasticsearch.yml中的cluster.name对应
spring.data.elasticsearch.cluster-name=my-application
#集群节点地址列表,用逗号分隔
spring.data.elasticsearch.cluster-nodes=localhost:9300
#开启Elasticsearch仓库
spring.data.elasticsearch.repositories.enabled=true

修改Elasticsearch的config目录下的配置文件elasticsearch.yml

cluster.name: my-application

六、索引操作

业务:创建一个商品对象,有这些属性:

id,title,category,brand,price,图片地址

1)、索引和映射

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Item {
    private Long id;
    private String title; //标题
    private String category;// 分类
    private String brand; // 品牌
    private Double price; // 价格
    private String images; // 图片地址

}

Spring Data通过注解来声明字段的映射属性,有下面的三个注解:

@Document:作用在类,标记实体类为文档对象,一般有两个属性
 indexName:对应索引库名称
 type:对应在索引库中的类型
 shards:分片数量,默认5
 replicas:副本数量,默认1
 
@Id:作用在成员变量,标记一个字段作为id主键

@Field:作用在成员变量,标记为文档的字段,并指定字段映射属性:

 type:字段类型,是枚举:FieldType,可以是text、long、short、date、integer、object等
  text:存储数据时候,会自动分词,并生成索引
  keyword:存储数据时候,不会分词建立索引
  Numerical:数值类型,分两类
   基本数据类型:long、interger、short、byte、double、float、half_float
   浮点数的高精度类型:scaled_float,需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原
   Date:日期类型,elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间
   
 index:是否索引,布尔类型,默认是true
 store:是否存储,布尔类型,默认是false
 analyzer:分词器名称,这里的ik_max_word即使用ik分词器
@AllArgsConstructor
@NoArgsConstructor
@Data
@Document(indexName = "item", type = "docs")
public class Item {
    @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; // 图片地址

}

ElasticsearchTemplate提供了API操作索引和映射关系的API

创建索引:

可以根据类的信息自动生成,也可以手动指定indexName和Settings

	public <T> boolean createIndex(Class<T> clazz)
        
	public boolean createIndex(String indexName)

	public boolean createIndex(String indexName, Object settings)

	public <T> boolean createIndex(Class<T> clazz, Object settings)        

映射:

	public <T> boolean putMapping(Class<T> clazz)
        
	public <T> boolean putMapping(Class<T> clazz, Object mapping)
        
	public boolean putMapping(String indexName, String type, Object mapping)        

1)创建索引并映射

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Test
    public void createIndexTest() {
        elasticsearchTemplate.createIndex(Item.class);
        elasticsearchTemplate.putMapping(Item.class);
    }

Elasticsearch安装及SpringBoot整合ElasticSearch_第9张图片

2)删除索引

	public <T> boolean deleteIndex(Class<T> clazz)

	public boolean deleteIndex(String indexName)        
    @Test
    public void deleteIndexTest() {
        elasticsearchTemplate.deleteIndex(Item.class);
    }

2)、新增文档数据

1)ElasticsearchRepository接口

@NoRepositoryBean
public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {

	<S extends T> S index(S entity);

	Iterable<T> search(QueryBuilder query);

	Page<T> search(QueryBuilder query, Pageable pageable);

	Page<T> search(SearchQuery searchQuery);

	Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);

	void refresh();

	Class<T> getEntityClass();
}

只要定义一个接口,继承ElasticsearchRepository就能实现基本的CRUD功能

public interface ItemRepository extends ElasticsearchRepository<Item, Long> {
}

2)新增一个对象

    @Autowired
    private ItemRepository itemRepository;

    @Test
    public void insertTest() {
        Item item = new Item(1L, "红米6A AI美颜 全网通4G手机 双卡双待 樱花粉", "手机",
                "小米", 649.00, "https://img14.360buyimg.com/n0/jfs/t27382/325/1059766233/108388/312ba10a/5bc02ed1Nbb470630.jpg");
        itemRepository.save(item);
    }

3)批量新增

    @Test
    public void insertListTest() {
        List<Item> list = new ArrayList<>();
        list.add(new Item(2L, "诺基亚 NOKIA X71", "手机",
                "诺基亚", 1999.00, "https://img14.360buyimg.com/n0/jfs/t1/27974/40/14932/117930/5cab0a31Eabb2cce7/209b50731b5c3a24.jpg"));
        list.add(new Item(3L, "华为(HUAWEI) 荣耀8X", "手机",
                "华为", 1699.00, "https://img14.360buyimg.com/n0/jfs/t1/29898/28/9910/112181/5c81d469E3fef484f/0d84baad19fb22b8.jpg"));
        itemRepository.saveAll(list);
    }

4)、修改

Elasticsearch中本没有修改,它的修改原理是该是先删除在新增

修改和新增是同一个接口,区分的依据就是id

    @Test
    public void updateTest() {
        Item item = new Item(1L, "红米6A", "手机",
                "小米", 600.00, "https://img14.360buyimg.com/n0/jfs/t27382/325/1059766233/108388/312ba10a/5bc02ed1Nbb470630.jpg");
        itemRepository.save(item);
    }

3)、查询

1)基本查询

ElasticsearchRepository提供了一些基本的查询方法:
Elasticsearch安装及SpringBoot整合ElasticSearch_第10张图片

    @Test
    public void findAllTest() {
        Iterable<Item> list = itemRepository.findAll();
        for (Item item : list) {
            System.out.println(item);
        }
    }

2)自定义方法

Spring Data 的另一个强大功能,是根据方法名称自动实现功能

Keyword Sample
And findByNameAndPrice
Or findByNameOrPrice
Is findByName
Not findByNameNot
Between findByPriceBetween
LessThanEqual findByPriceLessThan
GreaterThanEqual findByPriceGreaterThan
Before findByPriceBefore
After findByPriceAfter
Like findByNameLike
StartingWith findByNameStartingWith
EndingWith findByNameEndingWith
Contains/Containing findByNameContaining
In findByNameIn(Collectionnames)
NotIn findByNameNotIn(Collectionnames)
Near findByStoreNear
True findByAvailableTrue
False findByAvailableFalse
OrderBy findByAvailableTrueOrderByNameDesc

通过Title查询商品然后按照Id进行排序

public interface ItemRepository extends ElasticsearchRepository<Item, Long> {
    List<Item> findByTitleOrderById(String title);
}

添加测试数据:

    @Test
    public void insert() {
        List<Item> list = new ArrayList<>();
        list.add(new Item(4L, "红米6A 2", "手机",
                "小米", 600.00, "https://img14.360buyimg.com/n0/jfs/t27382/325/1059766233/108388/312ba10a/5bc02ed1Nbb470630.jpg"));
        list.add(new Item(5L, "红米6A 3", "手机",
                "小米", 600.00, "https://img14.360buyimg.com/n0/jfs/t27382/325/1059766233/108388/312ba10a/5bc02ed1Nbb470630.jpg"));
        list.add(new Item(6L, "红米6A 4", "手机",
                "小米", 600.00, "https://img14.360buyimg.com/n0/jfs/t27382/325/1059766233/108388/312ba10a/5bc02ed1Nbb470630.jpg"));
        list.add(new Item(7L, "红米6A 5", "手机",
                "小米", 600.00, "https://img14.360buyimg.com/n0/jfs/t27382/325/1059766233/108388/312ba10a/5bc02ed1Nbb470630.jpg"));
        itemRepository.saveAll(list);
    }
    @Test
    public void test01() {
        List<Item> list = itemRepository.findByTitleOrderById("红");
        for (Item item : list) {
            System.out.println(item);
        }
    }

3)自定义查询

    //matchQuery
    @Test
    public void test01() {
        //创建对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //在queryBuilder对象中自定义查询
        //matchQuery底层就是使用的termQuery
        queryBuilder.withQuery(QueryBuilders.matchQuery("title", "红米"));
        //查询,search 默认就是分页查找
        Page<Item> page = this.itemRepository.search(queryBuilder.build());
        //获取数据
        long totalElements = page.getTotalElements();
        System.out.println("获取的总条数:" + totalElements);
        for (Item item : page) {
            System.out.println(item);
        }
    }

    //termQuery:功能更强大,除了匹配字符串以外,还可以匹配int/long/double/float等
    @Test
    public void test02() {
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        builder.withQuery(QueryBuilders.termQuery("price", 600.00));
        //查找
        Page<Item> page = this.itemRepository.search(builder.build());
        for (Item item : page) {
            System.out.println(item);
        }
    }

    //boolQuery
    @Test
    public void test03() {
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        builder.withQuery(
                QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("title", "红米"))
                        .must(QueryBuilders.matchQuery("brand", "小米")));
        //查找
        Page<Item> page = this.itemRepository.search(builder.build());
        for (Item item : page) {
            System.out.println(item);
        }
    }

    //fuzzyQuery(模糊查询)
    @Test
    public void test04() {
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        builder.withQuery(QueryBuilders.fuzzyQuery("title", "红"));
        Page<Item> page = this.itemRepository.search(builder.build());
        for (Item item : page) {
            System.out.println(item);
        }
    }

matchQuery和termQuery的区别

matchQuery会先对搜索词进行分词,分词完毕后再逐个对分词结果进行匹配

termQuery是代表完全匹配,也就是精确查询,搜索前不会再对搜索词进行分词

4)分页查询

    @Test
    public void searchByPage() {
        //构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加基本分词查询
        queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机"));
        // 分页:
        int page = 0;
        int size = 2;
        queryBuilder.withPageable(PageRequest.of(page, size));
        //搜索,获取结果
        Page<Item> items = this.itemRepository.search(queryBuilder.build());
        //总条数
        long total = items.getTotalElements();
        System.out.println("总条数 = " + total);
        //总页数
        System.out.println("总页数 = " + items.getTotalPages());
        //当前页
        System.out.println("当前页:" + items.getNumber());
        //每页大小
        System.out.println("每页大小:" + items.getSize());
        for (Item item : items) {
            System.out.println(item);
        }
    }

Elasticsearch中的分页是从第0页开始

5)排序

    @Test
    public void searchAndSort(){
        //构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加基本分词查询
        queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机"));
        //排序
        queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
        //搜索,获取结果
        Page<Item> items = this.itemRepository.search(queryBuilder.build());
        //总条数
        long total = items.getTotalElements();
        System.out.println("总条数 = " + total);
        for (Item item : items) {
            System.out.println(item);
        }
    }

4)、聚合

1)聚合基本概念

桶:按照某种方式对数据进行分组,每一组数据在Elasticsearch中称为一个桶

度量:分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在Elasticsearch中称度量

2)聚合为桶

按照品牌brand进行分组:

    @Test
    public void testAgg() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        //1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
        queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand"));
        //2、查询,需要把结果强转为AggregatedPage类型
        AggregatedPage<Item> aggPage = (AggregatedPage<Item>) this.itemRepository.search(queryBuilder.build());
        //3、解析
        //3.1、从结果中取出名为brands的那个聚合,
        //因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
        StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
        //3.2、获取桶
        List<StringTerms.Bucket> buckets = agg.getBuckets();
        //3.3、遍历
        for (StringTerms.Bucket bucket : buckets) {
            //3.4、获取桶中的key,即品牌名称
            System.out.println(bucket.getKeyAsString());
            //3.5、获取桶中的文档数量
            System.out.println(bucket.getDocCount());
        }
    }

运行结果:

小米
5
华为
1
诺基亚
1

关键API:

AggregationBuilders:聚合的构建工厂类。所有聚合都由这个类来构建:

1)统计某个字段的数量
  ValueCountBuilder vcb=  AggregationBuilders.count("count_uid").field("uid");2)去重统计某个字段的数量(有少量误差)
 CardinalityBuilder cb= AggregationBuilders.cardinality("distinct_count_uid").field("uid");3)聚合过滤
FilterAggregationBuilder fab= AggregationBuilders.filter("uid_filter").filter(QueryBuilders.queryStringQuery("uid:001"));4)按某个字段分组
TermsBuilder tb=  AggregationBuilders.terms("group_name").field("name");5)求和
SumBuilder  sumBuilder=	AggregationBuilders.sum("sum_price").field("price");6)求平均
AvgBuilder ab= AggregationBuilders.avg("avg_price").field("price");7)求最大值
MaxBuilder mb= AggregationBuilders.max("max_price").field("price");8)求最小值
MinBuilder min=	AggregationBuilders.min("min_price").field("price");9)按日期间隔分组
DateHistogramBuilder dhb= AggregationBuilders.dateHistogram("dh").field("date");10)获取聚合里面的结果
TopHitsBuilder thb=  AggregationBuilders.topHits("top_result");11)嵌套的聚合
NestedBuilder nb= AggregationBuilders.nested("negsted_path").path("quests");12)反转嵌套
AggregationBuilders.reverseNested("res_negsted").path("kps");

AggregatedPage:聚合查询的结果类。它是Page的子接口

	//判断结果中是否有聚合
	boolean hasAggregations();

	//获取所有聚合形成的map,key是聚合名称
	Aggregations getAggregations();

	//根据聚合名称,获取指定聚合
	Aggregation getAggregation(String name);

3)嵌套聚合,求平均值

    @Test
    public void testSubAgg() {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        //1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
        queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand")
                .subAggregation(AggregationBuilders.avg("priceAvg").field("price")) //在品牌聚合桶内进行嵌套聚合,求平均值
        );
        //2、查询,需要把结果强转为AggregatedPage类型
        AggregatedPage<Item> aggPage = (AggregatedPage<Item>) this.itemRepository.search(queryBuilder.build());
        //3、解析
        //3.1、从结果中取出名为brands的那个聚合,
        //因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
        StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
        //3.2、获取桶
        List<StringTerms.Bucket> buckets = agg.getBuckets();
        //3.3、遍历
        for (StringTerms.Bucket bucket : buckets) {
            //3.4、获取桶中的key,即品牌名称  3.5、获取桶中的文档数量
            System.out.println(bucket.getKeyAsString() + ",共" + bucket.getDocCount() + "台");
            //3.6.获取子聚合结果:
            InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("priceAvg");
            System.out.println("平均售价:" + avg.getValue());
        }
    }

运行结果:

小米,共5台
平均售价:600.0
华为,共1台
平均售价:1699.0
诺基亚,共1台
平均售价:1999.0

你可能感兴趣的:(#,Elasticsearch,Ik分词器,Elasticsearch安装)