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

原创
2018/12/07 21:16
阅读数 1.5W

文档

ElasticSearch安装

docker 安装ElasticSearch(2.x版本)

docker 安装ElasticSearch(6.x版本)

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"
    }
]

项目源码

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

展开阅读全文
打赏
2
15 收藏
分享
加载中
AT杨博主

引用来自“丶老李头”的评论

博主能讲解一波geo_shape操作吗,现在用到找不到教程
这种能满足你的需要吗?

//搜索字段为 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 page = locationRepository.search(qb.build());
2019/05/09 23:28
回复
举报
博主能讲解一波geo_shape操作吗,现在用到找不到教程
2019/04/23 13:06
回复
举报
更多评论
打赏
2 评论
15 收藏
2
分享
返回顶部
顶部