标签:ElasticSearch8.Kibana8;
一、简介
Elasticsearch 是一个分布式、RESTful 格调的搜寻和数据分析引擎,实用于各种数据类型,数字、文本、地理位置、结构化数据、非结构化数据;
在理论的工作中,历经过 Elasticsearch 从 6.0
到7.0
的版本升级,而这次 SpringBoot3 和 ES8.0 的集成,尽管脚本的语法变动很小,然而 Java 客户端的 API 语法变化很大;
二、环境搭建
1、下载安装包
须要留神的是,这些安装包的版本要抉择对应的,不然容易出问题;
软件包:elasticsearch-8.8.2-darwin-x86_64.tar.gz
分词器工具:elasticsearch-analysis-ik-8.8.2.zip
可视化工具:kibana-8.8.2-darwin-x86_64.tar.gz
2、服务启动
不论是 ES 还是 Kibana,在首次启动后,会初始化很多配置文件,能够依据本人的须要做相干的配置调整,比方常见的端口调整,资源占用,平安校验等;
1、启动 ES
elasticsearch-8.8.2/bin/elasticsearch
本地拜访:localhost:9200
2、启动 Kibana
kibana-8.8.2/bin/kibana
本地拜访:http://localhost:5601
# 3、查看装置的插件
http://localhost:9200/_cat/plugins -> analysis-ik 8.8.2
三、工程搭建
1、工程构造
2、依赖治理
在 starter-elasticsearch
组件中,实际上依赖的是 elasticsearch-java
组件的 8.7.1
版本;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>${spring-boot.version}</version>
</dependency>
3、配置文件
在下面环境搭建的过程中,曾经禁用了用户和明码的登录验证,配置 ES 服务地址即可;
spring:
# ElasticSearch 配置
elasticsearch:
uris: localhost:9200
四、根底用法
1、实体类
通过 Document
和Field
注解形容 ES 索引构造的实体类,留神这里 JsonIgnoreProperties
注解,解决索引中字段和实体类非一一对应的而引起的 JSON 解析问题;
@JsonIgnoreProperties(ignoreUnknown = true)
@Document(indexName = "contents_index", createIndex = false)
public class ContentsIndex implements Serializable {
private static final long serialVersionUID=1L;
@Field(type= FieldType.Integer)
private Integer id;
@Field(type= FieldType.Keyword)
private String title;
@Field(type= FieldType.Keyword)
private String intro;
@Field(type= FieldType.Text)
private String content;
@Field(type= FieldType.Integer)
private Integer createId;
@Field(type= FieldType.Keyword)
private String createName;
@Field(type= FieldType.Date,format = DateFormat.date_hour_minute_second)
private Date createTime;
}
2、初始化索引
基于 ElasticsearchTemplate
类和上述实体类,实现索引构造的初始化,并且将 tb_contents
表中的数据同步到索引中,最初通过 ID 查问一条测试数据;
@Service
public class ContentsIndexService {private static final Logger log = LoggerFactory.getLogger(ContentsIndexService.class);
@Resource
private ContentsService contentsService ;
@Resource
private ElasticsearchTemplate template ;
/**
* 初始化索引构造和数据
*/
public void initIndex (){
// 解决索引构造
IndexOperations indexOps = template.indexOps(ContentsIndex.class);
if (indexOps.exists()){boolean delFlag = indexOps.delete();
log.info("contents_index exists,delete:{}",delFlag);
indexOps.createMapping(ContentsIndex.class);
} else {log.info("contents_index not exists");
indexOps.createMapping(ContentsIndex.class);
}
// 同步数据库表记录
List<Contents> contentsList = contentsService.queryAll();
if (contentsList.size() > 0){List<ContentsIndex> contentsIndexList = new ArrayList<>() ;
contentsList.forEach(contents -> {ContentsIndex contentsIndex = new ContentsIndex() ;
BeanUtils.copyProperties(contents,contentsIndex);
contentsIndexList.add(contentsIndex);
});
template.save(contentsIndexList);
}
// ID 查问
ContentsIndex contentsIndex = template.get("10",ContentsIndex.class);
log.info("contents-index-10:{}",contentsIndex);
}
}
3、仓储接口
继承 ElasticsearchRepository
接口,能够对 ES 这种特定类型的存储库进行通用增删改查操作;在测试类中对该接口的办法进行测试;
// 1、接口定义
public interface ContentsIndexRepository extends ElasticsearchRepository<ContentsIndex,Long> {
}
// 2、接口测试
public class ContentsIndexRepositoryTest {
@Autowired
private ContentsIndexRepository contentsIndexRepository;
@Test
public void testAdd (){
// 单个新增
contentsIndexRepository.save(buildOne());
// 批量新增
contentsIndexRepository.saveAll(buildList()) ;
}
@Test
public void testUpdate (){
// 依据 ID 查问后再更新
Optional<ContentsIndex> contentsOpt = contentsIndexRepository.findById(14L);
if (contentsOpt.isPresent()){ContentsIndex contentsId = contentsOpt.get();
System.out.println("id=14:"+contentsId);
contentsId.setContent("update-content");
contentsId.setCreateTime(new Date());
contentsIndexRepository.save(contentsId);
}
}
@Test
public void testQuery (){
// 单个 ID 查问
Optional<ContentsIndex> contentsOpt = contentsIndexRepository.findById(1L);
if (contentsOpt.isPresent()){ContentsIndex contentsId1 = contentsOpt.get();
System.out.println("id=1:"+contentsId1);
}
// 批量 ID 查问
Iterator<ContentsIndex> contentsIterator = contentsIndexRepository
.findAllById(Arrays.asList(10L,12L)).iterator();
while (contentsIterator.hasNext()){ContentsIndex contentsIndex = contentsIterator.next();
System.out.println("id="+contentsIndex.getId()+":"+contentsIndex);
}
}
@Test
public void testDelete (){contentsIndexRepository.deleteById(15L);
contentsIndexRepository.deleteById(16L);
}
}
4、查问语法
无论是 ElasticsearchTemplate
类还是 ElasticsearchRepository
接口,都是对 ES 罕用的简略性能进行封装,在理论应用时,简单的查问语法还是依赖 ElasticsearchClient
和原生的 API 封装;
这里次要演示七个查询方法,次要波及:ID 查问,字段匹配,组合与范畴查问,分页与排序,分组统计,最大值查问和含糊匹配;更多的查问 API 还是要多看文档中的案例才行;
public class ElasticsearchClientTest {
@Autowired
private ElasticsearchClient client ;
@Test
public void testSearch1 () throws IOException {
// ID 查问
GetResponse<ContentsIndex> resp = client.get(getReq ->getReq.index("contents_index").id("7"), ContentsIndex.class);
if (resp.found()){ContentsIndex contentsIndex = resp.source() ;
System.out.println("contentsIndex-7:"+contentsIndex);
}
}
@Test
public void testSearch2 () throws IOException {
// 指定字段匹配
SearchResponse<ContentsIndex> resp = client.search(searchReq -> searchReq.index("contents_index")
.query(query -> query.match(field -> field
.field("createName").query("张三"))),ContentsIndex.class);
printResp(resp);
}
@Test
public void testSearch3 () throws IOException {
// 组合查问:姓名和工夫范畴
Query byName = MatchQuery.of(field -> field.field("createName").query("王五"))._toQuery();
Query byTime = RangeQuery.of(field -> field.field("createTime")
.gte(JsonData.of("2023-07-10T00:00:00"))
.lte(JsonData.of("2023-07-12T00:00:00")))._toQuery();
SearchResponse<ContentsIndex> resp = client.search(searchReq -> searchReq.index("contents_index")
.query(query -> query.bool(boolQuery -> boolQuery.must(byName).must(byTime))),ContentsIndex.class);
printResp(resp);
}
@Test
public void testSearch4 () throws IOException {
// 排序和分页,在 14 条数据中,依据 ID 倒序排列,从第 5 条往后取 4 条数据
SearchResponse<ContentsIndex> resp = client.search(searchReq -> searchReq.index("contents_index")
.from(5).size(4)
.sort(sort -> sort.field(sortField -> sortField.field("id").order(SortOrder.Desc))),ContentsIndex.class);
printResp(resp);
}
@Test
public void testSearch5 () throws IOException {
// 依据 createId 分组统计
SearchResponse<ContentsIndex> resp = client.search(searchReq -> searchReq.index("contents_index")
.aggregations("createIdGroup",agg -> agg.terms(term -> term.field("createId"))),ContentsIndex.class);
Aggregate aggregate = resp.aggregations().get("createIdGroup");
LongTermsAggregate termsAggregate = aggregate.lterms();
Buckets<LongTermsBucket> buckets = termsAggregate.buckets();
for (LongTermsBucket termsBucket : buckets.array()) {System.out.println(termsBucket.key() + ":" + termsBucket.docCount());
}
}
@Test
public void testSearch6 () throws IOException {
// 查问最大的 ID
SearchResponse<ContentsIndex> resp = client.search(searchReq -> searchReq.index("contents_index")
.aggregations("maxId",agg -> agg.max(field -> field.field("id"))),ContentsIndex.class);
for (Map.Entry<String, Aggregate> entry : resp.aggregations().entrySet()){System.out.println(entry.getKey()+":"+entry.getValue().max().value());
}
}
@Test
public void testSearch7 () throws IOException {
// 含糊查问 title 字段,容许 1 个误差
Query byContent = FuzzyQuery.of(field -> field.field("title").value("设计").fuzziness("1"))._toQuery();
SearchResponse<ContentsIndex> resp = client.search(searchReq -> searchReq.index("contents_index").query(byContent),ContentsIndex.class);
printResp(resp);
}
private void printResp (SearchResponse<ContentsIndex> resp){TotalHits total = resp.hits().total();
System.out.println("total:"+total);
List<Hit<ContentsIndex>> hits = resp.hits().hits();
for (Hit<ContentsIndex> hit: hits) {ContentsIndex contentsIndex = hit.source();
System.out.println(hit.id()+":"+contentsIndex);
}
}
}
五、参考源码
文档仓库:https://gitee.com/cicadasmile/butte-java-note
源码仓库:https://gitee.com/cicadasmile/butte-spring-parent