文档
-
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"
}
]
项目源码
https://gitee.com/yimingkeji/springboot/tree/master/elasticsearch