1. 概述
本教程将介绍几种基础的MapReduce视图,并演示如何使用Couchbase Java SDK进行查询。
2. Maven依赖
在Maven项目中使用Couchbase,需在pom.xml中添加SDK依赖:
<dependency>
<groupId>com.couchbase.client</groupId>
<artifactId>java-client</artifactId>
<version>2.7.2</version>
</dependency>
最新版本可在Maven Central获取。
3. MapReduce视图
Couchbase中,MapReduce视图是一种索引类型,用于查询数据桶。它通过JavaScript的map函数和可选的reduce函数定义。
3.1. map函数
map函数对每个文档执行一次。视图创建时,会对桶中所有文档执行一次map函数,结果存储在桶中。
视图创建后,仅对新插入或更新的文档执行map函数以增量更新视图。
由于map函数结果存储在数据桶中,视图查询延迟极低。
示例:创建一个索引,匹配所有type为*"StudentGrade"且包含name*字段的文档:
function (doc, meta) {
if(doc.type == "StudentGrade" && doc.name) {
emit(doc.name, null);
}
}
✅ 关键点:
emit
函数指定索引键(第一个参数)和关联值(第二个参数)- 此例中仅将
name
存入索引键,值设为null
当三个文档的name为*"John Doe"时,索引键"John Doe"*会关联这三个文档。
3.2. reduce函数
reduce函数用于对map函数结果执行聚合计算。Couchbase管理界面提供内置函数:
_count
:计数_sum
:求和_stats
:统计
也可自定义reduce函数实现复杂聚合,后续将演示内置函数用法。
4. 视图与查询操作
4.1. 视图组织
视图按桶分组存储在设计文档中。理论上单个设计文档可包含无限视图,但建议:
- ⚠️ 性能优化:每个设计文档不超过10个视图
新创建的视图默认为开发视图,测试满意后需发布为生产视图。
4.2. 构建查询
通过设计文档名和视图名创建ViewQuery
对象:
ViewQuery query = ViewQuery.from("design-document-name", "view-name");
查询开发视图需添加development()
方法:
ViewQuery query
= ViewQuery.from("design-doc-name", "view-name").development();
4.3. 执行查询
使用ViewQuery
对象获取ViewResult
:
ViewResult result = bucket.query(query);
4.4. 处理结果
遍历ViewResult
获取文档ID和内容:
for(ViewRow row : result.allRows()) {
JsonDocument doc = row.document();
String id = doc.id();
String json = doc.content().toString();
}
5. 示例应用
后续将基于以下格式的学生成绩文档编写视图和查询(成绩范围0-100):
{
"type": "StudentGrade",
"name": "John Doe",
"course": "History",
"hours": 3,
"grade": 95
}
文档存储在*"baeldung-tutorial"桶,视图存于"studentGrades"*设计文档。连接桶的代码:
Bucket bucket = CouchbaseCluster.create("127.0.0.1")
.openBucket("baeldung-tutorial");
6. 精确匹配查询
6.1. 单键匹配
查找特定课程的成绩,创建*"findByCourse"*视图:
function (doc, meta) {
if(doc.type == "StudentGrade" && doc.course && doc.grade) {
emit(doc.course, null);
}
}
查询历史课程成绩:
ViewQuery query
= ViewQuery.from("studentGrades", "findByCourse").key("History");
6.2. 多键匹配
查找数学和科学课程成绩,使用keys()
方法:
ViewQuery query = ViewQuery
.from("studentGrades", "findByCourse")
.keys(JsonArray.from("Math", "Science"));
7. 范围查询
7.1. 单字段范围查询
创建*"findByGrade"*视图:
function (doc, meta) {
if(doc.type == "StudentGrade" && doc.grade) {
emit(doc.grade, null);
}
}
查询B级成绩(80-89分):
ViewQuery query = ViewQuery.from("studentGrades", "findByGrade")
.startKey(80)
.endKey(89)
.inclusiveEnd(true);
✅ 注意:
- 范围查询的
startKey
始终包含 - 若成绩均为整数,以下查询等效:
ViewQuery query = ViewQuery.from("studentGrades", "findByGrade") .startKey(80) .endKey(90) .inclusiveEnd(false);
查询A级成绩(≥90分):
ViewQuery query = ViewQuery
.from("studentGrades", "findByGrade")
.startKey(90);
查询不及格成绩(<60分):
ViewQuery query = ViewQuery
.from("studentGrades", "findByGrade")
.endKey(60)
.inclusiveEnd(false);
7.2. 多字段范围查询
创建*"findByCourseAndGrade"*视图,按课程和成绩排序:
function (doc, meta) {
if(doc.type == "StudentGrade" && doc.course && doc.grade) {
emit([doc.course, doc.grade], null);
}
}
索引键排序示例:
["History", 80]
["History", 90]
["Math", 82]
["Math", 97]
查询数学B级成绩(80-89分):
ViewQuery query = ViewQuery
.from("studentGrades", "findByCourseAndGrade")
.startKey(JsonArray.from("Math", 80))
.endKey(JsonArray.from("Math", 89))
.inclusiveEnd(true);
⚠️ 踩坑提示:
- 查询A级成绩需设置上限,否则会包含字典序大于"Math"的课程:
ViewQuery query = ViewQuery .from("studentGrades", "findByCourseAndGrade") .startKey(JsonArray.from("Math", 90)) .endKey(JsonArray.from("Math", 100));
查询数学不及格成绩需设置下限:
ViewQuery query = ViewQuery
.from("studentGrades", "findByCourseAndGrade")
.startKey(JsonArray.from("Math", 0))
.endKey(JsonArray.from("Math", 60))
.inclusiveEnd(false);
获取数学最高5个成绩(降序排序):
ViewQuery query = ViewQuery
.from("studentGrades", "findByCourseAndGrade")
.descending()
.startKey(JsonArray.from("Math", 100))
.endKey(JsonArray.from("Math", 0))
.inclusiveEnd(true)
.limit(5);
8. 聚合查询
8.1. 使用*count()*函数
创建*"countStudentsByCourse"*视图统计各课程学生数:
function (doc, meta) {
if(doc.type == "StudentGrade" && doc.course && doc.name) {
emit([doc.course, doc.name], null);
}
}
配置使用内置_count
函数。查询代码:
ViewQuery query = ViewQuery
.from("studentGrades", "countStudentsByCourse")
.reduce()
.groupLevel(1);
处理聚合结果:
ViewResult result = bucket.query(query);
Map<String, Long> numStudentsByCourse = new HashMap<>();
for(ViewRow row : result.allRows()) {
JsonArray keyArray = (JsonArray) row.key();
String course = keyArray.getString(0);
long count = Long.valueOf(row.value().toString());
numStudentsByCourse.put(course, count);
}
8.2. 使用*sum()*函数
创建*"sumCreditsByStudent"*视图统计学生总学分:
function (doc, meta) {
if(doc.type == "StudentGrade"
&& doc.name
&& doc.course
&& doc.hours) {
emit([doc.name, doc.course], doc.hours);
}
}
配置使用内置_sum
函数。查询代码:
ViewQuery query = ViewQuery
.from("studentGrades", "sumCreditsByStudent")
.reduce()
.groupLevel(1);
处理结果:
ViewResult result = bucket.query(query);
Map<String, Long> hoursByStudent = new HashMap<>();
for(ViewRow row : result.allRows()) {
String name = (String) row.key();
long sum = Long.valueOf(row.value().toString());
hoursByStudent.put(name, sum);
}
8.3. 计算平均绩点
结合两个视图计算GPA(A=4, B=3, C=2, D=1):
- 使用已有
sumCreditsByStudent
视图 - 创建*"sumGradePointsByStudent"*视图:
function (doc, meta) {
if(doc.type == "StudentGrade"
&& doc.name
&& doc.hours
&& doc.grade) {
if(doc.grade >= 90) {
emit(doc.name, 4*doc.hours);
}
else if(doc.grade >= 80) {
emit(doc.name, 3*doc.hours);
}
else if(doc.grade >= 70) {
emit(doc.name, 2*doc.hours);
}
else if(doc.grade >= 60) {
emit(doc.name, doc.hours);
}
else {
emit(doc.name, 0);
}
}
}
查询并处理结果:
ViewQuery query = ViewQuery.from(
"studentGrades",
"sumGradePointsByStudent")
.reduce()
.groupLevel(1);
ViewResult result = bucket.query(query);
Map<String, Long> gradePointsByStudent = new HashMap<>();
for(ViewRow row : result.allRows()) {
String course = (String) row.key();
long sum = Long.valueOf(row.value().toString());
gradePointsByStudent.put(course, sum);
}
计算GPA:
Map<String, Float> result = new HashMap<>();
for(Entry<String, Long> creditHoursEntry : hoursByStudent.entrySet()) {
String name = creditHoursEntry.getKey();
long totalHours = creditHoursEntry.getValue();
long totalGradePoints = gradePointsByStudent.get(name);
result.put(name, ((float) totalGradePoints / totalHours));
}
9. 总结
本文演示了:
- 编写基础MapReduce视图
- 构建与执行视图查询
- 处理查询结果
完整代码见GitHub项目。更多细节参考: