关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JAVA »MongoDB JVM 开发库使用简单介绍

    MongoDB JVM 开发库使用简单介绍

    2015-05-19 00:00:00 出处:ImportNew
    分享

    当存储基于文档的 JSON 数据的时候,MongoDB 是我最喜欢的数据库。基于 JVM 的语言在与 MongoDB 交互上有很多种选择。我觉得拿四个最流行的解决方案并且都实现一个用例,对我来说不失为一个好的练习。用例:创建一个可以获取一个城市和距其最近的城市的列表的 REST 服务。

    我要比较的四个选择是:标准的MongoDB Java Driver、Jongo、Monphia和Spring Data Mongo。为了简洁,我是用 groovy 完成代码,并且使用 Spring Boot 以减少样板代码。

    基础配置

    Spring Boot 应用的代码非常简洁,如下:

    import org.springframework.boot.SpringApplication
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration
    import org.springframework.context.annotation.ComponentScan
    import org.springframework.context.annotation.Configuration
    @EnableAutoConfiguration
    @ComponentScan
    @Configuration
    class MongoComparison
    {
        static void main(String[] args) {
            SpringApplication.run(MongoComparison, args);
        }
    }

    同时,我也提供了此次对比所使用的Gradle构建文件:

    buildscript {
        repositories {
            jcenter()
            maven {
                url 'http://repo.spring.io/milestone'
            }
        }
        dependencies {
            classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.9.RELEASE")
        }
    }
    apply plugin: 'groovy'
    apply plugin: 'spring-boot'
    repositories {
        jcenter()
        maven { url 'http://repo.spring.io/milestone' }
        maven { url 'http://www.allanbank.com/repo/' }
    }
    dependencies {
        compile("org.springframework.boot:spring-boot-starter-web")
        compile("org.springframework.boot:spring-boot-starter-data-mongodb")
        compile("org.jongo:jongo:1.1")
        compile("org.mongodb.morphia:morphia:0.108")
        compile("de.grundid.opendatalab:geojson-jackson:1.2")
        compile("org.codehaus.groovy:groovy-all:2.3.6")
     }
    task wrapper(type: Wrapper) {
        gradleVersion = '2.1'
    }

    因为我使用了 Spring Boot 和 Spring Data MongoDB 框架,一些配置可以忽略。例如,Spring Boot 框架在为 Spring Boot 的应用程序上下文提供了 MongoClient bean 和 MongoTemplate bean。你无需在你的配置文件中额外配置(我是用的是 YAML 风格的配置)。

    spring:
        groovy:
            template:
                check-template-location: false
        data:
            mongodb:
                host: "localhost"
                database: "citydbdata"

    基本框架完成之后,我们可以开始对比。

    MongoDB Java驱动

    因为所有的连接 MongoDB 的程序,都用到了 MongoDB 原生的 Java 驱动,所以我觉得从 MongoDB Java Driver (下称 Java Driver)开始最合适。Java Driver 是 JVM 上使用 MongoDB 的最底层的途径。也就是说,写出的程序会略显冗长,并且API不如其他的更加用户友好。然而,你使用Java Driver能够实现所有的功能。Java Driver 在 Spring Data MongoDB 里已经自动引入,如果你需要单独使用的话,需要引入相应的依赖。

    这是使用Java Driver实现的代码:

    import com.mongodb.*
    import org.bson.types.ObjectId
    import org.geojson.Point
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.http.*
    import org.springframework.web.bind.annotation.*
    import javax.annotation.PostConstruct
    import static org.springframework.web.bind.annotation.RequestMethod.GET
    @RestController
    @RequestMapping("/mongoclient")
    class CityControllerMongoClient {
        final DB db
        def dbObjectToCityTransformer = { DBObject it ->
            def objectMap = it.toMap()
            return new City(_id: objectMap._id, name: objectMap.name, location: new Point(objectMap.location.coordinates[0], objectMap.location.coordinates[1]))
        }
        @Autowired
        CityControllerMongoClient(MongoClient mongoClient) {
            db = mongoClient.getDB("citydbmongoclient")
        }
        @RequestMapping(value="/", method = GET)
        List<City> index() {
            return db.getCollection("city").find().collect(dbObjectToCityTransformer)
        }
        @RequestMapping(value="/near/{cityName}", method = GET)
        ResponseEntity nearCity(@PathVariable String cityName) {
            def city = dbObjectToCityTransformer(db.getCollection("city").findOne(new BasicDBObject("name", cityName)))
            if(city) {
                def point = new BasicDBObject([type: "Point", coordinates: [city.location.coordinates.longitude, city.location.coordinates.latitude]])
                def geoNearCommand =  new BasicDBObject([geoNear: "city", spherical: true, near: point])
                def closestCities = db.command(geoNearCommand).toMap()
                def closest = closestCities.results[1]
                return new ResponseEntity([name:closest.obj.name, distance:closest.dis/1000], HttpStatus.OK)
            }
            else {
                return new ResponseEntity(HttpStatus.NOT_FOUND)
            }
        }
        @PostConstruct
        void populateCities() {
            db.getCollection("city").drop()
            [new City(name: "London",
                    location: new Point(-0.125487, 51.508515)),
             new City(name: "Paris",
                     location: new Point(2.352222, 48.856614)),
             new City(name: "New York",
                     location: new Point(-74.005973, 40.714353)),
             new City(name: "San Francisco",
                     location: new Point(-122.419416, 37.774929))].each {
                DBObject location = new BasicDBObject([type: "Point", coordinates: [it.location.coordinates.longitude, it.location.coordinates.latitude]])
                DBObject city = new BasicDBObject([name: it.name, location: location])
                db.getCollection("city").insert(city)
            }
            db.getCollection("city").createIndex(new BasicDBObject("location", "2dsphere"))
        }
        static class City {
            ObjectId _id
            String name
            Point location
        }
    }

    Java Driver 整体以 DBObject 为中心,你需要一直提供领域对象和DBObject之间的映射。Java Driver没有提供任何形式的对象映射。幸运的是, DBObject 的结构很像 map,并且 Groovy Map 的简洁的风格让其操作起来方便许多。本例中,要找到距某城市最近的城市以及其最短距离时,需要用到 geoNear 命令,你可能需要从 mongoDB 的手册找到其详细的语法。语法缩略如下:

    {
       geoNear: collectionName,
       near: { type: "Point" , coordinates: [ longitude, latitude ] } ,
       spherical: true
    }

    geoNear 命令会返回集合中距离最近的对象,并且提供一个字段来标识他们之间的距离;距离的单位是米。 geoNear 命令中的near字段的格式有两种,一种是上面代码示例,另一种是更传统的2个 double 值组成的数组。因为前一种符合 GeoJSON 的标准,我更推荐这种方式。在我所有的例子中,我尽量都是用 GeoJSON 记法来存储地理位置信息数据。从代码里能看出来,我使用了一个提供了所有 GeoJSON 类型支持的 Java 类库。

    撇开所有 DBObject 到领域对象的约定,例子中的代码都非常易读。当然你需要知道 MongoDB 查询的细节;然而当你了解了之后,Java Driver 就是一个非常强大的工具。

    Jongo

    Jongo 框架支持基于字符串的交互和查询(查询时不需要创建 DBObject ),因此允许你使用接近于 Mongo Shell 的方式与 MongoDB 实例进行交互。Jongo 使用 Jackson 框架来完成对象映射,所以无需将查询结果和想插入的数据转换为 DBObject 实例。我在使用的 GeoJSON 库内置了Jackson 的支持,对此,我们无需为此多编写代码。

    Jongo的用例的代码如下:

    import com.fasterxml.jackson.databind.ObjectMapper
    import com.mongodb.MongoClient
    import org.bson.types.ObjectId
    import org.geojson.Point
    import org.jongo.Jongo
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.http.*
    import org.springframework.web.bind.annotation.*
    import javax.annotation.PostConstruct
    import static org.springframework.web.bind.annotation.RequestMethod.GET
    @RestController
    @RequestMapping("/jongo")
    class CityControllerJongo {
        final Jongo jongo
        @Autowired
        CityControllerJongo(MongoClient mongoClient) {
            jongo = new Jongo(mongoClient.getDB("citydbjongo"))
        }
        @RequestMapping(value="/", method = GET)
        List<City> index() {
            return jongo.getCollection("city").find().as(City).asList()
        }
        @RequestMapping(value="/near/{cityName}", method = GET)
        ResponseEntity nearCity(@PathVariable String cityName) {
            def city = jongo.getCollection("city").findOne("{name:'$cityName'}").as(City)
            if(city) {
                def command = """{
                    geoNear: "city",
                    near: ${new ObjectMapper().writeValueAsString(city.location)},
                    spherical: true
                }"""
                def closestCities = jongo.runCommand(command).as(GeoNearResult) as GeoNearResult<City>
                def closest = closestCities.results[1]
                return new ResponseEntity([name:closest.obj.name, distance:closest.dis/1000], HttpStatus.OK)
            }
            else {
                return new ResponseEntity(HttpStatus.NOT_FOUND)
            }
        }
        @PostConstruct
        void populateCities() {
            jongo.getCollection("city").drop()
            [ new City( name:"London",
                    location: new Point(-0.125487, 51.508515)),
              new City( name:"Paris",
                      location: new Point(2.352222, 48.856614)),
              new City( name:"New York",
                      location: new Point(-74.005973, 40.714353)),
              new City( name:"San Francisco",
                      location: new Point(-122.419416, 37.774929)) ].each {
                jongo.getCollection("city").save(it)
            }
            jongo.getCollection("city").ensureIndex("{location:'2dsphere'}")
        }
        static class GeoNearResult<O> {
            List<GeoNearItem<O>> results
        }
        static class GeoNearItem<O> {
            Double dis
            O obj
        }
        static class City {
            ObjectId _id
            String name
            Point location
        }
    }

    从例子中可以看出,Jongo 更面向字符串,尤其是使用 GeoNear 命令查询的时候。同时,多亏 Jackson 框架,我们查询和插入时,不用编写任何的转换的代码。

    如果你是先接触到MongoDB,熟悉shell命令并且不想做手工映射的话,Jongo是非常便捷的。但是,你需要去了解Mongo Shell API的确切语法;同时,你在构造查询、编写命令式没有自动的代码补全,如果你觉得这样是可以接受的话,Jongo是一个不错的选择。

    Morphia

    MongoDB 的开发者(因为Trisha Gee,我不能说汉子们)为MongoDB量身定做了一个映射框架。 Morphia是一个注解驱动的框架,也就是说为了使用 Morphia ,你得使用注解来注释你的 POJO (尽管如此,你可以不写注解以使用默认的注解)。 Morphia 支持 MongoDB 的大部分函数,遗憾的是没有对 GeoJSON 提供支持从而也不支持 geoNear。MongoDB 的开发者专注的开发 MongoDB Java Driver 3.0,有些忽略 Morphia。 可能会在未来的版本中提供对 GeoJSON 的支持。

    因为我用到了 geoNear 函数,除了把Java Driver的测试用例中的代码中的拿来复用也没有更好的选项了。如下是用 Morphia 实现的用例:

    import com.mongodb.*
    import org.bson.types.ObjectId
    import org.geojson.Point
    import org.mongodb.morphia.*
    import org.mongodb.morphia.annotations.*
    import org.mongodb.morphia.converters.TypeConverter
    import org.mongodb.morphia.mapping.MappedField
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.http.*
    import org.springframework.web.bind.annotation.*
    import javax.annotation.PostConstruct
    import static org.springframework.web.bind.annotation.RequestMethod.GET
    @RestController
    @RequestMapping("/mongomorphia")
    class CityControllerMorphia {
        final Datastore datastore
        @Autowired
        CityControllerMorphia(MongoClient mongoClient) {
            def morphia = new Morphia()
            morphia.mapper.converters.addConverter(GeoJsonPointTypeConverter)
            datastore = morphia.createDatastore(mongoClient, "citymorphia")
        }
        @RequestMapping(value="/", method = GET)
        List<City> index() {
            return datastore.find(City).asList()
        }
        @RequestMapping(value="/near/{cityName}", method = GET)
        ResponseEntity nearCity(@PathVariable String cityName) {
            def city = datastore.find(City, "name", cityName).get()
            if(city) {
                def point = new BasicDBObject([type: "Point", coordinates: [city.location.coordinates.longitude, city.location.coordinates.latitude]])
                def geoNearCommand =  new BasicDBObject([geoNear: "City", spherical: true, near: point])
                def closestCities = datastore.DB.command(geoNearCommand).toMap()
                def closest = (closestCities.results as List<Map>).get(1)
                return new ResponseEntity([name:closest.obj.name, distance:closest.dis/1000], HttpStatus.OK)
            }
            else {
                return new ResponseEntity(HttpStatus.NOT_FOUND)
            }
        }
        @PostConstruct
        void populateCities() {
            datastore.delete(datastore.createQuery(City))
            [new City(name: "London",
                    location: new Point(-0.125487, 51.508515)),
             new City(name: "Paris",
                     location: new Point(2.352222, 48.856614)),
             new City(name: "New York",
                     location: new Point(-74.005973, 40.714353)),
             new City(name: "San Francisco",
                     location: new Point(-122.419416, 37.774929))].each {
                datastore.save(it)
            }
            datastore.getCollection(City).createIndex(new BasicDBObject("location", "2dsphere"))
        }
        @Entity
        static class City {
            @Id
            ObjectId id
            String name
            Point location
        }
        static class GeoJsonPointTypeConverter extends TypeConverter {
            GeoJsonPointTypeConverter() {
                super(Point)
            }
            @Override
            Object decode(Class< > targetClass, Object fromDBObject, MappedField optionalExtraInfo) {
                double[] coordinates = (fromDBObject as DBObject).get("coordinates")
                return new Point(coordinates[0], coordinates[1])
            }
            @Override
            Object encode(Object value, MappedField optionalExtraInfo) {
                def point = value as Point
                return new BasicDBObject([type:"Point", coordinates:[point.coordinates.longitude, point.coordinates.latitude]])
            }
        }
    }

    因为 Morphia 框架没有对 GeoJSON 提供支持,所以,你要么使用传统的用包含两个坐标的 double 类型的数组,要么写你自己的转换器。我选择了后者,毕竟也不是那么难写。不要忘了把你的转换器加入到 Morphia 中。从代码中可以看出,我已经使用 Morphia 的注解注释了 City 类,对于那些熟悉 JPA 的开发者来说,这种方式直截了当。同时,因为 Morphia 不提供 2dsphere index 查询支持,你要自己创建 2dsphere 索引。

    针对MongoDB的Spring Data

    最后但同样重要的是 Spring Data,我在研究如何用它完成这个用例。如果你熟知 Spring Data 框架的话,你需要写与数据存储交互的库接口,使用方法名来指定你需要使用的查询。在此例中,我们只需要两个查询:根据名字查询城市,找到距某城市最近的城市。Spring Data 框架支持 geospatial 查询(地理空间查询)。

    Spring Data 框架有用来表示地理空间坐标的类,但是和 GeoJSON 不兼容(再提一遍:Spring 有其自有的方式)。所幸 Spring Data 能够自动生成索引并且 mongo 也能够处理 Spring Data 使用的坐标表示方式。

    这是针对Morphia的实现:(译注:我认为是原文错误)

    import org.bson.types.ObjectId
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.data.geo.*
    import org.springframework.data.mongodb.core.MongoTemplate
    import org.springframework.data.mongodb.core.index.*
    import org.springframework.data.mongodb.core.mapping.Document
    import org.springframework.data.mongodb.repository.MongoRepository
    import org.springframework.http.HttpStatus
    import org.springframework.http.ResponseEntity
    import org.springframework.web.bind.annotation.*
    import javax.annotation.PostConstruct
    import static org.springframework.web.bind.annotation.RequestMethod.GET
    @RestController
    @RequestMapping("/mongodata")
    class CityControllerMongoData {
        final CityRepository cityRepository
        @Autowired
        CityControllerMongoData(CityRepository cityRepository) {
            this.cityRepository = cityRepository
        }
        @RequestMapping(value="/", method = GET)
        List<City> index() {
            return cityRepository.findAll()
        }
        @RequestMapping(value="/near/{cityName}", method = GET)
        ResponseEntity nearCity(@PathVariable String cityName) {
            def city = cityRepository.findByName(cityName)
            if(city) {
                GeoResults<City> closestCities = cityRepository.findByLocationNear(city.location, new Distance(10000, Metrics.KILOMETERS))
                def closest = closestCities.content.get(1)
                return new ResponseEntity([name:closest.content.name, distance:closest.distance.in(Metrics.KILOMETERS).value], HttpStatus.OK)
            }
            else {
                return new ResponseEntity(HttpStatus.NOT_FOUND)
            }
        }
        @PostConstruct
        void populateCities() {
            cityRepository.deleteAll()
            [ new City( name:"London",
                    location: new Point(-0.125487, 51.508515)),
              new City( name:"Paris",
                      location: new Point(2.352222, 48.856614)),
              new City( name:"New York",
                      location: new Point(-74.005973, 40.714353)),
              new City( name:"San Francisco",
                      location: new Point(-122.419416, 37.774929)) ].each {
                cityRepository.save(it)
            }
        }
        @Document(collection = "city")
        static class City {
            ObjectId id
            String name
            @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
            Point location
        }
    }
    interface CityRepository extends MongoRepository<CityControllerMongoData.City, ObjectId> {
        CityControllerMongoData.City findByName(String name);
        GeoResults<CityControllerMongoData.City> findByLocationNear(Point point, Distance distance);
    }

    就可读性来说,Spring Data 无疑是最好的。你不需要知道 MongoDB 里的查询语句是如何构建的,你只需要使用库中的命名惯例就好。当你使用2dsphere 索引的时候,要记住一点,就是使用 near query 方法时要加入 distance 参数,不然 Spring Data 在查询 MongoDB 时会忽略 sphere 选项(在此情况下会失败?报错)。如果你不需要距离,你可以把命令的返回值城市对象的列表。你不用实现相应的接口,Spring Data 框架会替你实现。

    使用Spring Data MongoDB 框架的时候,你也可以使用 MongoTemplate 类。MongoTemplate 类提供了一种类似于 Jongo 和 Java Driver 的机制。使用 MongoTemplate 可以很容易的实现geoNear 查询。

    你也可能已经注意到,Spring Data MongoDB 是唯一一个没有在Java代码中提供数据库名字的框架。这是因为Spring Data使用MongoTemplate,而 MongoTemplate 需要你在配置文件中配置。也就是说,你可以将数据库的名字注入到对应的变量上,并且使用此变量来代表数据库的名字。

    对于Spring Data Mongo唯一不满意的地方就是他们选取了一种不标准的方式来表示地理信息数据。如果你有一个mongo的集合,集合里全是使用GeoJSON格式化过的数据,因为仓库不能处理,基本上你就“完蛋”了(至少生成的near查询不行)。我尝试了在我映射的City对象里使用GeoJSON的类,但是不能使转换正常工作(Spring Data框架没有使用Jackson框架做序列化)。并且,库接口里的geoNear方法生成的query串使用的旧的坐标对,而不是GeoJSON几何结构。如果Spring Data能够提供对GeoJSON格式的位置和查询的支持,就像在一个很好的蛋糕顶端添加了一颗樱桃。

    结论

    对于这个用例,我对于框架选择是:Spring Data,接着是Jongo和Java Driver。 Jongo排第二是因为其映射的功能,而不是其他方面的功能;Java Driver也基本相同。Morphia排最后是因为缺少对geoNear查询的支持,并且缺少对地理对象的内置支持(double类型的数组除外)。当Morphia的下一个版本发布的时候,它的关注点可能会改变。使用Java Driver写出的程序可能较冗长,但是和Groovy搭配在一起使用,冗长的缺点也可以克服。

    这是一个相当简单的例子,但是对我来说这是一个宝贵的学习经验。基于上文中我的了解,我可能倾向于使用Spring Data MongoDB框架,并且当在库接口里函数过于复杂时,我会引入Java Driver。或许在其他的场景下,我的选择会不一样,时间会给我答案。我觉得,胸中有了这两个的组合,没有什么我做不了的了。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    Destoon 模板存放规则及语法参考

    Destoon系统常量与变量

    Destoon系统目录文件结构说明

    Destoon 系统安装指南

    Destoon会员公司主页模板风格添加方法

    Destoon 二次开发入门

    Microsoft 将于 2026 年 10 月终止对 Windows 11 SE 的支持

    Windows 11 存储感知如何设置?了解Windows 11 存储感知开启的好处

    Windows 11 24H2 更新灾难:系统升级了,SSD固态盘不见了...

    小米路由器买哪款?Miwifi热门路由器型号对比分析

    IT头条

    Synology 对 Office 套件进行重大 AI 更新,增强私有云的生产力和安全性

    01:43

    StorONE 的高效平台将 Storage Guardian 数据中心占用空间减少 80%

    11:03

    年赚千亿的印度能源巨头Nayara 云服务瘫痪,被微软卡了一下脖子

    12:54

    国产6nm GPU新突破!砺算科技官宣:自研TrueGPU架构7月26日发布

    01:57

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    技术热点

    最全面的前端开发指南

    Windows7任务栏桌面下角的一些正在运行的图标不见了

    sql server快速删除记录方法

    SQL Server 7移动数据的6种方法

    SQL Server 2008的新压缩特性

    每个Java程序员必须知道的5个JVM命令行标志

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。