写点什么

CouchDB 是什么?为什么我们要关注它?

  • 2012-11-08
  • 本文字数:13137 字

    阅读完需:约 43 分钟

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 带来的乐趣!

关于作者

Leo Pryzbylski在 ClearBox 媒体是技术创新的标杆人物。他在挥舞着一个创造性解决问题的巨大棒槌的同时,还痴迷于软件体系结构,富有与软件不切题的很多问题战斗的经验。Leo 具有广泛的技能,这得益于他作为游戏开发者、QA 工程师、发布管理者、配置管理员、开发管理者、计算机科学家、网络入侵专家、嵌入式软件工程师、软件架构师、科学程序员和系统管理员的经验。

Warner Onstine 的 IT 生涯开始于 90 年代早期对 Intuit 的技术支持工作。那个时候他学会了如何开发 web 应用程序,同时开始了自己的软件工程师生涯。在那之后,他在很多地方工作过,包括 Intalio、亚利桑那大学,现在作为开发经理在 rSmart 工作。ClearBox Media 的萌芽始于 Warner 学习 ARG 的时候,同时在几年之前就开始从中找乐了。它们非常吸引人,因为无论是在现实生活还是虚拟环境中它们都是良好的调味剂。Warner 以及其他人发现了 ARG 能让社会变得更好的潜能,同时也是“The Human Mosaic Project”的指导性原则之一—感受乐趣。做得好。

原文地址 http://www.infoq.com/articles/warner-couchdb


感谢马国耀对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012-11-08 23:1014121
用户头像

发布了 321 篇内容, 共 118.4 次阅读, 收获喜欢 19 次。

关注

评论

发布
暂无评论
发现更多内容

面试官问我:什么是缓存击穿,该怎么解决?

喵叔

28天写作 12月日更

毕业总结

毛先生

技术人创业过程中应保持开放的心态

wood

创业 技术 28天写作

MySQL探秘(八):InnoDB的事务

程序员历小冰

MySQL 事务 28天写作 12月日更

你了解集合?那你倒是给我说说啊!【1】

XiaoLin_Java

12月日更

Redis为何这么快?

JavaEdge

12月日更

模块1

Geek_59dec2

聊聊 Kafka:Producer 源码解析

老周聊架构

如何用Python发送告警通知到钉钉?

老表

Python Linux 守护进程 跟老表学云服务器

字典树之旅01.开篇

极客志

自然语言处理 数据结构 算法 nlp 字典树

SQS 和 SNS 对比分析

liuzhen007

28天写作 12月日更

盘点JavaScript哪些常用的字符串对象

你好bk

JavaScript 大前端 字符串 基础知识 12月日更

团队基建系列 - 组织知识传承3 破局

搬砖的周狮傅

面试官synchronized连环问,学会Monitor之后轻松拿下

李子捌

Java、 28天写作 12月日更

架构训练营作业一

supermenG

架构师训练营 4 期

在线MySQL,SQL Server建表语句生成JSON测试数据工具

入门小站

工具

来来来,手摸手写一个hook

全栈潇晨

React React Hooks

字典树之旅02.Trie 的标准实现

极客志

自然语言处理 数据结构 算法 Trie 字典树

聊聊IT行业的项目管理模式

圣迪

项目管理 敏捷 pmp 开发 瀑布

记录:今年最骄傲的一件事

将军-技术演讲力教练

python scrapy极细拆解,打开Spider类看内容,顺手爬了一下优设网

梦想橡皮擦

12月日更

架构实战营 第4期 模块一作业

架构实战营 模块一 「架构实战营」

Java jar 如何防止被反编译

xcbeyond

28天写作 12月日更

学习能力

Nydia

一对一沟通有必要吗?

Justin

沟通 28天写作

「架构实战营」模块一《为何架构设计能力难以提升》作业

DaiChen

作业 模块一 「架构实战营」

老大react说:schedule,我们今年的小目标是一个亿

全栈潇晨

React React Hooks

学生管理系统架构设计

tony

「架构实战营」

[Pulsar] Consumer如何消费消息

Zike Yang

Apache Pulsar 12月日更

Prometheus Exporter (二十一)Ceph Exporter

耳东@Erdong

Prometheus Ceph 28天写作 exporter 12月日更

跟老表学云服务器开发专栏导航

老表

Python 内容合集 签约计划第二季 技术专题合集 跟老表学云服务器

CouchDB是什么?为什么我们要关注它?_数据库_Leo Pryzbylski_InfoQ精选文章