SpringBoot 集成 Elasticsearch
前面在 ubuntu 完成安装 elasticsearch,现在我们springboot将集成elasticsearch。
1、创建springboot项目
我们这边直接引入nosql中spring data elasticsearch启动器。
创建项目完成后。
项目结构:
pom文件:(新增 lombok 简化pojo)
<?xml version="1.0" encoding="utf-8"?> <project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelversion>4.0.0</modelversion> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version>2.1.6.release</version> <relativepath/> <!-- lookup parent from repository --> </parent> <groupid>com.yatces.elasticsearch</groupid> <artifactid>elasticsearch-demo</artifactid> <version>0.0.1-snapshot</version> <name>elasticsearch-demo</name> <description>demo project for spring boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-elasticsearch</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> <version>1.16.20</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-maven-plugin</artifactid> </plugin> </plugins> </build> </project>
2、添加 elasticsearch 配置
本人习惯 yml 文件,将 application.properties 重命名为 application.yml
spring: data: elasticsearch: cluster-name: elasticsearch cluster-nodes: 192.168.78.130:9300
3、新增实体类
@data @noargsconstructor @allargsconstructor @document(indexname = "product",type = "item",shards = 1,replicas = 0) public class item { @id long id; @field(type = fieldtype.text,analyzer = "ik_max_word") string title; //标题 @field(type = fieldtype.keyword) string category;// 分类 @field(type = fieldtype.keyword) string brand; // 品牌 @field(type = fieldtype.double) double price; // 价格 @field(index = false, type = fieldtype.keyword) string images; // 图片地址 }
主要注解:
@document 作用在类,标记实体类为文档对象,一般有四个属性
indexname:对应索引库名称
type:对应在索引库中的类型
shards:分片数量,默认5
replicas:副本数量,默认1
@id 作用在成员变量,标记一个字段作为id主键
@field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
type:字段类型,取值是枚举:fieldtype
index:是否索引,布尔类型,默认是true
store:是否存储,布尔类型,默认是false
analyzer:分词器名称:ik_max_word
4、编写测试
4.1新建itemtest
用于测试 elasticsearch 的使用,使用 elasticsearchtemplate 操作索引。
@runwith(springrunner.class) @springboottest(classes = elasticsearchdemoapplication.class) public class itemtest { @autowired private elasticsearchtemplate elasticsearchtemplate; }
4.2创建索引和映射
@test public void testcreate(){ // 创建索引,会根据item类的@document注解信息来创建 elasticsearchtemplate.createindex(item.class); // 配置映射,会根据item类中的id、field等字段来自动完成映射 elasticsearchtemplate.putmapping(item.class); }
在 kibana通过 get product/_mapping 查询结果
{ "product": { "mappings": { "item": { "properties": { "brand": { "type": "keyword" }, "category": { "type": "keyword" }, "images": { "type": "keyword", "index": false }, "price": { "type": "double" }, "title": { "type": "text", "analyzer": "ik_max_word" } } } } } }
4.3删除索引
@test
public void testdelete(){
//elasticsearchtemplate.deleteindex(item.class);
// indexname = "product"
elasticsearchtemplate.deleteindex("product");
}
kibana 再次查询,报404。
{ "error": { "root_cause": [ { "type": "index_not_found_exception", "reason": "no such index", "resource.type": "index_or_alias", "resource.id": "product", "index_uuid": "_na_", "index": "product" } ], "type": "index_not_found_exception", "reason": "no such index", "resource.type": "index_or_alias", "resource.id": "product", "index_uuid": "_na_", "index": "product" }, "status": 404 }
4.4新建 itemrepository
用于对 document 的操作测试
public interface itemrepository extends elasticsearchrepository<item, long>{ }
在 itemtest 中注入 itemrepository
@autowired private itemrepository itemrepository;
4.5新增文档
修改和新增是同一个接口,区分的依据就是id,新增用post 请求,修改用put请求。
@test public void testsavedocument(){ item item = new item(1l, "小米手机7", " 手机", "小米", 3499.00, "13123.jpg"); itemrepository.save(item); }
kibana 通过get product/_search 查询
{ "took": 1, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": 1, "max_score": 1, "hits": [ { "_index": "product", "_type": "item", "_id": "1", "_score": 1, "_source": { "id": 1, "title": "小米手机7", "category": " 手机", "brand": "小米", "price": 3499, "images": "13123.jpg" } } ] } }
4.6批量新增
@test public void testsavedocumentlist() { list<item> list = new arraylist<>(); list.add(new item(1l, "小米手机7", "手机", "小米", 3299.00, "13123.jpg")); list.add(new item(2l, "坚果手机r1", "手机", "锤子", 3699.00, "13123.jpg")); list.add(new item(3l, "华为meta10", "手机", "华为", 4499.00, "13123.jpg")); list.add(new item(4l, "小米mix2s", "手机", "小米", 4299.00, "13123.jpg")); list.add(new item(5l, "荣耀v10", "手机", "华为", 2799.00, "13123.jpg")); // 接收对象集合,实现批量新增 itemrepository.saveall(list); }
kibana 通过get product/_search 再次查询,得到5个doc
4.7基本查询
在 elasticsearchrepository 继承下来的查询方法
4.7.1根据id查询
@test public void testfindbyid(){ optional<item> optional = itemrepository.findbyid(1l); system.out.println(optional.get()); }
结果
4.7.2查询所有
@test public void testfindall(){ // 查询所有,并根据 price 降序排序 iterable<item> items = itemrepository.findall(sort.by(sort.direction.desc,"price")); items.foreach(system.out::println); }
结果
4.8自定义方法
spring data 的提供一个强大功能,是根据方法名称自动实现功能,下述自定义规范:
在itemrepository定义一个方法findbypricebetween,不用写这个方法的实现例如:根据价格区间查询所有 item
/**
* 根据价格区间查询
* @param price1
* @param price2
* @return
*/
list<item> findbypricebetween(double price1, double price2);
在 itemtest 编写测试
@test public void testfindbypricebetween(){ list<item> list = this.itemrepository.findbypricebetween(4000.00, 5000.00); list.foreach(system.out::println); }
结果
4.9高级查询
4.9.1基本查询
repository 的 search 方法,使用 querybuilders 构建查询条件
querybuilders 提供了大量的静态方法,用于生成各种不同类型的查询对象,例如:词条、模糊、通配符等querybuilder对象
public void testquery(){ // 词条查询 matchquerybuilder querybuilder = querybuilders.matchquery("title", "小米"); // 执行查询 iterable<item> items = this.itemrepository.search(querybuilder); items.foreach(system.out::println); }
结果
4.9.2自定义查询
@test public void testnativequery(){ // 构建查询条件 nativesearchquerybuilder querybuilder = new nativesearchquerybuilder(); // 添加基本的分词查询 querybuilder.withquery(querybuilders.matchquery("title", "小米")); // 执行搜索,获取结果 page<item> items = this.itemrepository.search(querybuilder.build()); // 打印总条数 system.out.println(items.gettotalelements()); // 打印总页数 system.out.println(items.gettotalpages()); items.foreach(system.out::println); }
结果
nativesearchquerybuilder:spring提供的一个查询条件构建器,帮助构建json格式的请求体。
page<item>:默认是分页查询,因此返回的是一个分页的结果对象,包含属性:
totalelements:总条数
totalpages:总页数
iterator:迭代器,本身实现了iterator接口,因此可直接迭代得到当前页的数据
4.9.3分页查询
利用nativesearchquerybuilder可以方便的实现分页
@test public void testnativepagequery(){ // 构建查询条件 nativesearchquerybuilder querybuilder = new nativesearchquerybuilder(); // 添加基本的分词查询 querybuilder.withquery(querybuilders.termquery("category", "手机")); // 初始化分页参数 int page = 0; int size = 3; // 设置分页参数 querybuilder.withpageable(pagerequest.of(page, size)); // 执行搜索,获取结果 page<item> items = this.itemrepository.search(querybuilder.build()); // 打印总条数 system.out.println("总条数:"+items.gettotalelements()); // 打印总页数 system.out.println("总页数:"+items.gettotalpages()); // 每页大小 system.out.println("每页大小:"+items.getsize()); // 当前页 system.out.println("当前页:"+items.getnumber()); items.foreach(system.out::println); }
结果:分页是从第0页开始
4.9.4排序
排序也通用通过nativesearchquerybuilder完成
@test public void testsortquery(){ // 构建查询条件 nativesearchquerybuilder querybuilder = new nativesearchquerybuilder(); // 添加基本的分词查询 querybuilder.withquery(querybuilders.termquery("category", "手机")); // 排序 querybuilder.withsort(sortbuilders.fieldsort("price").order(sortorder.desc)); // 执行搜索,获取结果 page<item> items = this.itemrepository.search(querybuilder.build()); // 打印总条数 system.out.println("总条数:"+items.gettotalelements()); items.foreach(system.out::println); }
结果
4.10聚合
4.10.1普通聚合
按照品牌brand进行分组
@test public void testbrandagg(){ nativesearchquerybuilder querybuilder = new nativesearchquerybuilder(); // 不查询任何结果 querybuilder.withsourcefilter(new fetchsourcefilter(new string[]{""}, null)); // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand querybuilder.addaggregation( aggregationbuilders.terms("brands").field("brand")); // 2、查询,需要把结果强转为aggregatedpage类型 aggregatedpage<item> aggpage = (aggregatedpage<item>) this.itemrepository.search(querybuilder.build()); // 3、解析 // 3.1、从结果中取出名为brands的那个聚合, // 因为是利用string类型字段来进行的term聚合,所以结果要强转为stringterm类型 stringterms agg = (stringterms) aggpage.getaggregation("brands"); // 3.2、获取桶 list<stringterms.bucket> buckets = agg.getbuckets(); // 3.3、遍历 for (stringterms.bucket bucket : buckets) { // 3.4、获取桶中的key,即品牌名称 和 文档数量 system.out.println(bucket.getkeyasstring() + ":" +bucket.getdoccount()); } }
结果
aggregationbuilders.terms("brands").field("brand") 聚合的构建工厂类aggregationbuilders,所有聚合都由这个类来构建
aggpage.getaggregation("brands")返回的结果都是aggregation类型对象,不过根据字段类型不同,又有不同的子类表示
4.10.2嵌套聚合
@test public void testsubavgagg(){ nativesearchquerybuilder querybuilder = new nativesearchquerybuilder(); // 不查询任何结果 querybuilder.withsourcefilter(new fetchsourcefilter(new string[]{""}, null)); // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand querybuilder.addaggregation( aggregationbuilders.terms("brands").field("brand") .subaggregation(aggregationbuilders.avg("avgprice").field("price")) // 在品牌聚合桶内进行嵌套聚合,求平均值 ); // 2、查询,需要把结果强转为aggregatedpage类型 aggregatedpage<item> aggpage = (aggregatedpage<item>) this.itemrepository.search(querybuilder.build()); // 3、解析 // 3.1、从结果中取出名为brands的那个聚合, // 因为是利用string类型字段来进行的term聚合,所以结果要强转为stringterm类型 stringterms agg = (stringterms) aggpage.getaggregation("brands"); // 3.2、获取桶 list<stringterms.bucket> buckets = agg.getbuckets(); // 3.3、遍历 buckets.foreach(bucket -> { // 3.4、获取桶中的key,即品牌名称 ; 获取桶中的文档数量 ;获取平均值结果: internalavg avg = (internalavg) bucket.getaggregations().asmap().get("avgprice"); system.out.println(bucket.getkeyasstring() + "共" + bucket.getdoccount() +",平均售价:"+ avg.getvalue() ); }); }
结果
上一篇: 洛谷 P2085 最小函数值
下一篇: 怎么用C语言实现一个通讯录?