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 格式,结构如下:
我们先要创建 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、创建索引
使用 Spring Data 的特性,创建BookRepository,继承ElasticsearchRepository,泛型参数Book就是上边的实体类,String则是文档 id 的类型:
public interface BookRepository extends ElasticsearchRepository<Book, String> {
}
完成上边的工作后,启动项目,会自动帮我们创建好索引库。
3.1.2、添加文档
创建好了索引库,就可以添加文档了。
创建一个BookService类,通过addBook()方法来批量添加文档数据:
@Service
public class BookService {
@Autowired
BookRepository bookRepository;
public void addBook(List<Book> books) {
elasticsearchRestTemplate.save(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<Book> 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<Book> books) {
elasticsearchRestTemplate.save(books);
}
}
除了save()方法,我们还可以使用bulkIndex()方法,它是一次批量添加数据的,而save()是逐个添加。来看如何使用bulkIndex()方法:
@Service
public class BookService {
@Autowired
ElasticsearchRestTemplate elasticsearchRestTemplate;
public void bulkAddBook(List<Book> books) {
List<IndexQuery> indexQueryList = new ArrayList<>();
books.forEach(book -> {
IndexQuery indexQuery = new IndexQuery();
indexQuery.setObject(book);
indexQueryList.add(indexQuery);
});
elasticsearchRestTemplate.bulkIndex(indexQueryList, IndexCoordinates.of("book"));
}
}
主要是通过IndexQuery的setObject()方法设置要添加的数据,bulkIndex()方法第二个参数用来指定索引信息。
如果需要的话可以使用IndexQuery的setId()方法来设置文档 id。
我的采集到的数据是以 JSON 格式保存在文件中,能否直接添加 JSON 数据呢,而不是转换成实体类的对象再添加。这个需求我们同样可以用bulkIndex()来完成:
@Service
public class BookService {
@Autowired
ElasticsearchRestTemplate elasticsearchRestTemplate;
public void bulkAddBook2(List<String> books) {
List<IndexQuery> indexQueryList = new ArrayList<>();
books.forEach(book -> {
IndexQuery indexQuery = new IndexQuery();
indexQuery.setSource(book);
indexQueryList.add(indexQuery);
});
elasticsearchRestTemplate.bulkIndex(indexQueryList, IndexCoordinates.of("book"));
}
}
其实就是通过IndexQuery的setSource()方法设置要添加的原始 JSON 数据。
这样从文件读取的数据就可以直接添加了:
public void writeBookDataToES2() {
......
try {
fileReader = new FileReader(filePath);
bufferedReader = new BufferedReader(fileReader);
String line;
ArrayList<String> 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 {
......
}
}
注意,使用ElasticsearchRestTemplate的save()或者bulkIndex()方法添加文档时,如果添加了相同文档 id 的数据,则之前的会被覆盖掉。如果是使用ElasticsearchRepository的save()方法,则无法正常添加。
本文详细的代码可以参考:https://github.com/SheHuan/LearnElasticsearch