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