https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/html
https://www.elastic.co/guide/cn/elasticsearch/guide/current/index-doc.htmljava
docker 安装ElasticSearch(2.x版本)node
docker 安装ElasticSearch(6.x版本) git
参考https://github.com/spring-projects/spring-data-elasticsearchgithub
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.2spring
Elasticsearch版本为6.5.0docker
依赖json
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
配置api
spring: data: elasticsearch: cluster-nodes: localhost:9300 # 节点名称,默认为elasticsearch,若是docker安装的,这里是docker-cluster # http://localhost:9200/_cluster/state 查看节点名称 cluster-name: docker-cluster
定义一个实体类springboot
@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数据:
新建一个实体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来处理分页请求参数
/**分页查询*/ @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 }
精确匹配,查询中文时,须要安装分词插件,查询英文没问题
/**精确匹配*/ @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" } ], # 其余省略 }
/**模糊匹配*/ @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" } ], # 其余省略 }
/**范围查询*/ @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数据:
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