springboot系列十二 Spring-Data-ElasticSearch
文件
-
https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/
-
https://www.elastic.co/guide/cn/elasticsearch/guide/current/index-doc.html
ElasticSearch安裝
SpringDataElasticsearch和ElasticSearch版本相容
參考https://github.com/spring-projects/spring-data-elasticsearch
spring data elasticsearch | elasticsearch |
---|---|
3.1.x | 6.2.2 |
3.0.x | 5.5.0 |
2.1.x | 2.4.0 |
2.0.x | 2.2.0 |
1.3.x | 1.5.2 |
如果版本不相容,會拋異常
org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}{ZAJHQCraS-6cuRir7xf-eg}{localhost}{192.168.1.123:9300}]
這裡使用SpringDataElasticsearch版本為3.1.2
Elasticsearch版本為6.5.0
基本CURD
依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置
spring: data: elasticsearch: cluster-nodes: localhost:9300 # 節點名稱,預設為elasticsearch,如果docker安裝的,這裡是docker-cluster # http://localhost:9200/_cluster/state 檢視節點名稱 cluster-name: docker-cluster
定義一個實體類
@Data
@Document(indexName = "user", type = "test")
public class User {
@Id
private String id;
private String name;
private int age = 18;
private Date createTime = new Date();
}
寫一個jpa的dao類
public interface UserRepository extends ElasticsearchRepository<User, String> {
User findByName(String name);
}
測試介面
@RestController
@RequestMapping("/user")
public class UserResource {
@Autowired private UserRepository userRepository;
@PostMapping("")
public User save1(@RequestBody User user){
return userRepository.save(user);
}
@GetMapping("")
public Iterable<User> findAll1(){
return userRepository.findAll();
}
@GetMapping("/{name}")
public User findOne1(@PathVariable String name){
return userRepository.findByName(name);
}
}
測試:新增一條資料 POST http://localhost:8080/user
檢視es資料:
QueryBuilder條件查詢
新增測試資料
新建一個實體Article
@Data
@Document(indexName = "article", type = "test")
public class Article {
@Id
private String id;
private String author;
private String title;
private String content;
private Date time;
}
再新增一個dao類
public interface ArticleRepository extends ElasticsearchRepository<Article, String> {
}
寫個測試介面,新增幾條資料
@Autowired private ArticleRepository articleRepository;
@PostMapping("")
public Article save(@RequestBody Article article){
return articleRepository.save(article);
}
分頁查詢
使用Pageable來處理分頁請求引數
- page: 從第幾頁開始
- size: 每頁條數
- sort: 排序欄位,可寫多個欄位
- direction: 升序或降序 asc|desc
/**分頁查詢*/
@GetMapping("/page")
public Page<Article> range(String query,
@PageableDefault(page = 0, size = 5, sort = "time", direction = Sort.Direction.DESC) Pageable pageable){
BoolQueryBuilder qb = QueryBuilders.boolQuery();
if(query != null) {
qb.must(QueryBuilders.matchQuery("title", query));
}
return articleRepository.search(qb, pageable);
}
測試: GET http://localhost:8080/article/page?query=了&page=0&size=2
{
"content": [
{
"id": "Sw6Gh2cBBlxbCrguspsL",
"author": "test",
"title": "java版本到多少了",
"content": "可能是12了",
"time": "2018-05-19T17:02:02.000+0000"
},
{
"id": "Sg6Gh2cBBlxbCrguJJsx",
"author": "王五",
"title": "奇怪了",
"content": "獨到的方式哈哈哈哈",
"time": "2018-03-19T17:02:02.000+0000"
}
],
"pageable": {
"sort": {
"sorted": true,
"unsorted": false,
"empty": false
},
"offset": 0,
"pageSize": 2,
"pageNumber": 0,
"unpaged": false,
"paged": true
},
"facets": [],
"aggregations": null,
"scrollId": null,
"maxScore": "NaN",
"totalPages": 2,
"totalElements": 3,
"size": 2,
"number": 0,
"first": true,
"numberOfElements": 2,
"sort": {
"sorted": true,
"unsorted": false,
"empty": false
},
"last": false,
"empty": false
}
精確匹配term
精確匹配,查詢中文時,需要安裝分詞外掛,查詢英文沒問題
/**精確匹配*/
@GetMapping("/term")
public Page<Article> term(String query){
BoolQueryBuilder qb = QueryBuilders.boolQuery();
qb.must(QueryBuilders.termQuery("author", query));
return (Page<Article>)articleRepository.search(qb);
}
測試:GET http://localhost:8080/article/term?query=test
{
"content": [
{
"id": "Sw6Gh2cBBlxbCrguspsL",
"author": "test",
"title": "java版本到多少了",
"content": "可能是12了",
"time": "2018-05-19T17:02:02.000+0000"
}
],
# 其他省略
}
模糊匹配match
/**模糊匹配*/
@GetMapping("/match")
public Page<Article> match(String query){
BoolQueryBuilder qb = QueryBuilders.boolQuery();
qb.must(QueryBuilders.matchQuery("content", query));
return (Page<Article>)articleRepository.search(qb);
}
/**短語模糊匹配*/
@GetMapping("/matchPhrase")
public Page<Article> matchPhraseQuery(String query){
BoolQueryBuilder qb = QueryBuilders.boolQuery();
qb.must(QueryBuilders.matchPhraseQuery("content", query));
return (Page<Article>)articleRepository.search(qb);
}
測試:GET http://localhost:8080/article/match?query=的
{
"content": [
{
"id": "Rw6Dh2cBBlxbCrguwJu4",
"author": "張三",
"title": "解放東路手機放",
"content": "的說法是實打實的",
"time": "2018-12-07T07:12:38.000+0000"
},
{
"id": "SQ6Fh2cBBlxbCrguV5vX",
"author": "李四",
"title": "詹姆斯來湖人了",
"content": "飛機歐時力的方式來顛覆了聖誕節是鄧麗君的時間309348噢03的類似放假了llldfjsljl",
"time": "2018-01-19T17:02:02.000+0000"
},
{
"id": "Sg6Gh2cBBlxbCrguJJsx",
"author": "王五",
"title": "奇怪了",
"content": "獨到的方式哈哈哈哈",
"time": "2018-03-19T17:02:02.000+0000"
}
],
# 其他省略
}
範圍查詢range
/**範圍查詢*/
@GetMapping("/range")
public Page<Article> range(long query){
BoolQueryBuilder qb = QueryBuilders.boolQuery();
qb.must(QueryBuilders.rangeQuery("time").gt(query));
//qb.must(QueryBuilders.rangeQuery("time").from(query).to(System.currentTimeMillis()));//大於query,小於當前時間
return (Page<Article>)articleRepository.search(qb);
}
測試:GET http://localhost:8080/article/range?query=1526749322000
{
"content": [
{
"id": "SA6Eh2cBBlxbCrguuJsK",
"author": "張三",
"title": "科比退役",
"content": "2018飛機上林德洛夫科比退役了",
"time": "2018-12-07T07:13:59.000+0000"
},
{
"id": "Rw6Dh2cBBlxbCrguwJu4",
"author": "張三",
"title": "解放東路手機放",
"content": "的說法是實打實的",
"time": "2018-12-07T07:12:38.000+0000"
}
],
# 其他省略
}
位置搜尋
Elasticsearch 提供了 兩種表示地理位置的方式:用緯度-經度表示的座標點使用 geo_point 欄位型別, 以 GeoJSON 格式定義的複雜地理形狀,使用 geo_shape 欄位型別。
這裡使用geo_point來舉例
初始化模型和資料
新建一個實體 Location
@Data
@Document(indexName = "location")
public class Location {
@Id
private String id;
@GeoPointField
private GeoPoint location;//位置座標 lon經度 lat緯度
private String address;//地址
}
新增一個dao類
public interface LocationRepository extends ElasticsearchRepository<Location, String> {
}
然後寫個測試介面,來新增幾條資料
@Autowired
private LocationRepository locationRepository;
@PostMapping("")
public Location save(@RequestBody Location location){
return locationRepository.save(location);
}
這裡使用百度地區的座標拾取器來取得位置座標
傳送門 百度位置座標拾取器
新增資料 POST http://localhost:8080/location
{
"location":{
"lon":120.137051,
"lat":30.265498
},
"address":"杭州西湖區政府"
}
重複新增,新增後檢視es資料:
計算2個座標的舉例
SpringDataElasticSearch提供了一個工具 GeoDistance
//參考 https://www.elastic.co/guide/cn/elasticsearch/guide/current/sorting-by-distance.html
//GeoDistance.PLANE 快速但精度略差 srcLat:源緯度 dstLat:目標緯度
GeoDistance.PLANE.calculate(double srcLat, double srcLon, double dstLat, double dstLon, DistanceUnit unit)
//GeoDistance.ARC 效率較差但精度高
GeoDistance.ARC.calculate(double srcLat, double srcLon, double dstLat, double dstLon, DistanceUnit unit)
根據座標位置查詢
在Location實體中新增一個欄位,用來介面返回“距離多少米”
private String distanceMeters;//距離多少米
測試介面
/**
* 搜尋附近
* @param lon 當前位置 經度
* @param lat 當前位置 緯度
* @param distance 搜尋多少範圍
* @param pageable 分頁引數
* @return
*/
@GetMapping("/searchNear")
public List<Location> searchNear(double lon, double lat, String distance, @PageableDefault Pageable pageable){
BoolQueryBuilder qb = QueryBuilders.boolQuery();
//搜尋欄位為 location
GeoDistanceQueryBuilder geoBuilder = new GeoDistanceQueryBuilder("location");
geoBuilder.point(lat, lon);//指定從哪個位置搜尋
geoBuilder.distance(distance, DistanceUnit.KILOMETERS);//指定搜尋多少km
qb.filter(geoBuilder);
//可新增其他查詢條件
//qb.must(QueryBuilders.matchQuery("address", address));
Page<Location> page = locationRepository.search(qb, pageable);
List<Location> list = page.getContent();
list.forEach(l -> {
double calculate = GeoDistance.ARC.calculate(l.getLocation().getLat(), l.getLocation().getLon(), lat, lon, DistanceUnit.METERS);
l.setDistanceMeters("距離" + (int)calculate + "m");
});
return list;
}
測試 GET http://localhost:8080/location/searchNear?lon=120.185919&lat=30.250649&distance=5
[
{
"id": "TQ6_h2cBBlxbCrguvps5",
"location": {
"lat": 30.251148,
"lon": 120.188578
},
"address": "杭州紅樓大酒店",
"distanceMeters": "距離261m"
},
{
"id": "Tw7Bh2cBBlxbCrguZ5ti",
"location": {
"lat": 30.265498,
"lon": 120.137051
},
"address": "杭州西湖區政府",
"distanceMeters": "距離4975m"
},
{
"id": "TA69h2cBBlxbCrguoZuZ",
"location": {
"lat": 30.249338,
"lon": 120.189279
},
"address": "杭州火車站",
"distanceMeters": "距離354m"
},
{
"id": "Tg7Ah2cBBlxbCrgu6ZtH",
"location": {
"lat": 30.256732,
"lon": 120.183853
},
"address": "浙大醫學院第二附屬醫院",
"distanceMeters": "距離704m"
}
]
這裡發現排序是亂的。下面來處理排序問題
根據座標位置查詢並排序
@GetMapping("/searchNearWithOrder")
public List<Location> searchNearWithOrder(double lon, double lat, String distance, @PageableDefault Pageable pageable){
//搜尋欄位為 location
GeoDistanceQueryBuilder geoBuilder = new GeoDistanceQueryBuilder("location");
geoBuilder.point(lat, lon);//指定從哪個位置搜尋
geoBuilder.distance(distance, DistanceUnit.KILOMETERS);//指定搜尋多少km
//距離排序
GeoDistanceSortBuilder sortBuilder = new GeoDistanceSortBuilder("location", lat, lon);
sortBuilder.order(SortOrder.ASC);//升序
sortBuilder.unit(DistanceUnit.METERS);
//構造查詢器
NativeSearchQueryBuilder qb = new NativeSearchQueryBuilder()
.withPageable(pageable)
.withFilter(geoBuilder)
.withSort(sortBuilder);
//可新增其他查詢條件
//qb.must(QueryBuilders.matchQuery("address", address));
Page<Location> page = locationRepository.search(qb.build());
List<Location> list = page.getContent();
list.forEach(l -> {
double calculate = GeoDistance.PLANE.calculate(l.getLocation().getLat(), l.getLocation().getLon(), lat, lon, DistanceUnit.METERS);
l.setDistanceMeters("距離" + (int)calculate + "m");
});
return list;
}
測試:GET http://localhost:8080/location/searchNearWithOrder?lon=120.185919&lat=30.250649&distance=5
[
{
"id": "TQ6_h2cBBlxbCrguvps5",
"location": {
"lat": 30.251148,
"lon": 120.188578
},
"address": "杭州紅樓大酒店",
"distanceMeters": "距離261m"
},
{
"id": "TA69h2cBBlxbCrguoZuZ",
"location": {
"lat": 30.249338,
"lon": 120.189279
},
"address": "杭州火車站",
"distanceMeters": "距離354m"
},
{
"id": "Tg7Ah2cBBlxbCrgu6ZtH",
"location": {
"lat": 30.256732,
"lon": 120.183853
},
"address": "浙大醫學院第二附屬醫院",
"distanceMeters": "距離704m"
},
{
"id": "Tw7Bh2cBBlxbCrguZ5ti",
"location": {
"lat": 30.265498,
"lon": 120.137051
},
"address": "杭州西湖區政府",
"distanceMeters": "距離4975m"
}
]