Elasticsearch|Elasticsearch 整合 Spring Boot(2)

在上一篇中我们已经准备好了数据,接下来就是创建索引、将数据添加到索引中了。
在Elasticsearch 使用 Java High Level REST Client 操作索引、文档中,我们引入了 Spring Data Elasticsearch 的依赖,这样我们就可以利用 Spring Data 的一些特性来简化 ES 的操作。
先贴一下参考官方文档 Spring Data Elasticsearch - Reference Documentation
1、连接 ES 整合 Spring Boot 后,我们连接 ES 节点的方式就很简单了,只需要在application.properties添加各个节点的 http 地址,多个地址用英文逗号隔开:

spring.elasticsearch.rest.uris=http://127.0.0.1:9200,http://127.0.0.1:9201,http://127.0.0.1:9202

2、创建文档数据实体类 前边我们将采集到的的数据保存成了 JSON 格式,结构如下:

Elasticsearch|Elasticsearch 整合 Spring Boot(2)
文章图片
我们先要创建 JSON 数据对应的实体类Book
@Document(indexName = "book") public class Book { private String id; @Field(type = FieldType.Keyword) private String skuId; @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart") private String name; @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart") private String author; private Float price; @Field(type = FieldType.Integer) private Integer commentCount; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String shop; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String publisher; @Field(type = FieldType.Text) private String img; ...省略get\set方法... }

@Document标记该类对应索引库中的文档信息,同时指定了索引库的名字为book,还可以指定分片数量;类里边的id属性是必需的,对应文档 id;类里边其它属性上的@Field注解相当于定义了文档中各个字段的类型、分词。
3、创建索引、添加文档数据 3.1、ElasticsearchRepository
3.1.1、创建索引 【Elasticsearch|Elasticsearch 整合 Spring Boot(2)】使用 Spring Data 的特性,创建BookRepository,继承ElasticsearchRepository,泛型参数Book就是上边的实体类,String则是文档 id 的类型:
public interface BookRepository extends ElasticsearchRepository { }

完成上边的工作后,启动项目,会自动帮我们创建好索引库。
3.1.2、添加文档 创建好了索引库,就可以添加文档了。
创建一个BookService类,通过addBook()方法来批量添加文档数据:
@Service public class BookService { @Autowired BookRepository bookRepository; public void addBook(List books) { bookRepository.saveAll(books); } }

