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; + } +} diff --git a/spring-data-elasticsearch/.classpath b/spring-data-elasticsearch/.classpath new file mode 100644 index 0000000000..6d7587a819 --- /dev/null +++ b/spring-data-elasticsearch/.classpath @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-data-elasticsearch/.project b/spring-data-elasticsearch/.project new file mode 100644 index 0000000000..09b9a781ed --- /dev/null +++ b/spring-data-elasticsearch/.project @@ -0,0 +1,29 @@ + + + spring-data-elasticsearch + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/spring-data-elasticsearch/pom.xml b/spring-data-elasticsearch/pom.xml index 27fb521dbc..3a6e330564 100644 --- a/spring-data-elasticsearch/pom.xml +++ b/spring-data-elasticsearch/pom.xml @@ -18,7 +18,7 @@ 4.11 1.7.12 1.1.3 - 1.3.2.RELEASE + 2.0.1.RELEASE @@ -44,6 +44,11 @@ spring-data-elasticsearch ${elasticsearch.version} + net.java.dev.jna + jna + 4.1.0 + test + org.slf4j slf4j-api diff --git a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java index 3857056b70..f93999a1cc 100644 --- a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java +++ b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/config/Config.java @@ -1,10 +1,16 @@ package com.baeldung.spring.data.es.config; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + import org.elasticsearch.client.Client; -import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.node.NodeBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -12,35 +18,33 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - @Configuration @EnableElasticsearchRepositories(basePackages = "com.baeldung.spring.data.es.repository") -@ComponentScan(basePackages = {"com.baeldung.spring.data.es.service"}) +@ComponentScan(basePackages = { "com.baeldung.spring.data.es.service" }) public class Config { + @Value("${elasticsearch.home:/usr/local/Cellar/elasticsearch/2.3.2}") + private String elasticsearchHome; + private static Logger logger = LoggerFactory.getLogger(Config.class); @Bean public Client client() { try { - Path tmpDir = Files.createTempDirectory(Paths.get(System.getProperty("java.io.tmpdir")), "elasticsearch_data"); + final Path tmpDir = Files.createTempDirectory(Paths.get(System.getProperty("java.io.tmpdir")), "elasticsearch_data"); - ImmutableSettings.Builder elasticsearchSettings = ImmutableSettings.settingsBuilder() - .put("http.enabled", "false") - .put("path.data", tmpDir.toAbsolutePath().toString()); + // @formatter:off + + final Settings.Builder elasticsearchSettings = + Settings.settingsBuilder().put("http.enabled", "false") + .put("path.data", tmpDir.toAbsolutePath().toString()) + .put("path.home", elasticsearchHome); + // @formatter:on logger.debug(tmpDir.toAbsolutePath().toString()); - return new NodeBuilder() - .local(true) - .settings(elasticsearchSettings.build()) - .node() - .client(); - } catch (IOException ioex) { + return new NodeBuilder().local(true).settings(elasticsearchSettings.build()).node().client(); + } catch (final IOException ioex) { logger.error("Cannot create temp dir", ioex); throw new RuntimeException(); } diff --git a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Article.java b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Article.java index 40db51ac13..01330a6c9c 100644 --- a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Article.java +++ b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Article.java @@ -1,27 +1,25 @@ package com.baeldung.spring.data.es.model; -import org.springframework.data.annotation.Id; -import org.springframework.data.elasticsearch.annotations.*; - -import java.util.Arrays; -import java.util.List; - import static org.springframework.data.elasticsearch.annotations.FieldIndex.not_analyzed; import static org.springframework.data.elasticsearch.annotations.FieldType.Nested; import static org.springframework.data.elasticsearch.annotations.FieldType.String; +import java.util.Arrays; +import java.util.List; + +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.InnerField; +import org.springframework.data.elasticsearch.annotations.MultiField; + @Document(indexName = "blog", type = "article") public class Article { @Id private String id; - @MultiField( - mainField = @Field(type = String), - otherFields = { - @NestedField(index = not_analyzed, dotSuffix = "verbatim", type = String) - } - ) + @MultiField(mainField = @Field(type = String), otherFields = { @InnerField(index = not_analyzed, suffix = "verbatim", type = String) }) private String title; @Field(type = Nested) @@ -71,11 +69,6 @@ public class Article { @Override public String toString() { - return "Article{" + - "id='" + id + '\'' + - ", title='" + title + '\'' + - ", authors=" + authors + - ", tags=" + Arrays.toString(tags) + - '}'; + return "Article{" + "id='" + id + '\'' + ", title='" + title + '\'' + ", authors=" + authors + ", tags=" + Arrays.toString(tags) + '}'; } } diff --git a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Author.java b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Author.java index c335c4534a..38f50e1614 100644 --- a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Author.java +++ b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/model/Author.java @@ -21,8 +21,6 @@ public class Author { @Override public String toString() { - return "Author{" + - "name='" + name + '\'' + - '}'; + return "Author{" + "name='" + name + '\'' + '}'; } } diff --git a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleService.java b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleService.java index 760bad4b01..b5a8fde633 100644 --- a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleService.java +++ b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleService.java @@ -6,10 +6,16 @@ import org.springframework.data.domain.Pageable; public interface ArticleService { Article save(Article article); + Article findOne(String id); + Iterable
findAll(); + Page
findByAuthorName(String name, Pageable pageable); + Page
findByAuthorNameUsingCustomQuery(String name, Pageable pageable); + long count(); + void delete(Article article); } diff --git a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleServiceImpl.java b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleServiceImpl.java index 3bb6e6a0e0..0ea922bdd3 100644 --- a/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleServiceImpl.java +++ b/spring-data-elasticsearch/src/main/java/com/baeldung/spring/data/es/service/ArticleServiceImpl.java @@ -49,6 +49,6 @@ public class ArticleServiceImpl implements ArticleService { @Override public void delete(Article article) { - articleRepository.delete(article); + articleRepository.delete(article); } } diff --git a/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryTest.java b/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryTest.java index fbc18cbb4c..1551d6442e 100644 --- a/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryTest.java +++ b/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchQueryTest.java @@ -1,12 +1,20 @@ package com.baeldung.spring.data.es; -import com.baeldung.spring.data.es.config.Config; -import com.baeldung.spring.data.es.model.Article; -import com.baeldung.spring.data.es.model.Author; -import com.baeldung.spring.data.es.service.ArticleService; -import org.elasticsearch.action.ActionFuture; -import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.index.IndexResponse; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.elasticsearch.index.query.MatchQueryBuilder.Operator.AND; +import static org.elasticsearch.index.query.QueryBuilders.boolQuery; +import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery; +import static org.elasticsearch.index.query.QueryBuilders.matchQuery; +import static org.elasticsearch.index.query.QueryBuilders.multiMatchQuery; +import static org.elasticsearch.index.query.QueryBuilders.nestedQuery; +import static org.elasticsearch.index.query.QueryBuilders.termQuery; +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.unit.Fuzziness; @@ -28,19 +36,13 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import static java.util.Arrays.asList; -import static java.util.stream.Collectors.toList; -import static org.elasticsearch.index.query.MatchQueryBuilder.Operator.AND; -import static org.elasticsearch.index.query.QueryBuilders.*; -import static org.junit.Assert.assertEquals; +import com.baeldung.spring.data.es.config.Config; +import com.baeldung.spring.data.es.model.Article; +import com.baeldung.spring.data.es.model.Author; +import com.baeldung.spring.data.es.service.ArticleService; @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {Config.class}, loader = AnnotationConfigContextLoader.class) +@ContextConfiguration(classes = { Config.class }, loader = AnnotationConfigContextLoader.class) public class ElasticSearchQueryTest { @Autowired @@ -60,7 +62,7 @@ public class ElasticSearchQueryTest { elasticsearchTemplate.deleteIndex(Article.class); elasticsearchTemplate.createIndex(Article.class); elasticsearchTemplate.putMapping(Article.class); - elasticsearchTemplate.refresh(Article.class, true); + elasticsearchTemplate.refresh(Article.class); Article article = new Article("Spring Data Elasticsearch"); article.setAuthors(asList(johnSmith, johnDoe)); @@ -85,127 +87,92 @@ public class ElasticSearchQueryTest { @Test public void givenFullTitle_whenRunMatchQuery_thenDocIsFound() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", "Search engines").operator(AND)) - .build(); - List
articles = elasticsearchTemplate - .queryForList(searchQuery, Article.class); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", "Search engines").operator(AND)).build(); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(1, articles.size()); } @Test public void givenOneTermFromTitle_whenRunMatchQuery_thenDocIsFound() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", "Engines Solutions")) - .build(); - List
articles = elasticsearchTemplate - .queryForList(searchQuery, Article.class); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", "Engines Solutions")).build(); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(1, articles.size()); assertEquals("Search engines", articles.get(0).getTitle()); } @Test public void givenPartTitle_whenRunMatchQuery_thenDocIsFound() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", "elasticsearch data")) - .build(); - List
articles = elasticsearchTemplate - .queryForList(searchQuery, Article.class); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", "elasticsearch data")).build(); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(3, articles.size()); } @Test public void givenFullTitle_whenRunMatchQueryOnVerbatimField_thenDocIsFound() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title.verbatim", "Second Article About Elasticsearch")) - .build(); - List
articles = elasticsearchTemplate - .queryForList(searchQuery, Article.class); + SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title.verbatim", "Second Article About Elasticsearch")).build(); + List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(1, articles.size()); - searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title.verbatim", "Second Article About")) - .build(); - articles = elasticsearchTemplate - .queryForList(searchQuery, Article.class); + searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title.verbatim", "Second Article About")).build(); + articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(0, articles.size()); } @Test public void givenNestedObject_whenQueryByAuthorsName_thenFoundArticlesByThatAuthor() { - QueryBuilder builder = nestedQuery("authors", - boolQuery().must(termQuery("authors.name", "smith"))); + final QueryBuilder builder = nestedQuery("authors", boolQuery().must(termQuery("authors.name", "smith"))); - SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder).build(); - List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder).build(); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(2, articles.size()); } @Test public void givenAnalyzedQuery_whenMakeAggregationOnTermCount_thenEachTokenCountsSeparately() { - TermsBuilder aggregation = AggregationBuilders.terms("top_tags").field("title"); - SearchResponse response = client.prepareSearch("blog").setTypes("article") - .addAggregation(aggregation).execute().actionGet(); + final TermsBuilder aggregation = AggregationBuilders.terms("top_tags").field("title"); + final SearchResponse response = client.prepareSearch("blog").setTypes("article").addAggregation(aggregation).execute().actionGet(); - Map results = response.getAggregations().asMap(); - StringTerms topTags = (StringTerms) results.get("top_tags"); + final Map results = response.getAggregations().asMap(); + final StringTerms topTags = (StringTerms) results.get("top_tags"); - List keys = topTags.getBuckets().stream().map(b -> b.getKey()).collect(toList()); + final List keys = topTags.getBuckets().stream().map(b -> b.getKeyAsString()).collect(toList()); Collections.sort(keys); - assertEquals(asList("about", "article", "data", "elasticsearch", - "engines", "search", "second", "spring", "tutorial"), keys); + assertEquals(asList("about", "article", "data", "elasticsearch", "engines", "search", "second", "spring", "tutorial"), keys); } @Test public void givenNotAnalyzedQuery_whenMakeAggregationOnTermCount_thenEachTermCountsIndividually() { - TermsBuilder aggregation = AggregationBuilders.terms("top_tags").field("tags") - .order(Terms.Order.aggregation("_count", false)); - SearchResponse response = client.prepareSearch("blog").setTypes("article") - .addAggregation(aggregation).execute().actionGet(); + final TermsBuilder aggregation = AggregationBuilders.terms("top_tags").field("tags").order(Terms.Order.aggregation("_count", false)); + final SearchResponse response = client.prepareSearch("blog").setTypes("article").addAggregation(aggregation).execute().actionGet(); - Map results = response.getAggregations().asMap(); - StringTerms topTags = (StringTerms) results.get("top_tags"); + final Map results = response.getAggregations().asMap(); + final StringTerms topTags = (StringTerms) results.get("top_tags"); - List keys = topTags.getBuckets().stream().map(b -> b.getKey()).collect(toList()); + final List keys = topTags.getBuckets().stream().map(b -> b.getKeyAsString()).collect(toList()); assertEquals(asList("elasticsearch", "spring data", "search engines", "tutorial"), keys); } @Test public void givenNotExactPhrase_whenUseSlop_thenQueryMatches() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchPhraseQuery("title", "spring elasticsearch").slop(1)) - .build(); - List
articles = elasticsearchTemplate - .queryForList(searchQuery, Article.class); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchPhraseQuery("title", "spring elasticsearch").slop(1)).build(); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(1, articles.size()); } @Test public void givenPhraseWithType_whenUseFuzziness_thenQueryMatches() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", "spring date elasticserch") - .operator(AND) - .fuzziness(Fuzziness.ONE) - .prefixLength(3)) - .build(); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", "spring date elasticserch").operator(AND).fuzziness(Fuzziness.ONE).prefixLength(3)).build(); - List
articles = elasticsearchTemplate - .queryForList(searchQuery, Article.class); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(1, articles.size()); } @Test public void givenMultimatchQuery_whenDoSearch_thenAllProvidedFieldsMatch() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(multiMatchQuery("tutorial") - .field("title") - .field("tags") - .type(MultiMatchQueryBuilder.Type.BEST_FIELDS)) - .build(); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(multiMatchQuery("tutorial").field("title").field("tags").type(MultiMatchQueryBuilder.Type.BEST_FIELDS)).build(); - List
articles = elasticsearchTemplate - .queryForList(searchQuery, Article.class); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(2, articles.size()); } } diff --git a/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchTest.java b/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchTest.java index 7b48772d3f..e10b5f48d7 100644 --- a/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchTest.java +++ b/spring-data-elasticsearch/src/test/java/com/baeldung/spring/data/es/ElasticSearchTest.java @@ -1,9 +1,15 @@ package com.baeldung.spring.data.es; -import com.baeldung.spring.data.es.config.Config; -import com.baeldung.spring.data.es.model.Article; -import com.baeldung.spring.data.es.model.Author; -import com.baeldung.spring.data.es.service.ArticleService; +import static java.util.Arrays.asList; +import static org.elasticsearch.index.query.MatchQueryBuilder.Operator.AND; +import static org.elasticsearch.index.query.QueryBuilders.fuzzyQuery; +import static org.elasticsearch.index.query.QueryBuilders.matchQuery; +import static org.elasticsearch.index.query.QueryBuilders.regexpQuery; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -17,17 +23,13 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; -import java.util.List; - -import static java.util.Arrays.asList; -import static org.elasticsearch.index.query.FilterBuilders.regexpFilter; -import static org.elasticsearch.index.query.MatchQueryBuilder.Operator.AND; -import static org.elasticsearch.index.query.QueryBuilders.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import com.baeldung.spring.data.es.config.Config; +import com.baeldung.spring.data.es.model.Article; +import com.baeldung.spring.data.es.model.Author; +import com.baeldung.spring.data.es.service.ArticleService; @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = {Config.class}, loader = AnnotationConfigContextLoader.class) +@ContextConfiguration(classes = { Config.class }, loader = AnnotationConfigContextLoader.class) public class ElasticSearchTest { @Autowired @@ -59,8 +61,7 @@ public class ElasticSearchTest { @Test public void givenArticleService_whenSaveArticle_thenIdIsAssigned() { - List authors = asList( - new Author("John Smith"), johnDoe); + final List authors = asList(new Author("John Smith"), johnDoe); Article article = new Article("Making Search Elastic"); article.setAuthors(authors); @@ -72,39 +73,34 @@ public class ElasticSearchTest { @Test public void givenPersistedArticles_whenSearchByAuthorsName_thenRightFound() { - Page
articleByAuthorName = articleService.findByAuthorName(johnSmith.getName(), new PageRequest(0, 10)); + final Page
articleByAuthorName = articleService.findByAuthorName(johnSmith.getName(), new PageRequest(0, 10)); assertEquals(2L, articleByAuthorName.getTotalElements()); } @Test public void givenCustomQuery_whenSearchByAuthorsName_thenArticleIsFound() { - Page
articleByAuthorName = articleService.findByAuthorNameUsingCustomQuery("John Smith", new PageRequest(0, 10)); + final Page
articleByAuthorName = articleService.findByAuthorNameUsingCustomQuery("John Smith", new PageRequest(0, 10)); assertEquals(3L, articleByAuthorName.getTotalElements()); } - @Test public void givenPersistedArticles_whenUseRegexQuery_thenRightArticlesFound() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withFilter(regexpFilter("title", ".*data.*")) - .build(); - List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withFilter(regexpQuery("title", ".*data.*")).build(); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(1, articles.size()); } @Test public void givenSavedDoc_whenTitleUpdated_thenCouldFindByUpdatedTitle() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(fuzzyQuery("title", "serch")) - .build(); - List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(fuzzyQuery("title", "serch")).build(); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(1, articles.size()); - Article article = articles.get(0); + final Article article = articles.get(0); final String newTitle = "Getting started with Search Engines"; article.setTitle(newTitle); articleService.save(article); @@ -117,10 +113,8 @@ public class ElasticSearchTest { final String articleTitle = "Spring Data Elasticsearch"; - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", articleTitle).minimumShouldMatch("75%")) - .build(); - List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", articleTitle).minimumShouldMatch("75%")).build(); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(1, articles.size()); final long count = articleService.count(); @@ -131,10 +125,8 @@ public class ElasticSearchTest { @Test public void givenSavedDoc_whenOneTermMatches_thenFindByTitle() { - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(matchQuery("title", "Search engines").operator(AND)) - .build(); - List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); + final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("title", "Search engines").operator(AND)).build(); + final List
articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); assertEquals(1, articles.size()); } }