From 25f454688117455174dc27a64c092d55375401e5 Mon Sep 17 00:00:00 2001 From: Kevin Gilmore Date: Fri, 8 Apr 2016 06:37:06 -0500 Subject: [PATCH] Spring Data Couchbase Article 2 --- spring-data-couchbase-2/.classpath | 2 +- .../data/couchbase/MyCouchbaseConfig.java | 19 ++ .../spring/data/couchbase/model/Student.java | 113 ++++++++++++ .../repos/CustomStudentRepository.java | 9 + .../repos/CustomStudentRepositoryImpl.java | 25 +++ .../couchbase/repos/StudentRepository.java | 11 ++ .../service/StudentRepositoryService.java | 58 ++++++ .../couchbase/service/StudentService.java | 22 +++ .../service/StudentTemplateService.java | 55 ++++++ .../data/couchbase/TestCouchbaseConfig.java | 6 + .../service/StudentRepositoryServiceTest.java | 13 ++ .../couchbase/service/StudentServiceTest.java | 166 ++++++++++++++++++ .../service/StudentTemplateServiceTest.java | 13 ++ 13 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/model/Student.java create mode 100644 spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/CustomStudentRepository.java create mode 100644 spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/CustomStudentRepositoryImpl.java create mode 100644 spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/StudentRepository.java create mode 100644 spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentRepositoryService.java create mode 100644 spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentService.java create mode 100644 spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentTemplateService.java create mode 100644 spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentRepositoryServiceTest.java create mode 100644 spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentServiceTest.java create mode 100644 spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentTemplateServiceTest.java diff --git a/spring-data-couchbase-2/.classpath b/spring-data-couchbase-2/.classpath index f4c34adf1f..679a31b6da 100644 --- a/spring-data-couchbase-2/.classpath +++ b/spring-data-couchbase-2/.classpath @@ -4,7 +4,7 @@ - + diff --git a/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/MyCouchbaseConfig.java b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/MyCouchbaseConfig.java index 5c0b1a93ca..8e6b971dbf 100644 --- a/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/MyCouchbaseConfig.java +++ b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/MyCouchbaseConfig.java @@ -3,9 +3,13 @@ package org.baeldung.spring.data.couchbase; import java.util.Arrays; import java.util.List; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +import org.springframework.data.couchbase.core.mapping.event.ValidatingCouchbaseEventListener; +import org.springframework.data.couchbase.core.query.Consistency; import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; @Configuration @EnableCouchbaseRepositories(basePackages={"org.baeldung.spring.data.couchbase"}) @@ -29,4 +33,19 @@ public class MyCouchbaseConfig extends AbstractCouchbaseConfiguration { protected String getBucketPassword() { return BUCKET_PASSWORD; } + + @Override + protected Consistency getDefaultConsistency() { + return Consistency.READ_YOUR_OWN_WRITES; + } + + @Bean + public LocalValidatorFactoryBean localValidatorFactoryBean() { + return new LocalValidatorFactoryBean(); + } + + @Bean + public ValidatingCouchbaseEventListener validatingCouchbaseEventListener() { + return new ValidatingCouchbaseEventListener(localValidatorFactoryBean()); + } } diff --git a/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/model/Student.java b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/model/Student.java new file mode 100644 index 0000000000..9c266c2c62 --- /dev/null +++ b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/model/Student.java @@ -0,0 +1,113 @@ +package org.baeldung.spring.data.couchbase.model; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Past; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +import org.joda.time.DateTime; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Version; +import org.springframework.data.couchbase.core.mapping.Document; + +import com.couchbase.client.java.repository.annotation.Field; + +@Document +public class Student { + private static final String NAME_REGEX = "^[a-zA-Z .'-]+$"; + + @Id + private String id; + @Field + @NotNull + @Size(min=1, max=20) + @Pattern(regexp=NAME_REGEX) + private String firstName; + @Field + @NotNull + @Size(min=1, max=20) + @Pattern(regexp=NAME_REGEX) + private String lastName; + @Field + @Past + private DateTime dateOfBirth; + @Field + @NotNull + private DateTime created; + @Field + private DateTime updated; + @Version + private long version; + + public Student() {} + + public Student(String id, String firstName, String lastName, DateTime dateOfBirth) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.dateOfBirth = dateOfBirth; + } + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getFirstName() { + return firstName; + } + public void setFirstName(String firstName) { + this.firstName = firstName; + } + public String getLastName() { + return lastName; + } + public void setLastName(String lastName) { + this.lastName = lastName; + } + public DateTime getDateOfBirth() { + return dateOfBirth; + } + public void setDateOfBirth(DateTime dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } + public DateTime getCreated() { + return created; + } + public void setCreated(DateTime created) { + this.created = created; + } + public DateTime getUpdated() { + return updated; + } + public void setUpdated(DateTime updated) { + this.updated = updated; + } + + @Override + public int hashCode() { + int hash = 1; + if(id != null) { + hash = hash * 31 + id.hashCode(); + } + if(firstName != null) { + hash = hash * 31 + firstName.hashCode(); + } + if(lastName != null) { + hash = hash * 31 + lastName.hashCode(); + } + if(dateOfBirth != null) { + hash = hash * 31 + dateOfBirth.hashCode(); + } + return hash; + } + + @Override + public boolean equals(Object obj) { + if((obj == null) || (obj.getClass() != this.getClass())) return false; + if(obj == this) return true; + Student other = (Student) obj; + return this.hashCode() == other.hashCode(); + } +} \ No newline at end of file diff --git a/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/CustomStudentRepository.java b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/CustomStudentRepository.java new file mode 100644 index 0000000000..9a5bf21492 --- /dev/null +++ b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/CustomStudentRepository.java @@ -0,0 +1,9 @@ +package org.baeldung.spring.data.couchbase.repos; + +import java.util.List; + +import org.baeldung.spring.data.couchbase.model.Student; + +public interface CustomStudentRepository { + List findByFirstNameStartsWith(String s); +} diff --git a/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/CustomStudentRepositoryImpl.java b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/CustomStudentRepositoryImpl.java new file mode 100644 index 0000000000..66b672a4fd --- /dev/null +++ b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/CustomStudentRepositoryImpl.java @@ -0,0 +1,25 @@ +package org.baeldung.spring.data.couchbase.repos; + +import java.util.List; + +import org.baeldung.spring.data.couchbase.model.Student; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.couchbase.core.CouchbaseTemplate; + +import com.couchbase.client.java.view.Stale; +import com.couchbase.client.java.view.ViewQuery; + +public class CustomStudentRepositoryImpl implements CustomStudentRepository { + + private static final String DESIGN_DOC = "student"; + + @Autowired + private CouchbaseTemplate template; + + public List findByFirstNameStartsWith(String s) { + return template.findByView(ViewQuery.from(DESIGN_DOC, "byFirstName") + .startKey(s) + .stale(Stale.FALSE), + Student.class); + } +} diff --git a/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/StudentRepository.java b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/StudentRepository.java new file mode 100644 index 0000000000..331ddc553d --- /dev/null +++ b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/repos/StudentRepository.java @@ -0,0 +1,11 @@ +package org.baeldung.spring.data.couchbase.repos; + +import java.util.List; + +import org.baeldung.spring.data.couchbase.model.Student; +import org.springframework.data.repository.CrudRepository; + +public interface StudentRepository extends CrudRepository, CustomStudentRepository { + List findByFirstName(String firstName); + List findByLastName(String lastName); +} diff --git a/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentRepositoryService.java b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentRepositoryService.java new file mode 100644 index 0000000000..58304afc1c --- /dev/null +++ b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentRepositoryService.java @@ -0,0 +1,58 @@ +package org.baeldung.spring.data.couchbase.service; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.baeldung.spring.data.couchbase.model.Student; +import org.baeldung.spring.data.couchbase.repos.StudentRepository; +import org.joda.time.DateTime; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +@Service +@Qualifier("StudentRepositoryService") +public class StudentRepositoryService implements StudentService { + + private StudentRepository repo; + @Autowired + public void setStudentRepository(StudentRepository repo) { + this.repo = repo; + } + + public Student findOne(String id) { + return repo.findOne(id); + } + + public List findAll() { + List people = new ArrayList(); + Iterator it = repo.findAll().iterator(); + while(it.hasNext()) { + people.add(it.next()); + } + return people; + } + + public List findByFirstName(String firstName) { + return repo.findByFirstName(firstName); + } + + public List findByLastName(String lastName) { + return repo.findByLastName(lastName); + } + + public void create(Student student) { + student.setCreated(DateTime.now()); + repo.save(student); + } + + public void update(Student student) { + student.setUpdated(DateTime.now()); + repo.save(student); + } + + public void delete(Student student) { + repo.delete(student); + } +} diff --git a/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentService.java b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentService.java new file mode 100644 index 0000000000..f483ef0fb6 --- /dev/null +++ b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentService.java @@ -0,0 +1,22 @@ +package org.baeldung.spring.data.couchbase.service; + +import java.util.List; + +import org.baeldung.spring.data.couchbase.model.Student; + +public interface StudentService { + + Student findOne(String id); + + List findAll(); + + List findByFirstName(String firstName); + + List findByLastName(String lastName); + + void create(Student student); + + void update(Student student); + + void delete(Student student); +} diff --git a/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentTemplateService.java b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentTemplateService.java new file mode 100644 index 0000000000..c3808e0015 --- /dev/null +++ b/spring-data-couchbase-2/src/main/java/org/baeldung/spring/data/couchbase/service/StudentTemplateService.java @@ -0,0 +1,55 @@ +package org.baeldung.spring.data.couchbase.service; + +import java.util.List; + +import org.baeldung.spring.data.couchbase.model.Student; +import org.joda.time.DateTime; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.stereotype.Service; + +import com.couchbase.client.java.view.ViewQuery; + +@Service +@Qualifier("StudentTemplateService") +public class StudentTemplateService implements StudentService { + + private static final String DESIGN_DOC = "student"; + + private CouchbaseTemplate template; + @Autowired + public void setCouchbaseTemplate(CouchbaseTemplate template) { + this.template = template; + } + + public Student findOne(String id) { + return template.findById(id, Student.class); + } + + public List findAll() { + return template.findByView(ViewQuery.from(DESIGN_DOC, "all"), Student.class); + } + + public List findByFirstName(String firstName) { + return template.findByView(ViewQuery.from(DESIGN_DOC, "byFirstName"), Student.class); + } + + public List findByLastName(String lastName) { + return template.findByView(ViewQuery.from(DESIGN_DOC, "byLastName"), Student.class); + } + + public void create(Student student) { + student.setCreated(DateTime.now()); + template.insert(student); + } + + public void update(Student student) { + student.setUpdated(DateTime.now()); + template.update(student); + } + + public void delete(Student student) { + template.remove(student); + } +} diff --git a/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/TestCouchbaseConfig.java b/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/TestCouchbaseConfig.java index 4edfb165bf..c298ef0a61 100644 --- a/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/TestCouchbaseConfig.java +++ b/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/TestCouchbaseConfig.java @@ -1,6 +1,7 @@ package org.baeldung.spring.data.couchbase; import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +import org.springframework.data.couchbase.core.query.Consistency; public class TestCouchbaseConfig extends MyCouchbaseConfig { @@ -8,4 +9,9 @@ public class TestCouchbaseConfig extends MyCouchbaseConfig { public String typeKey() { return MappingCouchbaseConverter.TYPEKEY_SYNCGATEWAY_COMPATIBLE; } + + @Override + public Consistency getDefaultConsistency() { + return Consistency.READ_YOUR_OWN_WRITES; + } } diff --git a/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentRepositoryServiceTest.java b/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentRepositoryServiceTest.java new file mode 100644 index 0000000000..040453fd73 --- /dev/null +++ b/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentRepositoryServiceTest.java @@ -0,0 +1,13 @@ +package org.baeldung.spring.data.couchbase.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +public class StudentRepositoryServiceTest extends StudentServiceTest { + + @Autowired + @Qualifier("StudentRepositoryService") + public void setStudentService(StudentService service) { + this.studentService = service; + } +} diff --git a/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentServiceTest.java b/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentServiceTest.java new file mode 100644 index 0000000000..79948cbedf --- /dev/null +++ b/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentServiceTest.java @@ -0,0 +1,166 @@ +package org.baeldung.spring.data.couchbase.service; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import javax.validation.ConstraintViolationException; + +import org.baeldung.spring.data.couchbase.IntegrationTest; +import org.baeldung.spring.data.couchbase.MyCouchbaseConfig; +import org.baeldung.spring.data.couchbase.model.Student; +import org.joda.time.DateTime; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.Cluster; +import com.couchbase.client.java.CouchbaseCluster; +import com.couchbase.client.java.document.JsonDocument; +import com.couchbase.client.java.document.json.JsonObject; + +public abstract class StudentServiceTest extends IntegrationTest { + + static final String typeField = "_class"; + static final String joe = "Joe"; + static final String college = "College"; + static final String joeCollegeId = "student:" + joe + ":" + college; + static final DateTime joeCollegeDob = DateTime.now().minusYears(21); + static final Student joeCollege = new Student(joeCollegeId, joe, college, joeCollegeDob); + static final JsonObject jsonJoeCollege = JsonObject.empty() + .put(typeField, Student.class.getName()) + .put("firstName", joe) + .put("lastName", college) + .put("created", DateTime.now().getMillis()) + .put("version", 1); + + static final String judy = "Judy"; + static final String jetson = "Jetson"; + static final String judyJetsonId = "student:" + judy + ":" + jetson; + static final DateTime judyJetsonDob = DateTime.now().minusYears(19).minusMonths(5).minusDays(3); + static final Student judyJetson = new Student(judyJetsonId, judy, jetson, judyJetsonDob); + static final JsonObject jsonJudyJetson = JsonObject.empty() + .put(typeField, Student.class.getName()) + .put("firstName", judy) + .put("lastName", jetson) + .put("created", DateTime.now().getMillis()) + .put("version", 1); + + StudentService studentService; + + @BeforeClass + public static void setupBeforeClass() { + Cluster cluster = CouchbaseCluster.create(MyCouchbaseConfig.NODE_LIST); + Bucket bucket = cluster.openBucket(MyCouchbaseConfig.BUCKET_NAME, MyCouchbaseConfig.BUCKET_PASSWORD); + bucket.upsert(JsonDocument.create(joeCollegeId, jsonJoeCollege)); + bucket.upsert(JsonDocument.create(judyJetsonId, jsonJudyJetson)); + bucket.close(); + cluster.disconnect(); + } + + @Test + public void whenCreatingStudent_thenDocumentIsPersisted() { + String firstName = "Eric"; + String lastName = "Stratton"; + DateTime dateOfBirth = DateTime.now().minusYears(25); + String id = "student:" + firstName + ":" + lastName; + Student expectedStudent = new Student(id, firstName, lastName, dateOfBirth); + studentService.create(expectedStudent); + Student actualStudent = studentService.findOne(id); + assertNotNull(actualStudent.getCreated()); + assertNotNull(actualStudent); + assertEquals(expectedStudent.getId(), actualStudent.getId()); + } + + @Test(expected=ConstraintViolationException.class) + public void whenCreatingStudentWithInvalidFirstName_thenConstraintViolationException() { + String firstName = "Er+ic"; + String lastName = "Stratton"; + DateTime dateOfBirth = DateTime.now().minusYears(25); + String id = "student:" + firstName + ":" + lastName; + Student student = new Student(id, firstName, lastName, dateOfBirth); + studentService.create(student); + } + + @Test(expected=ConstraintViolationException.class) + public void whenCreatingStudentWithFutureDob_thenConstraintViolationException() { + String firstName = "Jane"; + String lastName = "Doe"; + DateTime dateOfBirth = DateTime.now().plusDays(1); + String id = "student:" + firstName + ":" + lastName; + Student student = new Student(id, firstName, lastName, dateOfBirth); + studentService.create(student); + } + + @Test + public void whenFindingStudentByJohnSmithId_thenReturnsJohnSmith() { + Student actualStudent = studentService.findOne(joeCollegeId); + assertNotNull(actualStudent); + assertNotNull(actualStudent.getCreated()); + assertEquals(joeCollegeId, actualStudent.getId()); + } + + @Test + public void whenFindingAllStudents_thenReturnsTwoOrMoreStudentsIncludingJoeCollegeAndJudyJetson() { + List resultList = studentService.findAll(); + assertNotNull(resultList); + assertFalse(resultList.isEmpty()); + assertTrue(resultContains(resultList, joeCollege)); + assertTrue(resultContains(resultList, judyJetson)); + assertTrue(resultList.size() >= 2); + } + + @Test + public void whenFindingByFirstNameJohn_thenReturnsOnlyStudentsNamedJohn() { + String expectedFirstName = joe; + List resultList = studentService.findByFirstName(expectedFirstName); + assertNotNull(resultList); + assertFalse(resultList.isEmpty()); + assertTrue(allResultsContainExpectedFirstName(resultList, expectedFirstName)); + } + + @Test + public void whenFindingByLastNameSmith_thenReturnsOnlyStudentsNamedSmith() { + String expectedLastName = college; + List resultList = studentService.findByLastName(expectedLastName); + assertNotNull(resultList); + assertFalse(resultList.isEmpty()); + assertTrue(allResultsContainExpectedLastName(resultList, expectedLastName)); + } + + private boolean resultContains(List resultList, Student student) { + boolean found = false; + for(Student p : resultList) { + if(p.getId().equals(student.getId())) { + found = true; + break; + } + } + return found; + } + + private boolean allResultsContainExpectedFirstName(List resultList, String firstName) { + boolean found = false; + for(Student p : resultList) { + if(p.getFirstName().equals(firstName)) { + found = true; + break; + } + } + return found; + } + + private boolean allResultsContainExpectedLastName(List resultList, String lastName) { + boolean found = false; + for(Student p : resultList) { + if(p.getLastName().equals(lastName)) { + found = true; + break; + } + } + return found; + } +} diff --git a/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentTemplateServiceTest.java b/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentTemplateServiceTest.java new file mode 100644 index 0000000000..dd5be8e059 --- /dev/null +++ b/spring-data-couchbase-2/src/test/java/org/baeldung/spring/data/couchbase/service/StudentTemplateServiceTest.java @@ -0,0 +1,13 @@ +package org.baeldung.spring.data.couchbase.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +public class StudentTemplateServiceTest extends StudentServiceTest { + + @Autowired + @Qualifier("StudentTemplateService") + public void setStudentService(StudentService service) { + this.studentService = service; + } +}