由于我们采集到的数据事先保存在了文件中,所以需要从文件中读取数据,在调用上边的addBook()来添加:
@Service public class BookFileService { @Autowired BookService bookService; /** * 将去重后的数据写入 ES */ public void writeBookDataToES() { String filePath = System.getProperty("user.dir") + File.separator + "jd_book2.txt"; FileReader fileReader = null; BufferedReader bufferedReader = null; try { fileReader = new FileReader(filePath); bufferedReader = new BufferedReader(fileReader); String line; ArrayList books = new ArrayList<>(); while ((line = bufferedReader.readLine()) != null) { books.add(JSON.parseObject(line, Book.class)); if (books.size() >= 500) { // 添加数据到 ES bookService.addBook(books); books.clear(); } } bookService.addBook(books); } catch (Exception e) { e.printStackTrace(); } finally { ...... } } }

代码比较简单,就是逐行读取文件,然后批量添加即可。
最后执行writeBookDataToES()方法即可完成添加,速度还是很快的,基本10秒左右就完成了:
@RunWith(SpringRunner.class) @SpringBootTest class LearnElasticsearchApplicationTests {@Autowired BookFileService bookFileService; @Test void testES() throws IOException { bookFileService.writeBookDataToES(); } }

最后可以在 head 工具中浏览添加好的数据。
前边我们通过继承ElasticsearchRepository,扩展了BookRepository类,利用 Spring Data Repository 的特性,不需要再编写额外的代码,使用BookRepository就可以完成文档的添加、删除、查询等操作。遵照按照 Spring Data 的规范,我们还可以根据需要来扩展其它的方法来操作文档。如果你之前用过 Spring Data JPA,那么这个就很容易理解了。
3.2、ElasticsearchRestTemplate
ElasticsearchRestTemplate相当于一个 ES 客户端,它的内部基于 Java High Level REST Client 实现的。你可能还见过ElasticsearchTemplate,但是从 Spring Data 4.0开始已经过时了,目前推荐使用 ElasticsearchRestTemplate。
相比 ElasticsearchRepository 它的功能更丰富一些、效率更高,而且不用和数据体类绑定,使用 ElasticsearchRestTemplate 除了可以操作文档,还可以操作索引。
我们重点要学习的就是 ElasticsearchRestTemplate,它的主要功能通过如下三个接口定义的:
  • IndexOperations,定义索引相关的操作,例如创建、删除索引。
  • DocumentOperations,定义文档相关的操作,例如添加、删除、更新、基于文档 id 的简单查询。
  • SearchOperations,定义各种文档查询的操作。
3.2.1、创建索引 前边是通过定义BookRepository来实现索引的自动创建,其实有了ElasticsearchRestTemplate,我们可以完全不使用 Spring Data Repository 相关的扩展类,注释掉BookRepository,我们现在使用 ElasticsearchRestTemplate 来创建索引:
@Service public class BookService {@Autowired ElasticsearchRestTemplate elasticsearchRestTemplate; public void createIndex() { // 指定文档的数据实体类 IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Book.class); // 创建索引 indexOperations.create(); // 创建字段映射 Document mapping = indexOperations.createMapping(); // 给索引设置字段映射 indexOperations.putMapping(mapping); } }

删除索引就简单了,顺便说一下:
public void deleteIndex() { IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Book.class); boolean result = indexOperations.delete(); System.out.println(result); }

3.2.2、添加文档 首先我们可以使用 ElasticsearchRestTemplatesave()方法添加文档:
@Service public class BookService { @Autowired ElasticsearchRestTemplate elasticsearchRestTemplate; public void addBook(List books) { elasticsearchRestTemplate.save(books); } }

除了save()方法,我们还可以使用bulkIndex()方法,它是一次批量添加数据的,而save()是逐个添加。来看如何使用bulkIndex()方法:
@Service public class BookService {@Autowired ElasticsearchRestTemplate elasticsearchRestTemplate; public void bulkAddBook(List books) { List indexQueryList = new ArrayList<>(); books.forEach(book -> { IndexQuery indexQuery = new IndexQuery(); indexQuery.setObject(book); indexQueryList.add(indexQuery); }); elasticsearchRestTemplate.bulkIndex(indexQueryList, IndexCoordinates.of("book")); } }

主要是通过IndexQuerysetObject()方法设置要添加的数据,bulkIndex()方法第二个参数用来指定索引信息。
如果需要的话可以使用IndexQuerysetId()方法来设置文档 id。
我的采集到的数据是以 JSON 格式保存在文件中,能否直接添加 JSON 数据呢,而不是转换成实体类的对象再添加。这个需求我们同样可以用bulkIndex()来完成:
@Service public class BookService {@Autowired ElasticsearchRestTemplate elasticsearchRestTemplate; public void bulkAddBook2(List books) { List indexQueryList = new ArrayList<>(); books.forEach(book -> { IndexQuery indexQuery = new IndexQuery(); indexQuery.setSource(book); indexQueryList.add(indexQuery); }); elasticsearchRestTemplate.bulkIndex(indexQueryList, IndexCoordinates.of("book")); } }

其实就是通过IndexQuerysetSource()方法设置要添加的原始 JSON 数据。
这样从文件读取的数据就可以直接添加了:
public void writeBookDataToES2() { ...... try { fileReader = new FileReader(filePath); bufferedReader = new BufferedReader(fileReader); String line; ArrayList books = new ArrayList<>(); while ((line = bufferedReader.readLine()) != null) { books.add(line); if (books.size() >= 500) { bookService.bulkAddBook2(books); books.clear(); } } bookService.bulkAddBook2(books); } catch (Exception e) { e.printStackTrace(); } finally { ...... } }

注意,使用ElasticsearchRestTemplatesave()或者bulkIndex()方法添加文档时,如果添加了相同文档 id 的数据,则之前的会被覆盖掉。如果是使用ElasticsearchRepositorysave()方法,则无法正常添加。
本文详细的代码可以参考:https://github.com/SheHuan/LearnElasticsearch

    推荐阅读