SprignBoot整合Spring Data Elasticsearch

Posted edda_huang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SprignBoot整合Spring Data Elasticsearch相关的知识,希望对你有一定的参考价值。

一、原生java整合elasticsearch的API地址

https://www.elastic.co/guide/en/elasticsearch/client/java-api/6.2/java-docs.html

二、Spring Data的官网

http://projects.spring.io/spring-data/

技术图片

Spring Data 是的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如mysql),还是非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提高开发效率。

包含很多不同数据操作的模块:

技术图片

技术图片

Spring Data Elasticsearch的页面:https://projects.spring.io/spring-data-elasticsearch/

技术图片

特征:

  • 支持Spring的基于@Configuration的java配置方式,或者XML配置方式

  • 提供了用于操作ES的便捷工具类ElasticsearchTemplate。包括实现文档到POJO之间的自动智能映射。

  • 利用Spring的数据转换服务实现的功能丰富的对象映射

  • 基于注解的元数据映射方式,而且可扩展以支持更多不同的数据格式

  • 根据持久层接口自动生成对应实现方法,无需人工编写基本操作代码(类似mybatis,根据接口自动得到实现)。当然,也支持人工定制查询

三、SprignBoot整合Spring Data Elasticsearch

(1)创建项目

创建spring boot工程

技术图片
(2)依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.1.7.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.es</groupId>
  12. <artifactId>es</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>es</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  18. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  19. <java.version>1.8</java.version>
  20. </properties>
  21. <dependencies>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-web</artifactId>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-starter-test</artifactId>
  37. <scope>test</scope>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.springframework.boot</groupId>
  41. <artifactId>spring-boot-starter-test</artifactId>
  42. <scope>test</scope>
  43. </dependency>
  44. </dependencies>
  45. <build>
  46. <plugins>
  47. <plugin>
  48. <groupId>org.springframework.boot</groupId>
  49. <artifactId>spring-boot-maven-plugin</artifactId>
  50. </plugin>
  51. </plugins>
  52. </build>
  53. </project>

(3)创建实体

