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-6.2.4\bin,双击elasticsearch.bat文件启动
在这里插入图片描述
访问http://127.0.0.1:9200/,看到如下效果证明启动成功
在这里插入图片描述

三、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
在这里插入图片描述

四、安装Ik分词器

1)、下载

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

注意:Elasticsearch和IK分词器必须版本统一
在这里插入图片描述

2)、安装

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

目录结构如下:
在这里插入图片描述
重启Elasticsearch,启动日志中打印如下日志证明Ik分词器安装成功
在这里插入图片描述

3)、IK扩展词和停用词

修改IKAnalyzer.cfg.xml配置文件
在这里插入图片描述

<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict"></entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<!-- <entry key="remote_ext_dict">words_location</entry> -->
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

五、项目基础配置

1)、添加依赖

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

版本对应关系:

Spring Data ElasticsearchElasticsearch
3.2.x6.7.2
3.1.x6.2.2
3.0.x5.5.0
2.1.x2.4.0
2.0.x2.2.0
1.3.x1.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);
    }

在这里插入图片描述

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提供了一些基本的查询方法:
在这里插入图片描述

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

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

KeywordSample
AndfindByNameAndPrice
OrfindByNameOrPrice
IsfindByName
NotfindByNameNot
BetweenfindByPriceBetween
LessThanEqualfindByPriceLessThan
GreaterThanEqualfindByPriceGreaterThan
BeforefindByPriceBefore
AfterfindByPriceAfter
LikefindByNameLike
StartingWithfindByNameStartingWith
EndingWithfindByNameEndingWith
Contains/ContainingfindByNameContaining
InfindByNameIn(Collection<String>names)
NotInfindByNameNotIn(Collection<String>names)
NearfindByStoreNear
TruefindByAvailableTrue
FalsefindByAvailableFalse
OrderByfindByAvailableTrueOrderByNameDesc

通过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<T>的子接口

	//判断结果中是否有聚合
	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
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邋遢的流浪剑客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值