一、前言
在前一篇文章中,我们学习了ES中的一部分的搜索功能,作为一名Java工程师,更多时候我们是用代码去操作ES,同时对于Java而言时下最流行的就是Springboot了,所以这里我们将ES和Springboot整合将上一篇文章中的所有DSL操作,使用Java代码来操作一遍从而加深记忆。
二、创建一个Springboot项目
1、POM文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>springboot-es</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-es</name><description>springboot-es</description><properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.7.6</spring-boot.version></properties><dependencies><!--Spring Data ES--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><!--Spring Web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--Spring Test--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--Lombok:简化开发--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>17</source><target>17</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.example.SpringbootEsApplication</mainClass><skip>true</skip></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>
依赖解释:
(1)首先我们是用的是Springboot2.7.6的版本,JDK则使用的是17,当然用JDK8也是一样的。
(2)我们是用SpringData作为操作数据的框架,当然你也可以用原生的客户端。
(3)添加了Web依赖,我们直接使用接口的形式可以更方便的看出效果
(4)lombok:则是为了简化开发,可以简单的认为是用于生成get/set方法
2、配置文件
server:port: 8088
spring:elasticsearch:uris: http://localhost:9200
这里没啥好说的,就是定义了程序运行在8088端口,同时es的地址为localhost:9200。至此Springboot和ES的整合就完成了,是不是很简单。当然这背后可一点也不简单,之所以我们可以这么简单的配置就能做好整合,是因为Springboot为我们提供的强大的自动装配,这个不在本文的范畴,暂时放下不表。
3、编写代码
在上一篇文章中,我们已经定义了索引的结构
PUT /hotel
{"mappings": {"properties": {"title":{"type": "text" },"city":{"type": "keyword"},"price":{"type": "double"},"create_time":{"type": "date","format": "yyyy-MM-dd HH:mm:ss"},"amenities":{"type": "text"},"full_room":{"type": "boolean"},"location":{"type": "geo_point"},"praise":{"type": "integer"}}}
3.1、编写实体类
按照这个索引的结构,我们创建对应的实体类,这个和我们是用Mybatis时很像,也要定义对应的实体类。
package com.example.entity;import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;/*** @Author hardy(叶阳华)* @Description* @Date 2024/11/26 14:06*/
@Data
@Document(indexName = "hotel", createIndex = false)
public class Hotel {/*** ID*/@Idprivate String id;/*** 标题:text类型,使用ik_max_word作为分词器*/@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")private String title;/*** 所在城市:所在城市没必要分词*/@Field(type = FieldType.Keyword)private String city;/*** 价格*/@Field(type = FieldType.Double)private BigDecimal price;/*** 便利措施*/@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")private String amenities;/*** 创建时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@Field(value = "create_time", type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd HH:mm:ss")private Date createTime;/*** 是否满员*/@Field(value = "full_room", type = FieldType.Boolean)private Boolean fullRoom;/*** 位置*/@GeoPointFieldprivate GeoPoint location;@Field(type = FieldType.Integer)private Integer praise;
}
说一下笔者编写过程中碰到的小坑
(1)Date类型,如果要自定义日期的格式,需要将format设置为custom(不建议,即将被废弃),或者设置为{},否则就会提示日期转换异常
(2)地理位置,一开始笔者也以为可以直接使用@Field(type = FieldType.Point) 然后,并没有这个类型,官网上则说使用 @GeoPoint 然而还是没有,最终发现有一个@GeoPointField尝试了一下发现可以。
(3)@GeoPointField 并没有任何属性,如果我们的字段名和索引中的字段名不一致,则可以使用 @Field去标记,如
3.2、查询索引是否存在
@Resourceprivate ElasticsearchOperations elasticsearchOperations;/*** 校验索引是否存在*/@GetMapping("checkIndexExists")public Boolean checkIndexExists() {return elasticsearchOperations.indexOps(Hotel.class).exists();}
tips:ElasticsearchOperations是SpringData为我们提供的一个工具类,用于简化开发。
执行结果
3.3、批量写入数据
/*** 批量写入*/@PostMapping("/batchCreate")public Boolean batchCreate(@RequestBody List<Hotel> hotelList) {if (CollectionUtils.isEmpty(hotelList)) {throw new IllegalArgumentException("参数不能为空");}final Iterable<Hotel> hotels = elasticsearchOperations.save(hotelList);hotels.iterator().forEachRemaining(System.out::println);return true;}
请求参数:
[{"id": "001","title": "文雅酒店","city": "青岛","price": 556,"createTime": "2020-04-18 12:00:00","amenities": "浴池,普通停车场/充电停车场","fullRoom": false,"location": {"lat": 36.083078,"lon": 120.37566},"praise": 10},{"id": "002","title": "金都嘉怡假日酒店","city": "北京","price": 337,"createTime": "2021-03-15 20:00:00","amenities": "wifi,充电停车场/可升降停车场","fullRoom": false,"location": {"lat": 39.915153,"lon": 116.403},"praise": 60},{"id": "003","itle": "金都欣欣酒店","city": "天津","price": 200,"createTime": "2021-05-09 16:00:00","amenities": "提供假日party,免费早餐,可充电停车场","fullRoom": true,"location": {"lat": 39.186555,"lon": 117.162007},"praise": 30},{"id": "004","title": "金都酒店","city": "北京","price": 500,"createTime": "2021-02-18 08:00:00","amenities": "浴池(假日需预定),室内游泳池,普通停车场","fullRoom": true,"location": {"lat": 39.915343,"lon": 116.4239},"praise": 20},{"id": "005","title": "文雅精选酒店","city": "北京","price": 800,"createTime": "2021-01-01 08:00:00","amenities": "浴池(假日需预定),wifi,室内游泳池,普通停车场","fullRoom": true,"location": {"lat": 39.918229,"lon": 116.422011},"praise": 20}
]
结果:
3.4、查询所有数据
@GetMapping("/queryAll")
public List<Hotel> queryAll() {Criteria criteria = new Criteria();Query query = new CriteriaQuery(criteria);final SearchHits<Hotel> searchHits = elasticsearchOperations.search(query, Hotel.class);//没有命中任何文档if (!searchHits.hasSearchHits()) {return new ArrayList<>();}return searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
}
结果:
3.5、分页查询
@GetMapping("/pageQuery")public Page<Hotel> pageQuery(String city, int pageNo, int pageSize) {Criteria criteria = Criteria.where("city").is(city);PageRequest pageRequest = PageRequest.of(pageNo, pageSize);Query query = new CriteriaQuery(criteria);query.setPageable(pageRequest);final SearchHits<Hotel> searchHits = elasticsearchOperations.search(query, Hotel.class);if (!searchHits.hasSearchHits()) {return new PageImpl<>(new ArrayList<>());}List<Hotel> hotels = searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());//总记录数final long totalHits = searchHits.getTotalHits();// 返回分页结果return new PageImpl<>(hotels, pageRequest, totalHits);}
结果:
{"content": [{"id": "002","title": "金都嘉怡假日酒店","city": "北京","price": 337,"amenities": "wifi,充电停车场/可升降停车场","createTime": "2021-03-15 20:00:00","fullRoom": false,"location": {"lat": 39.915153,"lon": 116.403},"praise": 60},{"id": "004","title": "金都酒店","city": "北京","price": 500,"amenities": "浴池(假日需预定),室内游泳池,普通停车场","createTime": "2021-02-18 08:00:00","fullRoom": true,"location": {"lat": 39.915343,"lon": 116.4239},"praise": 20},{"id": "005","title": "文雅精选酒店","city": "北京","price": 800,"amenities": "浴池(假日需预定),wifi,室内游泳池,普通停车场","createTime": "2021-01-01 08:00:00","fullRoom": true,"location": {"lat": 39.918229,"lon": 116.422011},"praise": 20}],"pageable": {"sort": {"empty": true,"unsorted": true,"sorted": false},"offset": 0,"pageNumber": 0,"pageSize": 3,"paged": true,"unpaged": false},"last": true,"totalPages": 1,"totalElements": 3,"first": true,"size": 3,"number": 0,"sort": {"empty": true,"unsorted": true,"sorted": false},"numberOfElements": 3,"empty": false
}
和上面直接返回所有数据不同,分页查询还会返回分页相关信息
三、结束语
至此Springboot和ES已经整合,并且做了一些基础的查询操作,后续我们会继续审核ES的查询,更多的复杂的查询在后面的文章中,希望对你有所帮助。