映射---注解

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

  • @Document 作用在类,标记实体类为文档对象,一般有两个属性

    <ul><li>
    	<p>indexName:对应索引库名称</p>
    	</li>
    	<li>
    	<p>type:对应在索引库中的类型</p>
    	</li>
    	<li>
    	<p>shards:分片数量,默认5</p>
    	</li>
    	<li>
    	<p>replicas:副本数量,默认1</p>
    	</li>
    </ul></li>
    <li>
    <p><strong><code>@Id</code></strong> 作用在成员变量,标记一个字段作为id主键</p>
    </li>
    <li>
    <p><strong><code>@Field</code> </strong>作用在成员变量,标记为文档的字段,并指定字段映射属性:</p>
    
    <ul><li>
    	<p>type:字段类型,是是枚举:FieldType,可以是text、long、short、date、integer、object等</p>
    
    	<ul><li>
    		<p>text:存储数据时候,会自动分词,并生成索引</p>
    		</li>
    		<li>
    		<p>keyword:存储数据时候,不会分词建立索引</p>
    		</li>
    		<li>
    		<p>Numerical:数值类型,分两类</p>
    
    		<ul><li>
    			<p>基本数据类型:long、interger、short、byte、double、float、half_float</p>
    			</li>
    			<li>
    			<p>浮点数的高精度类型:scaled_float</p>
    
    			<ul><li>
    				<p>需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。</p>
    				</li>
    			</ul></li>
    		</ul></li>
    		<li>
    		<p>Date:日期类型</p>
    
    		<ul><li>
    			<p>elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。</p>
    			</li>
    		</ul></li>
    	</ul></li>
    	<li>
    	<p>index:是否索引,布尔类型,默认是true</p>
    	</li>
    	<li>
    	<p>store:是否存储,布尔类型,默认是false</p>
    	</li>
    	<li>
    	<p>analyzer:分词器名称,这里的<code>ik_max_word</code>即使用ik分词器</p>
    	</li>
    </ul></li>
    
  1. package com.es.bean;
  2. import org.springframework.data.annotation.Id;
  3. import org.springframework.data.elasticsearch.annotations.Document;
  4. import org.springframework.data.elasticsearch.annotations.Field;
  5. import org.springframework.data.elasticsearch.annotations.FieldType;
  6. @Document(indexName = "item", type = "docs", shards = 1, replicas = 0)
  7. public class Item {
  8. @Id
  9. private Long id;
  10. @Field(type = FieldType.Text, analyzer = "ik_max_word")
  11. private String title; //标题
  12. @Field(type = FieldType.Keyword)
  13. private String category;// 分类
  14. @Field(type = FieldType.Keyword)
  15. private String brand; // 品牌
  16. @Field(type = FieldType.Double)
  17. private Double price; // 价格
  18. @Field(type = FieldType.Keyword, index = false)
  19. private String images; // 图片地址
  20. public Long getId() {
  21. return id;
  22. }
  23. public void setId(Long id) {
  24. this.id = id;
  25. }
  26. public String getTitle() {
  27. return title;
  28. }
  29. public void setTitle(String title) {
  30. this.title = title;
  31. }
  32. public String getCategory() {
  33. return category;
  34. }
  35. public void setCategory(String category) {
  36. this.category = category;
  37. }
  38. public String getBrand() {
  39. return brand;
  40. }
  41. public void setBrand(String brand) {
  42. this.brand = brand;
  43. }
  44. public Double getPrice() {
  45. return price;
  46. }
  47. public void setPrice(Double price) {
  48. this.price = price;
  49. }
  50. public String getImages() {
  51. return images;
  52. }
  53. public void setImages(String images) {
  54. this.images = images;
  55. }
  56. public Item(Long id, String title, String category, String brand, Double price, String images) {
  57. this.id = id;
  58. this.title = title;
  59. this.category = category;
  60. this.brand = brand;
  61. this.price = price;
  62. this.images = images;
  63. }
  64. public Item() {
  65. }
  66. @Override
  67. public String toString() {
  68. return "Item{" +
  69. "id=" + id +
  70. ", title=‘" + title + ‘‘‘ +
  71. ", category=‘" + category + ‘‘‘ +
  72. ", brand=‘" + brand + ‘‘‘ +
  73. ", price=" + price +
  74. ", images=‘" + images + ‘‘‘ +
  75. ‘}‘;
  76. }
  77. }

