位置服務(LBS)解決的主要問題是當前位置周圍某個範圍內的人或場所.
在傳統的解決方案,開發人員須要根據複雜的幾何運算與大量的SQL語句進行查找,這無疑加大的開發人員的開發難度.
如今咱們須要更爲方便的解決方案,MongoDB爲咱們完美解決此類LBS問題.此篇文章也主要使用SpringData,將spring與MongoDB進行整合.java
MongoDB目前支持二維的地圖查詢,查詢區域包括圓形與矩形,距離單位包括MILES,KILOMETERS,NEUTRAL,下面的示例演示距離單位爲NEUTRAL,而實際生產應用中則會用到MILES與KILOMETERS.spring
首先定義一個位置集合,給定a,b,c,d節點.mongodb
1app 2spa 3hibernate 4code 5xml 6對象 7排序 8 |
> db.createCollection("location") { "ok" : 1 } > db.location.save( {_id: "A", position: [0.1, -0.1]} ) > db.location.save( {_id: "B", position: [1.0, 1.0]} ) > db.location.save( {_id: "C", position: [0.5, 0.5]} ) > db.location.save( {_id: "D", position: [-0.5, -0.5]} )
|
接着指定location索引
1 |
db.location.ensureIndex( {position: "2d"} ) |
如今咱們能夠進行簡單的GEO查詢
查詢point(0,0),半徑0.7附近的點
1 2 3 4 |
> db.location.find( {position: { $near: [0,0], $maxDistance: 0.7 } } ) { "_id" : "A", "position" : [ 0.1, -0.1 ] }
|
查詢point(0,0),半徑0.75附近的點
1 2 3 4 5 6 |
> db.location.find( {position: { $near: [0,0], $maxDistance: 0.75 } } ) { "_id" : "A", "position" : [ 0.1, -0.1 ] } { "_id" : "C", "position" : [ 0.5, 0.5 ] } { "_id" : "D", "position" : [ -0.5, -0.5 ] }
|
咱們能夠看到半徑不同,查詢出的點也不同,由於c點座標爲[0.5,0.5],c至圓點的距離根據勾股定理可得出Math.sqrt(0.25 +0.25) ≈ 0.707,因此最大距離0.7時查找不到你要的點.
查詢[0.25, 0.25], [1.0,1.0]區域附近的點
1 2 3 4 5 |
> db.location.find( {position: { $within: { $box: [ [0.25, 0.25], [1.0,1.0] ] } } } ) { "_id" : "C", "position" : [ 0.5, 0.5 ] } { "_id" : "B", "position" : [ 1, 1 ] }
|
spring data爲咱們封裝了mongoDB訪問接口與實現,咱們能夠像使用hibernateTemplate同樣使用mongoTemplate.
首先咱們須要像hibernate同樣定義pojo類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document;
@Document(collection = "location") public class Location {
@Id private String id;
private double[] position;
/** getter setter hashcode equals toString ... */
}
|
定義Dao,咱們先使用最簡單的mongoTemplate來實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.geo.Box; import org.springframework.data.mongodb.core.geo.Point; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository;
@Repository public class LocationDao {
@Autowired MongoTemplate mongoTemplate;
public List<Location> findCircleNear(Point point, double maxDistance) { return mongoTemplate.find( new Query(Criteria.where("position").near(point).maxDistance(maxDistance)), Location.class); }
public List<Location> findBoxNear(Point lowerLeft, Point upperRight) { return mongoTemplate.find( new Query(Criteria.where("position").within(new Box(lowerLeft, upperRight))), Location.class); }
}
|
最後咱們寫一個test類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
import java.util.Collection; import java.util.List;
import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.geo.Point; import org.springframework.data.mongodb.core.index.GeospatialIndex; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:/applicationContext.xml", "classpath:/application-mongo.xml" }) public class MongoDBTest {
@Autowired LocationDao locationDao;
@Autowired MongoTemplate template;
@Before public void setUp() { // 等同db.location.ensureIndex( {position: "2d"} ) template.indexOps(Location.class).ensureIndex(new GeospatialIndex("position")); // 初始化數據 template.save(new Location("A", 0.1, -0.1)); template.save(new Location("B", 1, 1)); template.save(new Location("C", 0.5, 0.5)); template.save(new Location("D", -0.5, -0.5)); }
@Test public void findCircleNearTest() { List<Location> locations = locationDao.findCircleNear(new Point(0, 0), 0.7); print(locations); System.err.println("-----------------------"); locations = locationDao.findCircleNear(new Point(0, 0), 0.75); print(locations); }
@Test public void findBoxNearTest() { List<Location> locations = locationDao.findBoxNear(new Point(0.2, 0.2), new Point(1, 1)); print(locations); }
public static void print(Collection<Location> locations) { for (Location location : locations) { System.err.println(location); } }
}
|
你們能夠看到運行結果與咱們直接在mongoDB上的同樣.
MongoRepository提供了對MongoTemplate的封裝與實現,只須要繼承MongoRepository接口,填上對應的bean類與ID類型,無需實現裏面的方法便可使用,先看代碼.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import org.springframework.data.mongodb.core.geo.Box; import org.springframework.data.mongodb.core.geo.Distance; import org.springframework.data.mongodb.core.geo.Point; import org.springframework.data.mongodb.repository.MongoRepository;
public interface LocationRepository extends MongoRepository<Location, String> {
List<Location> findByPositionNear(Point p, Distance d);
List<Location> findByPositionWithin(Box b);
}
|
而後在test類中引用此類便可,MongoRepository實現了最基本的增刪改查的功能,要想增長額外的查詢方法,能夠按照如下規則定義接口的方法.
自定義查詢方法,格式爲findBy+字段名+方法名,方法傳進的參數即字段的值,此外還支持分頁查詢,經過傳進一個Pageable對象會返回Page集合.
原理相信你們也很清楚,即aop,細節就不說拉.
near與within方法區別,near方法查詢後會對結果集對distance進行排序且有大小限制,而within是無序的也無大小限制.
若是你們有新發現,也可回帖,我會及時補充.