BAEL-203: Querying Couchbase with MapReduce Views
This commit is contained in:
parent
99e75746f3
commit
e371bb9093
|
@ -0,0 +1,6 @@
|
|||
package com.baeldung.couchbase.mapreduce;
|
||||
|
||||
public interface CouchbaseKeyGenerator<T> {
|
||||
|
||||
String generateKey(T t);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.baeldung.couchbase.mapreduce;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class DuplicateKeyException extends Exception {
|
||||
|
||||
public DuplicateKeyException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.baeldung.couchbase.mapreduce;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class RandomUUIDGenerator<T> implements CouchbaseKeyGenerator<T> {
|
||||
|
||||
@Override
|
||||
public String generateKey(T t) {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package com.baeldung.couchbase.mapreduce;
|
||||
|
||||
public class StudentGrade {
|
||||
|
||||
private String name;
|
||||
private String course;
|
||||
private Integer grade;
|
||||
private Integer hours;
|
||||
|
||||
public StudentGrade() { }
|
||||
|
||||
public StudentGrade(String name, String course, Integer grade, Integer hours) {
|
||||
this.name = name;
|
||||
this.course = course;
|
||||
this.grade = grade;
|
||||
this.hours = hours;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCourse() {
|
||||
return course;
|
||||
}
|
||||
|
||||
public void setCourse(String course) {
|
||||
this.course = course;
|
||||
}
|
||||
|
||||
public Integer getGrade() {
|
||||
return grade;
|
||||
}
|
||||
|
||||
public void setGrade(Integer grade) {
|
||||
this.grade = grade;
|
||||
}
|
||||
|
||||
public Integer getHours() {
|
||||
return hours;
|
||||
}
|
||||
|
||||
public void setHours(Integer hours) {
|
||||
this.hours = hours;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.baeldung.couchbase.mapreduce;
|
||||
|
||||
public class StudentGradeKeyGenerator implements CouchbaseKeyGenerator<StudentGrade> {
|
||||
|
||||
@Override
|
||||
public String generateKey(StudentGrade g) {
|
||||
return g.getName() + ":" + g.getCourse();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package com.baeldung.couchbase.mapreduce;
|
||||
|
||||
import com.couchbase.client.deps.com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.couchbase.client.java.document.json.JsonArray;
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
|
||||
public class StudentGradeQueryBuilder {
|
||||
|
||||
final ObjectMapper om = new ObjectMapper();
|
||||
|
||||
public ViewQuery findAll() {
|
||||
return ViewQuery.from("studentGrades", "findByCourse");
|
||||
}
|
||||
|
||||
public ViewQuery findByCourse(String course) {
|
||||
return ViewQuery.from("studentGrades", "findByCourse")
|
||||
.key(course);
|
||||
}
|
||||
|
||||
public ViewQuery findByCourses(String... courses) {
|
||||
return ViewQuery.from("studentGrades", "findByCourse")
|
||||
.keys(JsonArray.from(courses));
|
||||
}
|
||||
|
||||
public ViewQuery findByGradeInRange(int lower, int upper, boolean inclusiveEnd) {
|
||||
return ViewQuery.from("studentGrades", "findByGrade")
|
||||
.startKey(lower)
|
||||
.endKey(upper)
|
||||
.inclusiveEnd(inclusiveEnd);
|
||||
}
|
||||
|
||||
public ViewQuery findByGradeLessThan(int upper) {
|
||||
return ViewQuery.from("studentGrades", "findByGrade")
|
||||
.endKey(upper)
|
||||
.inclusiveEnd(false);
|
||||
}
|
||||
|
||||
public ViewQuery findByGradeGreaterThan(int lower) {
|
||||
return ViewQuery.from("studentGrades", "findByGrade")
|
||||
.startKey(lower);
|
||||
}
|
||||
|
||||
public ViewQuery findByCourseAndGradeInRange(String course, int minGrade, int maxGrade, boolean inclusiveEnd) {
|
||||
return ViewQuery.from("studentGrades", "findByCourseAndGrade")
|
||||
.startKey(JsonArray.from(course, minGrade))
|
||||
.endKey(JsonArray.from(course, maxGrade))
|
||||
.inclusiveEnd(inclusiveEnd);
|
||||
}
|
||||
|
||||
public ViewQuery findTopGradesByCourse(String course, int limit) {
|
||||
return ViewQuery.from("studentGrades", "findByCourseAndGrade")
|
||||
.startKey(JsonArray.from(course, 100))
|
||||
.endKey(JsonArray.from(course, 0))
|
||||
.inclusiveEnd(true)
|
||||
.descending()
|
||||
.limit(limit);
|
||||
}
|
||||
|
||||
public ViewQuery countStudentsByCourse() {
|
||||
return ViewQuery.from("studentGrades", "countStudentsByCourse")
|
||||
.reduce()
|
||||
.groupLevel(1);
|
||||
}
|
||||
|
||||
public ViewQuery sumCreditsByStudent() {
|
||||
return ViewQuery.from("studentGrades", "sumCreditsByStudent")
|
||||
.reduce()
|
||||
.groupLevel(1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
package com.baeldung.couchbase.mapreduce;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.couchbase.client.deps.com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.CouchbaseCluster;
|
||||
import com.couchbase.client.java.document.JsonDocument;
|
||||
import com.couchbase.client.java.document.json.JsonArray;
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
import com.couchbase.client.java.view.ViewResult;
|
||||
import com.couchbase.client.java.view.ViewRow;
|
||||
|
||||
public class StudentGradeService {
|
||||
|
||||
final CouchbaseKeyGenerator<StudentGrade> keyGenerator;
|
||||
final CouchbaseCluster cluster;
|
||||
final Bucket bucket;
|
||||
final ObjectMapper om = new ObjectMapper();
|
||||
final StudentGradeQueryBuilder queryBuilder;
|
||||
|
||||
public StudentGradeService(CouchbaseKeyGenerator<StudentGrade> keyGenerator) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.queryBuilder = new StudentGradeQueryBuilder();
|
||||
cluster = CouchbaseCluster.create("127.0.0.1");
|
||||
bucket = cluster.openBucket("baeldung-tutorial");
|
||||
}
|
||||
|
||||
public String insert(StudentGrade studentGrade) throws DuplicateKeyException {
|
||||
String id = keyGenerator.generateKey(studentGrade);
|
||||
if(bucket.exists(id)) {
|
||||
throw new DuplicateKeyException("document already exists with key " + id);
|
||||
}
|
||||
JsonObject content = JsonObject.empty()
|
||||
.put("type", "StudentGrade")
|
||||
.put("name", studentGrade.getName())
|
||||
.put("course", studentGrade.getCourse())
|
||||
.put("grade", studentGrade.getGrade())
|
||||
.put("hours", studentGrade.getHours());
|
||||
JsonDocument doc = JsonDocument.create(id, content);
|
||||
bucket.insert(doc);
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<JsonDocument> findAll() {
|
||||
ViewQuery query = queryBuilder.findAll();
|
||||
ViewResult result = bucket.query(query);
|
||||
return extractDocuments(result);
|
||||
}
|
||||
|
||||
private List<JsonDocument> extractDocuments(ViewResult result) {
|
||||
List<JsonDocument> docs = new ArrayList<>();
|
||||
for(ViewRow row : result.allRows()) {
|
||||
JsonDocument doc = row.document();
|
||||
docs.add(doc);
|
||||
}
|
||||
return docs;
|
||||
}
|
||||
|
||||
public List<JsonDocument> findByCourse(String course) {
|
||||
ViewQuery query = queryBuilder.findByCourse(course);
|
||||
ViewResult result = bucket.query(query);
|
||||
return extractDocuments(result);
|
||||
}
|
||||
|
||||
public List<JsonDocument> findByCourses(String... courses) {
|
||||
ViewQuery query = queryBuilder.findByCourses(courses);
|
||||
ViewResult result = bucket.query(query);
|
||||
return extractDocuments(result);
|
||||
}
|
||||
|
||||
public List<JsonDocument> findByGradeInRange(int lower, int upper, boolean inclusiveEnd) {
|
||||
ViewQuery query = queryBuilder.findByGradeInRange(lower, upper, inclusiveEnd);
|
||||
ViewResult result = bucket.query(query);
|
||||
return extractDocuments(result);
|
||||
}
|
||||
|
||||
public List<JsonDocument> findByGradeLessThan(int upper) {
|
||||
ViewQuery query = queryBuilder.findByGradeLessThan(upper);
|
||||
ViewResult result = bucket.query(query);
|
||||
return extractDocuments(result);
|
||||
}
|
||||
|
||||
public List<JsonDocument> findByGradeGreaterThan(int lower) {
|
||||
ViewQuery query = queryBuilder.findByGradeGreaterThan(lower);
|
||||
ViewResult result = bucket.query(query);
|
||||
return extractDocuments(result);
|
||||
}
|
||||
|
||||
public List<JsonDocument> findByCourseAndGradeInRange(String course, int minGrade, int maxGrade, boolean inclusiveEnd) {
|
||||
ViewQuery query = queryBuilder.findByCourseAndGradeInRange(course, minGrade, maxGrade, inclusiveEnd);
|
||||
ViewResult result = bucket.query(query);
|
||||
return extractDocuments(result);
|
||||
}
|
||||
|
||||
public List<JsonDocument> findTopGradesByCourse(String course, int limit) {
|
||||
ViewQuery query = queryBuilder.findTopGradesByCourse(course, limit);
|
||||
ViewResult result = bucket.query(query);
|
||||
return extractDocuments(result);
|
||||
}
|
||||
|
||||
public Map<String, Long> countStudentsByCourse() {
|
||||
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);
|
||||
}
|
||||
|
||||
return numStudentsByCourse;
|
||||
}
|
||||
|
||||
public Map<String, Long> sumCreditHoursByStudent() {
|
||||
ViewQuery query = ViewQuery.from("studentGrades", "sumHoursByStudent")
|
||||
.reduce()
|
||||
.groupLevel(1);
|
||||
ViewResult result = bucket.query(query);
|
||||
|
||||
Map<String, Long> creditHoursByStudent = new HashMap<>();
|
||||
for(ViewRow row : result.allRows()) {
|
||||
String course = (String) row.key();
|
||||
long sum = Long.valueOf(row.value().toString());
|
||||
creditHoursByStudent.put(course, sum);
|
||||
}
|
||||
|
||||
return creditHoursByStudent;
|
||||
}
|
||||
|
||||
public Map<String, Long> sumGradePointsByStudent() {
|
||||
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);
|
||||
}
|
||||
|
||||
return gradePointsByStudent;
|
||||
}
|
||||
|
||||
public Map<String, Float> calculateGpaByStudent() {
|
||||
Map<String, Long> creditHoursByStudent = sumCreditHoursByStudent();
|
||||
Map<String, Long> gradePointsByStudent = sumGradePointsByStudent();
|
||||
|
||||
Map<String, Float> result = new HashMap<>();
|
||||
for(Entry<String, Long> creditHoursEntry : creditHoursByStudent.entrySet()) {
|
||||
String name = creditHoursEntry.getKey();
|
||||
long totalHours = creditHoursEntry.getValue();
|
||||
long totalGradePoints = gradePointsByStudent.get(name);
|
||||
result.put(name, ((float) totalGradePoints / totalHours));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package com.baeldung.couchbase.mapreduce;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.couchbase.client.java.document.JsonDocument;
|
||||
import com.couchbase.client.java.view.ViewResult;
|
||||
import com.couchbase.client.java.view.ViewRow;
|
||||
|
||||
public class StudentGradeServiceIntegrationTest {
|
||||
private static final Logger logger = LoggerFactory.getLogger(StudentGradeServiceIntegrationTest.class);
|
||||
|
||||
static StudentGradeService studentGradeService;
|
||||
static Set<String> gradeIds = new HashSet<>();
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpBeforeClass() throws Exception {
|
||||
studentGradeService = new StudentGradeService(new StudentGradeKeyGenerator());
|
||||
insertStudentGrade(new StudentGrade("John Doe", "History", 80, 3));
|
||||
insertStudentGrade(new StudentGrade("Jane Doe", "History", 89, 3));
|
||||
insertStudentGrade(new StudentGrade("Bob Smith", "History", 90, 3));
|
||||
insertStudentGrade(new StudentGrade("Mary Jones", "History", 92, 3));
|
||||
insertStudentGrade(new StudentGrade("Jane Doe", "Math", 59, 3));
|
||||
insertStudentGrade(new StudentGrade("Bob Smith", "Math", 91, 3));
|
||||
insertStudentGrade(new StudentGrade("Mary Jones", "Math", 86, 3));
|
||||
insertStudentGrade(new StudentGrade("John Doe", "Science", 85, 4));
|
||||
insertStudentGrade(new StudentGrade("Bob Smith", "Science", 97, 4));
|
||||
insertStudentGrade(new StudentGrade("Mary Jones", "Science", 84, 4));
|
||||
}
|
||||
|
||||
private static void insertStudentGrade(StudentGrade studentGrade) {
|
||||
try {
|
||||
String id = studentGradeService.insert(studentGrade);
|
||||
gradeIds.add(id);
|
||||
} catch (DuplicateKeyException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenFindAll_thenSuccess() {
|
||||
List<JsonDocument> docs = studentGradeService.findAll();
|
||||
printDocuments(docs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenFindByCourse_thenSuccess() {
|
||||
List<JsonDocument> docs = studentGradeService.findByCourse("History");
|
||||
printDocuments(docs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenFindByCourses_thenSuccess() {
|
||||
List<JsonDocument> docs = studentGradeService.findByCourses("History", "Science");
|
||||
printDocuments(docs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenFindByGradeInRange_thenSuccess() {
|
||||
List<JsonDocument> docs = studentGradeService.findByGradeInRange(80, 89, true);
|
||||
printDocuments(docs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenFindByGradeLessThan_thenSuccess() {
|
||||
List<JsonDocument> docs = studentGradeService.findByGradeLessThan(60);
|
||||
printDocuments(docs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenFindByGradeGreaterThan_thenSuccess() {
|
||||
List<JsonDocument> docs = studentGradeService.findByGradeGreaterThan(90);
|
||||
printDocuments(docs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenFindByCourseAndGradeInRange_thenSuccess() {
|
||||
List<JsonDocument> docs = studentGradeService.findByCourseAndGradeInRange("Math", 80, 89, true);
|
||||
printDocuments(docs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenFindTopGradesByCourse_thenSuccess() {
|
||||
List<JsonDocument> docs = studentGradeService.findTopGradesByCourse("Science", 2);
|
||||
printDocuments(docs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenCountStudentsByCourse_thenSuccess() {
|
||||
Map<String, Long> map = studentGradeService.countStudentsByCourse();
|
||||
printMap(map);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenSumCreditHoursByStudent_thenSuccess() {
|
||||
Map<String, Long> map = studentGradeService.sumCreditHoursByStudent();
|
||||
printMap(map);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenSumGradePointsByStudent_thenSuccess() {
|
||||
Map<String, Long> map = studentGradeService.sumGradePointsByStudent();
|
||||
printMap(map);
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void whenCalculateGpaByStudent_thenSuccess() {
|
||||
Map<String, Float> map = studentGradeService.calculateGpaByStudent();
|
||||
printGpaMap(map);
|
||||
}
|
||||
|
||||
private void printMap(Map<String, Long> map) {
|
||||
for(Map.Entry<String, Long> entry : map.entrySet()) {
|
||||
logger.info(entry.getKey() + "=" + entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void printGpaMap(Map<String, Float> map) {
|
||||
for(Map.Entry<String, Float> entry : map.entrySet()) {
|
||||
logger.info(entry.getKey() + "=" + entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void printDocuments(List<JsonDocument> docs) {
|
||||
for(JsonDocument doc : docs) {
|
||||
String key = doc.id();
|
||||
logger.info(key + " = " + doc.content().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void printViewResultDocuments(ViewResult result) {
|
||||
for(ViewRow row : result.allRows()) {
|
||||
JsonDocument doc = row.document();
|
||||
String key = doc.id();
|
||||
logger.info(key + "=" + doc.content().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void printViewResultRows(ViewResult result) {
|
||||
for(ViewRow row : result.allRows()) {
|
||||
logger.info(row.toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>web - %date [%thread] %-5level %logger{36} - %message%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework" level="WARN" />
|
||||
<logger name="com.baeldung" level="DEBUG" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
Loading…
Reference in New Issue