您的位置:首页 > 编程语言 > Java开发

springboot系列十二 Spring-Data-ElasticSearch Jpa、QueryBuilder、MatchQuery、位置搜索、GeoPoint

2018-12-07 21:16 731 查看

文档

ElasticSearch安装

docker 安装ElasticSearch(2.x版本)

docker 安装ElasticSearch(6.x版本)

SpringDataElasticsearch和ElasticSearch版本兼容

参考https://github.com/spring-projects/spring-data-elasticsearch

spring data elasticsearchelasticsearch
3.1.x6.2.2
3.0.x5.5.0
2.1.x2.4.0
2.0.x2.2.0
1.3.x1.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"
}
]

项目源码

https://gitee.com/yimingkeji/springboot/tree/master/elasticsearch

(adsbygoogle = window.adsbygoogle || []).push({});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息