位置服务(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是无序的也无大小限制.
若是你们有新发现,也可回帖,我会及时补充.