(4)控制层

  1. package com.es.controller;
  2. import com.es.bean.Item;
  3. import com.es.dao.ItemRepository;
  4. import org.elasticsearch.index.query.QueryBuilders;
  5. import org.elasticsearch.search.aggregations.AggregationBuilders;
  6. import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
  7. import org.elasticsearch.search.aggregations.metrics.avg.InternalAvg;
  8. import org.elasticsearch.search.sort.SortBuilders;
  9. import org.elasticsearch.search.sort.SortOrder;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.data.domain.Page;
  12. import org.springframework.data.domain.PageRequest;
  13. import org.springframework.data.domain.Sort;
  14. import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
  15. import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
  16. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  17. import org.springframework.web.bind.annotation.GetMapping;
  18. import org.springframework.web.bind.annotation.RestController;
  19. import java.util.ArrayList;
  20. import java.util.List;
  21. @RestController
  22. public class ItemController {
  23. @Autowired
  24. ItemRepository itemRepository;
  25. @Autowired
  26. ElasticsearchTemplate esTemplate;
  27. /**
  28. * 创建索引
  29. * ElasticsearchTemplate中提供了创建索引的API
  30. */
  31. @GetMapping("/create/indices")
  32. public void createIndices() {
  33. // 创建索引,会根据Item类的@Document注解信息来创建
  34. esTemplate.createIndex(Item.class);
  35. // 配置映射,会根据Item类中的id、Field等字段来自动完成映射
  36. esTemplate.putMapping(Item.class);
  37. }
  38. /**
  39. * 删除索引
  40. */
  41. @GetMapping("/delete/indices")
  42. public void deleteIndices() {
  43. esTemplate.deleteIndex(Item.class);
  44. // 根据索引名字删除
  45. //esTemplate.deleteIndex("item");
  46. }
  47. /**
  48. * 创建单个索引
  49. */
  50. @GetMapping("/add/index")
  51. public void addIndex() {
  52. Item item = new Item(1L, "小米手机7", " 手机", "小米", 3499.00, "http://image.baidu.com/13123.jpg");
  53. itemRepository.save(item);
  54. }
  55. /**
  56. * 批量创建索引
  57. */
  58. @GetMapping("/add/index/list")
  59. public void addIndexList() {
  60. List<Item> list = new ArrayList<Item>();
  61. list.add(new Item(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.baidu.com/13123.jpg"));
  62. list.add(new Item(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.baidu.com/13123.jpg"));
  63. list.add(new Item(3L, "华为META10", "手机", "华为", 4499.00, "http://image.baidu.com/13123.jpg"));
  64. list.add(new Item(4L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.baidu.com/13123.jpg"));
  65. list.add(new Item(5L, "荣耀V10", "手机", "华为", 2799.00, "http://image.baidu.com/13123.jpg"));
  66. // 接收对象集合,实现批量新增
  67. itemRepository.saveAll(list);
  68. }
  69. /**
  70. * 修改索引
  71. */
  72. @GetMapping("/update/index")
  73. public void updateIndex() {
  74. Item item = new Item(1L, "苹果XSMax", "手机", "小米", 3499.00, "http://image.baidu.com/13123.jpg");
  75. itemRepository.save(item);
  76. }
  77. /**
  78. * 查询所有
  79. */
  80. @GetMapping("/find/index")
  81. public Object queryAll() {
  82. // 查找所有
  83. //Iterable<Item> list = this.itemRepository.findAll();
  84. // 对某字段排序查找所有 Sort.by("price").descending() 降序
  85. // Sort.by("price").ascending():升序
  86. Iterable<Item> list = this.itemRepository.findAll(Sort.by("price").ascending());
  87. for (Item item : list) {
  88. System.out.println(item);
  89. }
  90. return list;
  91. }
  92. /**
  93. * 价格范围查询
  94. */
  95. @GetMapping("/find/index/by/price")
  96. public Object queryByPriceBetween() {
  97. List<Item> list = itemRepository.findByPriceBetween(2000.00, 3500.00);
  98. for (Item item : list) {
  99. System.out.println("item = " + item);
  100. }
  101. return list;
  102. }
  103. @GetMapping("/find/index/findByCategoryAndPrice")
  104. public Object findByNameAndPrice() {
  105. List<Item> list = itemRepository.findByCategoryAndPrice("手机", 3699.00);
  106. for (Item item : list) {
  107. System.out.println(item);
  108. }
  109. return list;
  110. }
  111. /**
  112. * match底层是词条匹配
  113. *
  114. * @return
  115. */
  116. @GetMapping("/find/index/matchQuery")
  117. public Object matchQuery() {
  118. //构建查询条件
  119. NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
  120. //添加分词查询
  121. builder.withQuery(QueryBuilders.matchQuery("title", "华为"));
  122. // 查询 自动分页 ,默认查找第一页的10条数据
  123. Page<Item> list = itemRepository.search(builder.build());
  124. //总条数
  125. System.out.println(list.getTotalElements());
  126. for (Item it : list) {
  127. System.out.println(it);
  128. }
  129. return list;
  130. }
  131. /**
  132. * termQuery
  133. *
  134. * @return
  135. */
  136. @GetMapping("/find/index/termQuery")
  137. public Object termQuery() {
  138. // 查询条件生成器
  139. NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
  140. queryBuilder.withQuery(QueryBuilders.termQuery("price", 3499.00));
  141. // 查询 自动分页 ,默认查找第一页的10条数据
  142. Page<Item> list = itemRepository.search(queryBuilder.build());
  143. for (Item it : list) {
  144. System.out.println(it);
  145. }
  146. return list;
  147. }
  148. /**
  149. * booleanQuery
  150. *
  151. * @return
  152. */
  153. @GetMapping("/find/index/booleanQuery")
  154. public Object booleanQuery() {
  155. NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
  156. queryBuilder.withQuery(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("title", "华为")).must(QueryBuilders.matchQuery("brand", "华为")));
  157. //查找
  158. Page<Item> list = itemRepository.search(queryBuilder.build());
  159. System.out.println("总条数:" + list.getTotalElements());
  160. for (Item it : list) {
  161. System.out.println(it);
  162. }
  163. return list;
  164. }
  165. /**
  166. * 模糊查询
  167. *
  168. * @return
  169. */
  170. @GetMapping("/find/index/fuzzyQuery")
  171. public Object fuzzyQuery() {
  172. NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
  173. queryBuilder.withQuery(QueryBuilders.fuzzyQuery("title", "faceoooo"));
  174. Page<Item> list = itemRepository.search(queryBuilder.build());
  175. System.out.println("总条数:" + list.getTotalElements());
  176. for (Item it : list) {
  177. System.out.println(it);
  178. }
  179. return list;
  180. }
  181. /**
  182. * 分页查询
  183. *
  184. * @return
  185. */
  186. @GetMapping("/find/index/pageSearch")
  187. public Object pageSearch() {
  188. NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
  189. queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机"));
  190. //分页
  191. int page = 0;
  192. int size = 3;
  193. queryBuilder.withPageable(PageRequest.of(page, size));
  194. //搜索
  195. Page<Item> page1 = itemRepository.search(queryBuilder.build());
  196. //总条数
  197. System.out.println("总条数:" + page1.getTotalElements());
  198. //总页数
  199. System.out.println(page1.getTotalPages());
  200. // 当前页
  201. System.out.println(page1.getNumber());
  202. //每页大小
  203. System.out.println(page1.getSize());
  204. //所有数据
  205. for (Item item : page1) {
  206. System.out.println(item);
  207. }
  208. return page1;
  209. }
  210. /**
  211. * 排序查询
  212. */
  213. @GetMapping("/find/index/searchAndSort")
  214. public void searchAndSort() {
  215. // 构建查询条件
  216. NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
  217. // 添加基本分词查询
  218. queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机"));
  219. // 排序
  220. queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
  221. // 搜索,获取结果
  222. Page<Item> items = this.itemRepository.search(queryBuilder.build());
  223. // 总条数
  224. long total = items.getTotalElements();
  225. System.out.println("总条数 = " + total);
  226. for (Item item : items) {
  227. System.out.println(item);
  228. }
  229. }
  230. /**
  231. * 聚合查询
  232. * 聚合为桶bucket--分组--类似group by
  233. * 桶就是分组,比如这里我们按照品牌brand进行分组:
  234. */
  235. @GetMapping("/find/index/searchAgg")
  236. public Object searchAgg(){
  237. NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
  238. // 不查询任何结果
  239. //queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
  240. // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
  241. queryBuilder.addAggregation(
  242. AggregationBuilders.terms("brands").field("brand"));
  243. // 2、查询,需要把结果强转为AggregatedPage类型
  244. AggregatedPage<Item> aggPage = (AggregatedPage<Item>) this.itemRepository.search(queryBuilder.build());
  245. // 3、解析
  246. // 3.1、从结果中取出名为brands的那个聚合,
  247. // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
  248. StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
  249. // 3.2、获取桶
  250. List<StringTerms.Bucket> buckets = agg.getBuckets();
  251. // 3.3、遍历
  252. for (StringTerms.Bucket bucket : buckets) {
  253. // 3.4、获取桶中的key,即品牌名称
  254. System.out.println(bucket.getKeyAsString());
  255. // 3.5、获取桶中的文档数量
  256. System.out.println(bucket.getDocCount());
  257. }
  258. return buckets;
  259. }
  260. /**
  261. * 嵌套聚合,求平均值---度量
  262. * 需求:求桶--分组,每个品牌手机的平均价格
  263. * 思路:(分组求桶) + 求平均值(度量)
  264. */
  265. @GetMapping("/find/index/subAgg")
  266. public Object subAgg() {
  267. NativeSearchQueryBuilder queryBuilder1 = new NativeSearchQueryBuilder();
  268. queryBuilder1.addAggregation(AggregationBuilders.terms("brands").field("brand")
  269. .subAggregation(AggregationBuilders.avg("priceAvg").field("price")));
  270. AggregatedPage<Item> aggregatedPage = (AggregatedPage<Item>) itemRepository.search(queryBuilder1.build());
  271. StringTerms brands = (StringTerms) aggregatedPage.getAggregation("brands");
  272. List<StringTerms.Bucket> buckets = brands.getBuckets();
  273. for (StringTerms.Bucket bu : buckets) {
  274. System.out.print(bu.getKeyAsString() + " " + bu.getDocCount() + " ");
  275. InternalAvg avg = (InternalAvg) bu.getAggregations().asMap().get("priceAvg");
  276. System.out.println(avg.getValue());
  277. }
  278. return buckets;
  279. }
  280. }

(5)Repository接口

  1. package com.es.dao;
  2. import com.es.bean.Item;
  3. import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
  4. import java.util.List;
  5. /**
  6. * 接口关系:
  7. * ElasticsearchRepository --> ElasticsearchCrudRepository --> PagingAndSortingRepository --> CrudRepository
  8. */
  9. public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
  10. /**
  11. * 根据价格区间查询
  12. * @param price1
  13. * @param price2
  14. * @return
  15. */
  16. List<Item> findByPriceBetween(double price1, double price2);
  17. List<Item> findByCategoryAndPrice(String name, double price);
  18. }

(6)application.properties配置

技术图片

  1. spring.data.elasticsearch.repositories.enabled=true
  2. spring.data.elasticsearch.cluster-name=zzq-es
  3. spring.data.elasticsearch.cluster-nodes=192.168.1.16:9300

(7)启动类

  1. package com.es;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class EsApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(EsApplication.class, args);
  8. }
  9. }

四、总结

(1)端口问题

redis:   6379
mq:   浏览器访问  6181
         代码访问  61616
es: 浏览器访问  9200
          代码访问  9300

 

(2)自定义方法

Keyword Sample
And findByNameAndPrice findBy属性名1And属性名2
Or findByNameOrPrice
Is findByName
Not findByNameNot
Between findByPriceBetween
LessThanEqual findByPriceLessThan
GreaterThanEqual findByPriceGreaterThan
Before findByPriceBefore
After findByPriceAfter
Like findByNameLike
StartingWith findByNameStartingWith
Contains/Containing findByNameContaining
In findByNameIn(Collection<String>names)
NotIn findByNameNotIn(Collection<String>names)
Near findByStoreNear
True findByAvailableTrue
False findByAvailableFalse
OrderBy findByAvailableTrueOrderByNameDesc

例如

  1. public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
  2. /**
  3. * 根据价格区间查询
  4. * @param price1
  5. * @param price2
  6. * @return
  7. */
  8. List<Item> findByPriceBetween(double price1, double price2);
  9. }

(3)基本概念

Elasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与关系型数据相似。

对比关系:

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

  1. 类型(type)-----------------------------Table 数据表
  2. 文档(Document)----------------Row 行
  3. 字段(Field)-------------------Columns 列
概念 说明
索引库(indices) indices是index的复数,代表许多的索引,
类型(type) 类型是模拟mysql中的table概念,一个索引库下可以有不同类型的索引,比如商品索引,订单索引,其数据格式不同。不过这会导致索引库混乱,因此未来版本中会移除这个概念
文档(document) 存入索引库原始的数据。比如每一条商品信息,就是一个文档
字段(field) 文档中的属性
映射配置(mappings) 字段的数据类型、属性、是否索引、是否存储等特性

另外,在Elasticsearch有一些集群相关的概念:

索引集(Indices,index的复数):逻辑上的完整索引
分片(shard):数据拆分后的各个部分
副本(replica):每个分片的复制
要注意的是:Elasticsearch本身就是分布式的,因此即便你只有一个节点,Elasticsearch默认也会对你的数据进行分片和副本操作,当你向集群添加新数据时,数据也会在新加入的节点中进行平衡。
 

github : https://github.com/2014team/elasticsearch

推荐文章:https://blog.csdn.net/weixin_42633131/article/details/82902812










以上是关于SprignBoot整合Spring Data Elasticsearch的主要内容,如果未能解决你的问题,请参考以下文章

《SpringCloud 从入门到入土 》 第二章:微服务构建:Spring Boot

《SpringCloud 从入门到入土 》 第二章:微服务构建:Spring Boot

Spring整合HibernateHibernate JPASpring Data JPASpring Data Redis

Spring整合HibernateHibernate JPASpring Data JPASpring Data Redis

SpringBoot整合spring-data-jpa

Spring Boot 整合Spring Data JPA