• CouchDB简单应用


    CouchDB是众多称作NoSQL解决方案中的一员。与众不同的是,CouchDB是一个面向文档的数据库,在它里面所有文档域(Field)都是以键值对的形式存储的。域(Field)可以是一个简单的键值对、列表或者是map。

    CouchDB会为存储到数据库中的每一个文档分配一个文档级别的唯一标识符(id),同时每次将变动保存到数据库中时还会分配一个修订号(rev)。

    NoSQL数据库的出现代表着传统的关系型数据库的转变,它能够提供很多好处,当然其自身也面临着挑战。CouchDB为我们提供了下面的特性:

     
    • 容易地在多个服务器实例之间进行数据库复制
    • 快速地索引和检索
    • REST风格的文档插入、更新、检索和删除的接口
    • 基于JSON的文档格式(更容易地在不同语言之间转换)
    • 为用户选择的语言提供多个库(指一些流行的语言)
    • 通过_changes订阅数据更新

    NoSQL系统可视化向导中可以找到一个非常出色的工具,它能帮你决定哪一个数据存储适合你。该指南描述了选择数据库系统时应该关注的三个方面(NoSQL和关系型数据库都是如此)。在我们的项目中使用该指南筛选数据库时会关注下面的特性:

    • 可用性
    • 一致性
    • 分区容忍度

    CouchDB侧重于AP(可用性和分区容忍度),这正是满足我们的数据关注点所要寻找的数据库(更不用说在连续的或者点对点的设备间进行数据复制的能力)。相比之下,MongoDB侧重于CP(一致性和分区容忍度),像Neo4J这样的数据库则提供了特有的面向图形的结构。

    另一个出色的工具是这篇博客文章,它对Cassandra、MongoDB、CouchDB、Redis、Riak、Hbase和Membase做了比较。

     

    当然,对于一个给定的项目你很可能有多个工具,换言之,这就需要明确需求并找到合适的工具以满足这些需求。

    我们将如何使用CouchDB?

    我们将要构建一个简单的本地事件数据库,用于存储一些事件以及这些事件发生的位置。我们将会把它分为两个文档,通过它们的文档id将两者关联起来。这两个文档是:

    • 事件
    • 位置

    (本文稍后会为这两个文档创建Java类)

    Jcouchdb

    我们将使用jcouchdb与CouchDB数据库交互。这是一个经过良好测试并且易于使用的Java库,它会自动地将Java对象序列化、反序列化进CouchDB数据库。选择jcouchdb的另一个原因是它和CouchDB自身的API非常相似。

    Jcouchdb的替代方案有那些?

    如果你不喜欢jcouchdb,或者想要尝试其他的库,那么可选项也有很多,如:

    其中有少数已经很久没有更新了,所以,如果你要做一些测试,请确保预留一些时间解决程序的问题。

    开始

    从哪儿开始呢?我们将会使用Maven3构建这个示例项目。哪怕不知道Maven也能理解代码,但是为了构建并运行示例项目你需要安装它。可以从Maven网站找到Maven3.

    指南的这个部分假定你具有一定的Maven3知识,但是如果你不了解Maven,你可以直接使用从我们的库中下载的pom.xml文件并直接使用它。

    我们将会跳过POM创建的初始部分,但是如果需要创建POM文件的细节或者仅仅想要开始编码可以从我们的github库中下载它。首先要做的就是指定需要的jcouchdb和Spring组件。

    <properties>
        <spring.framework.version>3.1.0.RELEASE</spring.framework.version>
        <spring-xml.version>2.0.0.RELEASE</spring-xml.version>
        <jcouchdb.version>0.11.0-1</jcouchdb.version>
    ...
    </properties>
    

    在文件顶部指定版本信息的一个原因是,这样可以很容易地一次性将一个库(或者一组库,如Spring)快速地更新到新版本。

    <dependencies>
        <dependency>
            <groupId>com.google.code.jcouchdb</groupId>
            <artifactId>jcouchdb</artifactId>
            <version>${jcouchdb.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.framework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.framework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.framework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ws</groupId>
            <artifactId>spring-xml</artifactId>
            <version>${spring-xml.version}</version>
        </dependency>
        ...
    </dependencies>
    

    在初始化依赖设置完成之后,我们需要为项目设置剩下的目录结构。我们将遵循标准的Maven设置:

    -src
        -main
            -java
            -resources
            -webapp
        -test
            -java
            -resources
    

    设置CouchDB

    完成了初始化设置之后,接下来就需要设置CouchDB数据库了。幸运的是,有一些非常好的解决方案可以帮助我们快速地启动并运行CouchDB。

    它们都提供了免费的账号,能够完美的设置好数据库,以便我们开始开发工作。  (单击放大图片)

    图1.CouchAnt首页

    (单击放大图片)

    图2.CouchAnt Futon页面

    (单击放大图片)

    图3。Iris Couch注册页面

    (单击放大图片)

    图4. Iris CouchFuton页面

    另一个选择是在本地机器(或主机)上安装CouchDB。我们并不会带你在你的操作系统上安装它,但是在CouchDB的wiki上有一些非常好的说明。

    在账号创建完成之后(或者在设置并启动CouchDB之后),将需要创建一个的数据库。在我们的应用程序中选择了couchspring作为数据库名。你可以随意取名,但是当我们开始配置设置时需要将其修改为对应的名字。

    在CloudAnt中,可以在数据库截图(图1)中创建数据库,而对于Iris Couch来说可以直接在Futon页面(管理CouchDB实例的用户界面)中创建。关于管理页面的更多信息可以在CouchDB wiki中找到。本文并不会过多的使用管理页面,但是这是一个非常好的操作视图的工具。

    图5.在管理页面中创建数据库的步骤1

    图6.在Futon页面中创建数据库的步骤2

    配置jcouchdb、Spring和POJOS

    在新数据库设置完成之后,我们需要:

    • 创建基础POJO对象
    • 提供一个json配置映射,它能够将CouchDB使用的Java对象和JSON对象自动地进行转换
    • Spring 配置

    首先,让我们创建一些对象!

    带有自定义注解的POJOs

    我们将要为事件系统创建的基础对象是哪些?

    • Event——存储来自于外部源(如Eventful.com)或Web界面的事件
    • Place——存储事件的发生位置。

    同时,还会结合使用一些其他的对象(从外部源中抽取数据时做一些额外的数据处理):

    • AppDocument ——由json映射实用程序所使用的用来定义文档类型识别域的基础对象
    • Description——用于格式化并过滤事件的描述
    • Location——用于记录给定位置/地点的经纬度

    首先,需要创建基础类AppDocument

    AppDocument.java

    package com.clearboxmedia.couchspring.domain;
    
    import org.jcouchdb.document.BaseDocument;
    import org.svenson.JSONProperty;
    
    public class AppDocument extends BaseDocument {
        /**
         * Returns the simple name of the class as doc type.
         * 
         * The annotation makes it a read-only property and also
         * shortens the JSON name a little.
         *
         * @return document type name
         */
        @JSONProperty(value = "docType", readOnly = true)
        public String getDocumentType()
        {
            return this.getClass().getSimpleName();
        }
    
    }
    

    该对象继承了jcouchdb自身的BaseDocument对象,同时它还提供了一种区分不同的文档类型的方法。CouchDB并没有提供处理这些内容的默认方式,而是将其留给了开发者,让他们去实现各自的处理方式。我们选择使用类名作为识别符,例如:Event对象的docType会输出Event,而Place对象会输出Place。

    接下来需要创建Event类。

    Event.java(为简单起见,我们省略了一些域和方法)

    package com.clearboxmedia.couchspring.domain;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Iterator;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.xml.bind.annotation.XmlRootElement;
    import javax.xml.bind.annotation.XmlElement;
    
    import org.svenson.JSONProperty;
    
    public class Event extends AppDocument {
        private String id;
        private String title;
        private String description;
        private String startTime;
        private String stopTime;
        private String venueId;
        private Map

    这里有几件有趣的事。首先就是我们将会在对象中存储的是venueId而非venue,为什么要这样做呢?

    因为CouchDB并不是关系型数据库,它没有一种直接的方式去定义两个不同文档之间的关系,因此我们在Event对象中存储venue的id。我们可以在event对象中存储venue对象,但是将这些对象分开存储更清晰,尤其对于一个给定的地点可能会有多个事件。因此,我们将会提供一个动态的获取器,仅在我们需要的时候才会检索venue对象,而不是存储关系。我们将会在查询文档部分介绍这如何实现。[todo: 动态查询]

    现在,我们来定义Place类。

    Place.java

    package com.clearboxmedia.couchspring.domain;
    import java.util.LinkedHashMap;
    import java.util.List;
    
    public class Place extends AppDocument {
        private String id;
        private String name;
        private String address1;
        private String address2;
        private String city;
        private String state;
        private String postalCode;
        private String lastUpdated;
        private Boolean active;
        private Location location;
        private String venueType;
        private List<String> tags;
    
        public Place() {
        }
    
        public String getId() {
            return this.id;
        }
    
        public void setId(final String id) {
            this.id = id;
        }
    
        public String getName() {
            return this.name;
        }
    
        public void setName(final String name) {
            this.name = name;
        }
    
        public String getAddress1() {
            return this.address1;
        }
    
        public void setAddress1(final String address1) {
            this.address1 = address1;
        }
    
        public String getAddress2() {
            return this.address2;
        }
    
        public void setAddress2(final String address2) {
            this.address2 = address2;
        }
    
        public String getCity() {
            return this.city;
        }
    
        public void setCity(final String city) {
            this.city = city;
        }
    
        public String getState() {
            return this.state;
        }
    
        public void setState(final String state) {
            this.state = state;
        }
    
        public Location getLocation() {
            return this.location;
        }
    
        public void setLocation(final Location location) {
            this.location = location;
        }
    
        public String getVenueType() {
            return this.venueType;
        }
    
        public void setVenueType(final String venueType) {
            this.venueType = venueType;
        }
    
        public String getPostalCode() {
            return this.postalCode;
        }
    
        public void setPostalCode(final String postalCode) {
            this.postalCode = postalCode;
        }
    
        public String getLastUpdated() {
            return this.lastUpdated;
        }
    
        public void setLastUpdated(final String lastUpdated) {
            this.lastUpdated = lastUpdated;
        }
    
        public Boolean getActive() {
            return this.active;
        }
    
        public void setActive(final Boolean active) {
            this.active = active;
        }
    
        public List<String> getTags() {
            return this.tags;
        }
    
        public void setTags(final List<String> tags) {
            this.tags = tags;
        }
    }
    

    我们不再详细介绍其他的辅助对象Description 或者 Location,因为它们实在是太简单了。如果你对它们感兴趣,可以从GitHub仓库中检出它们。

    配置jcouchdb和JsonConfigFactory

    在配置之前,需要创建一些即将使用的类。JsonConfigFactory用于json数据(CouchDB)和Java类之间的映射,CouchDbServerFactory为将要连接的服务器创建新的实例。

    JsonConfigFactory.java public class JsonConfigFactory {
        /**
         * Factory method for creating a {@link JSONConfig}
         *
         * @return {@link JSONConfig} to create
         */
        JSONConfig createJsonConfig() {
            final DateConverter dateConverter = new DateConverter();
    
            final DefaultTypeConverterRepository typeConverterRepository = 
                                                    new DefaultTypeConverterRepository();
            typeConverterRepository.addTypeConverter(dateConverter);
            // typeConverterRepository.addTypeConverter(new LatLongConverter());
    
            // we use the new sub type matcher  
            final ClassNameBasedTypeMapper typeMapper = new ClassNameBasedTypeMapper();
            typeMapper.setBasePackage(AppDocument.class.getPackage().getName());
            // we only want to have AppDocument instances
            typeMapper.setEnforcedBaseType(AppDocument.class);
            // we use the docType property of the AppDocument 
            typeMapper.setDiscriminatorField("docType");        
            // we only want to do the expensive look ahead if we're being told to
            // deliver AppDocument instances.        
            typeMapper.setPathMatcher(new SubtypeMatcher(AppDocument.class));
    
            final JSON generator = new JSON();
            generator.setIgnoredProperties(Arrays.asList("metaClass"));
            generator.setTypeConverterRepository(typeConverterRepository);
            generator.registerTypeConversion(java.util.Date.class, dateConverter);
            generator.registerTypeConversion(java.sql.Date.class, dateConverter);
            generator.registerTypeConversion(java.sql.Timestamp.class, dateConverter);
    
            final JSONParser parser = new JSONParser();
            parser.setTypeMapper(typeMapper);
            parser.setTypeConverterRepository(typeConverterRepository);
            parser.registerTypeConversion(java.util.Date.class, dateConverter);
            parser.registerTypeConversion(java.sql.Date.class, dateConverter);
            parser.registerTypeConversion(java.sql.Timestamp.class, dateConverter);
    
            return new JSONConfig(generator, parser);
        }
    }
    

    该类会创建一个生成器,它将Java类(Event或Place)转换成对应的json,而解析器执行相反的过程。在typeMapper(生成器和解析器都会用到它)中有几个关键点需要注意,特别是基础类型和鉴别器域。

    typeMapper.setEnforcedBaseType(AppDocument.class)仅会转换继承自AppDocument类的文档。

    typeMapper.setDiscriminatorField("docType")将使用docType域和值来鉴别不同的文档类型。可以自由地将该域修改为其他的名字,但是这需要在AppDocument类中改变方法和json映射。为了刷新内存,可以参考下面的方法:

    @JSONProperty(value = "docType", readOnly = true)
    public String getDocumentType()
    {
        return this.getClass().getSimpleName();
    }
    

    要注意的最后一个点是typeMapper.setPathMatcher(new SubtypeMatcher(AppDocument.class)),它会自动查找子类型从而确保我们在继承自AppDocument的对象间转换。可以为检索或查询数据库的几个jcouchdb方法调用提供自己的解析器,但是在本教程不打算探讨那些内容。

    既然有了我们需要的类,是配置spring上下文的时候了。我们将我们的CouchDB特有的配置项分离到couchdb-config.xml中。

    couchdb-config.xml

    <beans 
        xmlns="http://www.springframework.org/schema/beans" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:tx="http://www.springframework.org/schema/tx" 
        xmlns:util="http://www.springframework.org/schema/util"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
            http://www.springframework.org/schema/tx 
            http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
            http://www.springframework.org/schema/util 
            http://www.springframework.org/schema/util/spring-util-3.1.xsd
            http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context-3.1.xsd
            http://www.springframework.org/schema/lang
            http://www.springframework.org/schema/lang/spring-lang-3.1.xsd">
    
    
        <context:annotation-config />
    
        <bean id="properties" 
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" />
    
        <bean id="jsonConfigFactory"
    class="com.clearboxmedia.couchspring.json.JsonConfigFactory"/>
    
        <bean id="jsonConfig" factory-bean="jsonConfigFactory" 
        factory-method="createJsonConfig"/>
    
        <!-- If my db requires username/password, I will need to set up a Principal -->
        <bean id="couchPrincipal"
    class="org.apache.http.auth.UsernamePasswordCredentials">
          <constructor-arg value="${couchdb.username}" />
          <constructor-arg value="${couchdb.password}" />
        </bean>
    
        <bean id="serverFactory" 
    class="com.clearboxmedia.couchspring.couch.CouchDbServerFactory" />
    
        <bean id="couchDbServer" factory-bean="serverFactory" 
    factory-method="createCouchDbServerInstance">
          <constructor-arg value="${couchdb.url}"/>
          <constructor-arg name="credentials" ref="couchPrincipal" />
        </bean>
    
        <bean id="systemDatabase" class="org.jcouchdb.db.Database">
            <constructor-arg ref="couchDbServer"/>
            <constructor-arg value="couchspring-dev"/>
            <property name="jsonConfig" ref="jsonConfig"/>
        </bean>
    </beans>
    

    我们需要做的第一件事就是使用设置注解,它设置了spring上下文的注解。接下来的两个部分设置了jsonConfigFactory,使之做好了在服务器实例中使用的准备。最后,创建了用于创建couchDbServer实例的serverFactory,该实例随后和jsonConfig以及想要连接的数据库名称一起传入了jcouchd database实例。所有属性( username,password 和url)现在都是通过命令行传入的,但是可以简单到仅提供一个指定的属性文件。

    现在,我们已经配置好一切,是时候编写一些测试了。

    创建、保存、检索、更新和删除

    在深入探讨视图创建之前,先从一些基础测试开始,如:创建、更新、检索和删除。因为,在每一个测试中我们都会对它们做一些事情。下面是CouchSaveTest类的定义,对于其他的测试来说也是如此。

    CouchSaveTest.java (header)
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("/root-context.xml")
    public class CouchSaveTest {
    
        @Autowired
        protected Database database;
        ...
    }
    

    第一个注解@RunWith告诉Maven使用SpringJUnit4ClassRunner运行该测试(不是标准的JUnit类运行器)。这样下一个注解@ContextConfiguration("/root-context.xml")才能为这个测试启动一个Spring上下文。该上下文会加载所有的CouchDB 实体、POJOs和它们的JSON注释以及会自动将视图更新到CouchDB服务器的CouchDBUpdater。在下面的Views部分我们将会介绍最后一个注解。

    最后,我们告诉Spring将数据库自动装配到该测试类中,使我们能够使用它。

    Document creation **文档创建

    无论在哪种类型的数据库存储系统中,创建新纪录的能力(本例中是文档)都是首要步骤之一。 使用jcouchdb的API如何实现这些呢?

    CouchSaveTest.java (testEventSave())
    @Test
    public void testEventSave() {
        Event document = new Event();
        document.setTitle("Test");
        assertTrue(document.getId() == null);
    
        database.createDocument(document);
        assertTrue(document.getId() != null);
    }
    

    这里,我们创建了一个新的Event对象,然后将其作为参数调用了database.createDocument()方法。之后JsonConfigFactory会将我们的域映射到CouchDB文档。[插入屏幕截图]

    文档的检索和更新

    CouchSaveTest.java (testEventSave_Update())
    @Test
    public void testEventSave_Update() {
        Event document = database.getDocument(Event.class, "2875977125");
        assertTrue(document != null);
    
        document.setDescription("Testing out save");
    
        database.createOrUpdateDocument(document);
    
        Event newdocument = database.getDocument(Event.class, "2875977125");
        assertTrue(document != null);
        assertTrue(document.getDescription().equals("Testing out save"));
    }
    

    该方法实际上测试了两件事情,首先通过调用Event document = database.getDocument(Event.class, "2875977125");检索文档,检索时传入了文档的id——“2875977125”。其次还测试了更新方法database.createOrUpdateDocument(document);,它的作用正如其名,或创建一个新的文档,或更新一个已有的文档(意味着如果在数据库中能够找到匹配该id的文档时就会更新它)。

    CouchSaveTest.java (testEventSave_Exists2())
    @Test(expected = IllegalStateException.class)
    public void testEventSave_Exists2() {
        Event document = database.getDocument(Event.class, "2875977125");
        assertTrue(document != null);
    
        database.createDocument(document);
        assertFalse(document.getId().equals("2875977125"));
    }
    

    如果我们试图创建一个已经存在的文档,最后一个测试会抛出一个异常(注意,我们没有使用createOrUpdateDocument()方法)。

    文档删除

    删除文档和创建、更新文档一样简单。

    CouchDeleteTest.java (testEventDelete())
    @Test
    public void testEventDelete() {
        Event document = database.getDocument(Event.class, "3083848875");
        assertTrue(document != null);
        database.delete(document);
    
        try {
            document = database.getDocument(Event.class, "3083848875");
        }
        catch (Exception e) {
            assertTrue(e instanceof org.jcouchdb.exception.NotFoundException);
        }
    }
    
    @Test(expected = org.jcouchdb.exception.NotFoundException.class)
    public void testEventDelete_NotExists() {
        Event document = database.getDocument(Event.class, "-2");
        assertTrue(document != null);
        database.delete(document);
        document = database.getDocument(Event.class, "-2");
        assertTrue(document == null);
    }
    

    这两个测试调用了delete()方法分别对存在的和不存在的文档进行了删除,第二种情况会抛出NotFoundException异常。

    查询文档

    现在已经介绍完基础的CRUD操作,那么接下来需要做一些稍微复杂的工作。通过更多的方式查询数据库,而不仅仅是通过要查找的文档的id。在本文中我们仅会探究视图的冰山一角,因为它们可以非常复杂。关于视图的更多内容可以在CouchDB wiki以及在线版的CouchDB: 权威向导中找到。

    视图介绍

    首先,CouchDB视图究竟是什么,它如何工作?

    视图是过滤或查询数据库中数据的一种方式。视图通常使用JavaScript编写,当然使用其他语音也可以编写视图,但那是另一话题,我们并不会在此进行介绍。每一个视图都将文档内的键映射到值。直到文档存取时才会更新视图索引,但是如果你愿意,你也可以通过外部脚本改变这个行为。当查询设计文档中的某一个视图时该文档中的所有视图都会被更新。

    设计文档

    在介绍视图创建之前我们应当讨论如何自动上传应用(并保持视图最新)。所有的视图都捆绑到一个设计文档。本例中我们将有两个设计文档:

    1. event
    2. place

    org.jcouchdb.util.CouchDBUpdater类会自动地创建这两个设计文档,该类是在couchdb-services.xml文件中配置的。

    couchdb-services.xml
    
    <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:tx="http://www.springframework.org/schema/tx"
          xmlns:util="http://www.springframework.org/schema/util"
          xmlns:context="http://www.springframework.org/schema/context"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
          http://www.springframework.org/schema/tx
          http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
          http://www.springframework.org/schema/util
          http://www.springframework.org/schema/util/spring-util-2.5.xsd
          http://www.springframework.org/schema/context
          http://www.springframework.org/schema/context/spring-context-2.5.xsd">
    
          <context:annotation-config />
    
          <bean id="systemUpdater"
            class="org.jcouchdb.util.CouchDBUpdater"
            init-method="updateDesignDocuments">
                <property name="createDatabase" value="true"/>
                <property name="database" ref="systemDatabase"/>
                <property name="designDocumentDir" value="designdocs/"/>
            </bean>
        </beans>
    

    CouchDBUpdater会监听designdocs目录中的变化,同时自动地将这些变化推到配置的CouchDB数据库。

    那么designdocs目录实际上包含什么内容呢?

        -designdocs
            -event
                -allByDate.map.js
                -allByParentId.map.js
                -allByVenueId.map.js
                -list.map.js
            -place
                -list.map.js
    

    实际上每一个目录都映射到CouchDB中的一个设计文档。

    (点击放大图片)

    图7 DesignDocs事件

    很好,接下来就让我们编写这些视图。

    我们的第一个视图

    下面是一个简单的视图,它会查找所有的“event” 类型的文档。

    function(doc) {
        if (doc.docType == 'Event') {
            emit(doc.id, doc);
        }
    }
    

    该视图会简单的返回所有具有docType域并且该域值为Event的文档的id。让我们稍微检查一下它做了什么。第一行是一个JavaScript函数定义,它仅接受一个doc参数。然后就能够检查存储在文档内的值(本例中是doc.docType)。最后,调用了emit函数,它有两个参数key和value,其中value可以为null。在本例中,key是doc.id域,value是整个文档。

    在接下来的几个视图示例中,emit函数才是我们实际上用到的查询数据库的方法。关于emit,我们需要理解的另外一关键点是,它会对返回的文档按照key值进行排序。

    下面是调用list视图的测试用例。

    CouchViewTest.java (testListingQuery())
        @Test
        public void testListingQuery() {
            String viewName = "event/list";
            ViewResult results = database.queryView(viewName, Event.class, null, null);         
            assertNotNull(results);
            assertEquals(27, results.getRows().size()); 
        } 
    

    通过venue id检索events

    为了方便使用,首先需要创建的视图之一就是通过关联的venueId检索给定事件集合的视图。为此,需要编写一个key为venueId、值为document的视图(尽管jcouchdb的函数并没有严格的需要)。那么,该视图是什么样的呢?

    function(doc) {
        if (doc.type == 'Event') {
            emit(doc.venueId, {doc});
        }
    }
    

    它和之前编写的那个简单视图非常相似,但是这次从应用程序中调用该视图进行查询时需要传递一个venueId。

    CouchViewTest.java (testQueryByVenueId())
        @Test
        public void testQueryByVenueId() {
            String viewName = "event/allByVenueId";
    
    
            ViewAndDocumentsResult
    

    此处关于如何调用视图的关键区别之一就是使用queryViewAndDocumentsByKeys()方法传入了viewName、映射的Event类以及要查询的键(这种情况下只会查询出键值为指定venueId的事件)。

    通过日期查询事件

    这两个视图都比较简单。而通过日期查询这种稍微复杂一点功能的如何实现呢?首先,我们需要定义视图。

    function(doc) {
        if (doc.docType == 'Event') {
            var startDate = new Date(doc.startTime);
            var startYear = startDate.getFullYear();
            var startMonth = startDate.getMonth();
            var startDay = startDate.getDate();
            emit([
                startYear,
                startMonth,
                startDay
            ]);
        }
    }
    

    现在,我们如何调用这个函数呢?

    [todo: 在java中调用视图的代码示例]

    从事件中检索venue的动态查询

    CouchViewTest.java (testQueryByDate())
        @Test
        public void testQueryByDate() {
            String viewName = "event/allByDate";
            Options opts = new Options();
            opts.startKey(Arrays.asList(2013, 7, 11));
            opts.endKey(Arrays.asList(2013, 7, 11));
    
            ViewAndDocumentsResult
    

    此处有一个名为Options的新对象,通过它可以指定想要传入视图的查询选项。在本例中,我们提供了一个startKey和一个endKey去检索对象集合。需要注意的一件事情就是,你要返回或匹配的数据需与传入数据的类型保持一致。在本例中处理的是int类型,所以传入的键必须是int类型的域。当然顺序也是键,我们依次传入了年、日、月从而匹配视图中的年、日、月。

    那么,endKey是什么呢?实际上,endKey参数允许我们指定查询的范围。在本例中我们选择了相同的日期,但是可以很容易地选择不同的值从而返回更多或更少的文档。CouchDB会简单地依次比较每一个键,直到再没有匹配的数据时才会返回结果文档集合。

    从事件中检索venue的动态查询

    除了通过Event id查询地址(place)之外,此处所要做的事情就是编写和之前queryByVenueId相同的逻辑。

        @JSONProperty(ignore = true)
        public Place getVenue() {
            String viewName = "place/allByEventId";
    
            ViewAndDocumentsResult
    

    你仅需要为place文档编写一个和allByVenueId相似的视图,仅此而已。

    接下来去向何方?

    视图(或map)只是CouchDB所提供的map/reduce功能的第一部分。那么什么是reduce(和re-reduce)功能,它能给我们带来什么呢?

    Reduce允许我们从之前的map中获取结果集,同时能对其执行附加的操作将结果集分解成更加简洁的形式。

    Reduce和re-reduce的功能需要你自己去探索,但是你可以通过它们做一些非常有趣的事情。探索并感受CouchDB带来的乐趣!

    ---转自:http://www.infoq.com/cn/articles/warner-couchdb

  • 相关阅读:
    Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?
    Mybaits 源码解析 (十一)----- @MapperScan将Mapper接口生成代理注入到Spring-静态代理和动态代理结合使用
    Mybaits 源码解析 (十)----- Spring-Mybatis框架使用与源码解析
    Mybaits 源码解析 (九)----- 一级缓存和二级缓存源码分析
    Mybaits 源码解析 (八)----- 结果集 ResultSet 自动映射成实体类对象(上篇)
    Mybaits 源码解析 (七)----- Select 语句的执行过程分析(下篇)
    Mybaits 源码解析 (六)----- Select 语句的执行过程分析(上篇)
    Mybaits 源码解析 (五)----- Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
    Mybaits 源码解析 (四)----- SqlSession的创建过程
    Mybaits 源码解析 (三)----- Mapper映射的解析过程
  • 原文地址:https://www.cnblogs.com/liuruitao/p/4717842.html
Copyright © 2020-2023  润新知