【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

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 )关注我们,并与我们的编辑和其他读者朋友交流。

公众号推荐:

跳进 AI 的奇妙世界,一起探索未来工作的新风貌!想要深入了解 AI 如何成为产业创新的新引擎?好奇哪些城市正成为 AI 人才的新磁场?《中国生成式 AI 开发者洞察 2024》由 InfoQ 研究中心精心打造,为你深度解锁生成式 AI 领域的最新开发者动态。无论你是资深研发者,还是对生成式 AI 充满好奇的新手,这份报告都是你不可错过的知识宝典。欢迎大家扫码关注「AI前线」公众号,回复「开发者洞察」领取。

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

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

关注

评论

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

LP流动性挖矿/算力系统开发源码搭建

Congge420

区块链 系统开发 云算力挖矿系统开发详解 云算力模式系统开发源码

知识拷问:工作站和服务器哪个更适合做CST电磁仿真?

思茂信息

仿真软件 cst cst使用教程 cst电磁仿真 cst仿真软件

Makefile常用命令详解

小万哥

c++ 程序员 面试 后端 makefile

软件测试/测试开发丨Python学习笔记之基本数据类型与操作

测试人

Python 软件测试 测试开发 数据类型

Python从0到1丨图像增强的顶帽运算和底帽运算

华为云开发者联盟

Python 人工智能 华为云 华为云开发者联盟 企业号 5 月 PK 榜

在研制带处理器的电子产品时,如何提高抗干扰能力和电磁兼容性?

华秋PCB

电磁 电路 处理器 控制器 抗干扰

火山引擎DataLeap:3步打造“指标管理”体系,幸福里数据中心是这么做的

字节跳动数据平台

大数据 字节跳动 数据管理 指标管理 数据研发

吉林省网络安全等级测评机构有哪些?在哪里?

行云管家

网络安全 等级保护 吉林

【源码分析】【netty】FastThreadLocal为什么快?

如果晴天

源码分析 Netty 多线程 并发 netty

开源7天Github斩获4.5万Stars!阿里2023版高并发设计实录鲨疯了

做梦都在改BUG

Java 架构 微服务 系统设计 高并发

NFT元宇宙链游系统开发逻辑分析

Congge420

区块链 NFT链游 元宇宙链游

IPP Swap孵化器/LP挖矿系统开发方案

Congge420

区块链 ipfs挖矿用什么app

ChatGPT系统开发AI人功智能方案

Congge420

AI Gallery ChatGPT 人工智能ChatGPT 吗?

城市开发者平台:程序员成长和创新的“家园”

华为云开发者联盟

云计算 华为云 华为云开发者联盟 企业号 5 月 PK 榜

火山引擎DataTester分享:A/B实验中常见的8个错误

字节跳动数据平台

行业分析| 快对讲-融合会议的应用

anyRTC开发者

音视频 视频会议 快对讲 融合会议 电话会议

一文掌握 Go 并发模式 Context 上下文

陈明勇

Go golang 后端 Context 上下文

AE插件 真实地图路径绘制GEOlayers 3 Mac v1.5.7

真大的脸盆

Mac Mac 软件 AE插件 地图路径插件

DevOps 的道术法器,探寻 DevOps “立体化”实践之旅

CODING DevOps

DevOps 软件工程 CODING DevOps

车载手势识别技术:未来交通的革命性解决方案

来自四九城儿

教你使用Feign替换RestTemplate

做梦都在改BUG

融云跨平台 SDK 自动生成技术的探索和实践

融云 RongCloud

sdk 融云 办公效率 通讯 图片资源

量化交易系统开发——现货策略

薇電13242772558

量化策略

SaaS 产品如何选择设计协作工具?

CODING DevOps

敏捷 设计 SaaS CoDesign 团队协作工具

如何用一行代码实现监测 OpenAI,大幅提升使用体验

Yestodorrow

可观测性 用户体验 应用性能 ChatGPT

数字城市发展下的技术趋势,你了解多少?

没有用户名丶

常用的表格检测识别方法-表格区域检测方法(上)

合合技术团队

人工智能 深度学习 文字识别 表格识别 表格检测

LeetCode题解:136. 只出现一次的数字,排序后搜索,JavaScript,详细注释

Lee Chen

LeetCode

部分等保政策相关专业术语英文翻译汇总

行云管家

等保 等级保护 等保2.0

厚积薄发|迭代为什么叫冲刺?

CODING DevOps

DevOps 敏捷 软件工程

架构实战-毕业设计

程序员小张

「架构实战营」

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