上一篇文章讲解了如何使用docker安装ElasticSearch:查看
现在开始介绍SpringBoot集成ElasticSearch
SpringBoot 整合 ElasticSearch 有两种方案,
- ElasticSearch 官方提供的是 Elasticsearch Java API Client
- Spring 提供的 Spring Data Elasticsearch
在这里我介绍Spring Data ElasticSearch的使用
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
添加配置
在application.yml添加ElasticSearch配置
spring:
elasticsearch:
rest:
uris: http://localhost:9200
创建实体类用于映射索引
这里我以文章为例,searchText字段被用于全文搜索
@Document(indexName = "post")
@Data
public class PostDoc {
@Id
private String id; // 主键
@Field(type = FieldType.Keyword, index = false, copyTo = "searchText")
private String title; // 标题
@Field(type = FieldType.Keyword, index = false, copyTo = "searchText")
private String content; // 内容
@Field(type = FieldType.Keyword, index = false, copyTo = "searchText")
private String tags; // 标签
@Field(type = FieldType.Keyword, index = false, copyTo = "searchText")
private String keywords; // 关键词
@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)
private LocalDateTime updateTime; // 更新时间
@Field(type = FieldType.Keyword, index = false)
private String year; // 年份
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String searchText; // 搜索项
}
@Field指定普通属性
type 对应Elasticsearch中属性类型,使用FiledType枚举快速获取。
text 类型能被分词
keywords 不能被分词
index 是否创建索引,作为搜索条件时index必须为true
analyzer 指定分词器类型
copyTo 指定数据被复制到的字段,我们如果需要全文搜索,可以把需要搜索的字段,统一放到一个字段里,性能会比你从多个字段搜快一些
创建持久层
public interface PostDocRepository extends ElasticsearchRepository<PostDoc, String> {
}
创建Service
这个Service里只有查询的方法,增删改可以直接调用PostDocRepository,PostDocRepository也可以通过方法命名或者注解实现查询,这里我选择用NativeQuery构建查询,官方文档:查看
@Service
public class PostDocService {
@Resource
private ElasticsearchTemplate elasticsearchTemplate;
public List<PostDoc> searchArticles(String keyword, String year, int pageNum, int pageSize) {
NativeQuery query = NativeQuery.builder()
.withQuery(q -> q
.bool(bool -> bool
.must(m -> m
.match(m1 -> m1
.field("searchText")
.query(keyword)
)
)
.filter(f -> f
.term(term -> term
.field("year")
.value(year)
)
)
)
)
.withPageable(PageRequest.of(pageNum - 1, pageSize)) // 分页
.withFields("id", "title", "content", "tags", "keywords", "updateTime", "year", "sourceAlias") // 字段筛选
.build();
return elasticsearchTemplate.search(query, PostDoc.class).stream()
.map(hit -> hit.getContent())
.toList();
}
}
测试类
@SpringBootTest
@Slf4j
public class ElasticSearchTests {
@Resource
private PostDocRepository postDocRepository;
@Resource
private PostDocService postDocService;
private static final Random random = new Random();
// 生成一百万个文章
@Test
void generateAndSavePosts() {
List<PostDoc> postDocs = new ArrayList<>();
int totalRecords = 9000;
for (int i = 0; i < totalRecords; i++) {
PostDoc postDoc = new PostDoc();
postDoc.setId(UUID.randomUUID().toString()); // 随机ID
// 生成中文标题,30个字符以内
postDoc.setTitle(generateChineseText(30)); // 使用自定义的生成中文字符的方法
// 生成中文内容,3000个字符
postDoc.setContent(generateChineseText(3000)); // 生成3000个随机中文字符
// 标签,随机生成几个中文词语
postDoc.setTags(generateChineseText(5)); // 生成5个字符的中文标签
// 关键词,随机生成几个中文词语
postDoc.setKeywords(generateChineseText(5)); // 生成5个字符的中文关键词
// 更新时间,使用当前时间
postDoc.setUpdateTime(LocalDateTime.now());
// 年份,随机选择一个年份
postDoc.setYear(String.valueOf(2010 + random.nextInt(15)));
// searchText 为合并的文本内容(例如,标题 + 内容 + 标签 + 关键词)
String searchText = postDoc.getTitle() + " " + postDoc.getContent() + " " + postDoc.getTags() + " " + postDoc.getKeywords();
postDoc.setSearchText(searchText);
postDocs.add(postDoc);
// 每1000条进行一次批量保存
if (postDocs.size() == 1000) {
savePostsBatch(postDocs);
postDocs.clear(); // 清空待保存的列表
}
}
// 如果剩余不足10000条,最后进行保存
if (!postDocs.isEmpty()) {
savePostsBatch(postDocs);
}
}
// 自定义生成中文字符的方法
private String generateChineseText(int length) {
// 中文字符集
String chineseCharacters = "的一是了不在人有我他我们在这说去得到和为与可不有就都而在到和还想对好作这样子能天时其很本去分会看地从出学现就更无些已定家使法多如国道间声先己年时都工行可利等想日实样情见明正被下新心应点已成明定行法只少种方身问关与想方年工意明水家己放提连使回照面明解合法明且并则水电声同分成图形利商用根整语音价线力目备山现试别民教务老历界技专信高在个些共去复设设子响建按主有期备共界线近单八主济标影规称细装接位般并式具里情准声目空我文流究加精确化年些市加再以与定究民存选成究界半位百常到起发走从业求主从等设种写证才建月率从次育按带到去表变能权标接台价月空际界总众面些八影体流明内还度动群法多也";
Random rand = new Random();
StringBuilder result = new StringBuilder();
for (int i = 0; i < length; i++) {
int index = rand.nextInt(chineseCharacters.length());
result.append(chineseCharacters.charAt(index));
}
return result.toString();
}
// 批量插入文章
private void savePostsBatch(List<PostDoc> postDocs) {
try {
postDocRepository.saveAll(postDocs); // 使用 saveAll 批量保存
System.out.println("成功保存 " + postDocs.size() + " 条数据");
} catch (Exception e) {
e.printStackTrace();
System.err.println("批量保存数据失败");
}
}
// 测试搜索文章
@Test
void testSearchPosts() {
// 记录查询开始时间
Instant start = Instant.now();
// 执行搜索
List<PostDoc> posts = postDocService.searchArticles("工位", "2018", 1, 30);
// 记录查询结束时间
Instant end = Instant.now();
// 计算查询耗时(毫秒)
long duration = java.time.Duration.between(start, end).toMillis();
// 输出查询时间和结果
log.info("查询耗时: " + duration + "毫秒");
log.info(posts.toString());
}
}
这里我仅有插入和搜索的测试,对于文档的更新和删除直接调用PostDocRepository.update()和PostDocRepository.delete()即可,参数就是一个PostDoc实体对象,更新和删除时ID不能为空
全部添加完后,我的目录是这样的
之后我们就可以测试了
实际测试下来,利用测试类生成一个100万个3000字文章的索引,然后对一个字段进行模糊搜索,搜索耗时200ms,速度还是很快的