diff --git a/jpa/pom.xml b/jpa/pom.xml index f5a0818ac..2451c0fa2 100644 --- a/jpa/pom.xml +++ b/jpa/pom.xml @@ -25,6 +25,7 @@ security multiple-datasources eclipselink + query-by-example diff --git a/jpa/query-by-example/README.md b/jpa/query-by-example/README.md new file mode 100644 index 000000000..c4f459201 --- /dev/null +++ b/jpa/query-by-example/README.md @@ -0,0 +1,11 @@ +# Spring Data JPA - Query-by-Example (QBE) example + +This project contains samples of Query-by-Example of Spring Data JPA. + +## Support for Query-by-Example + +Query by Example (QBE) is a user-friendly querying technique with a simple interface. It allows dynamic query creation and does not require to write queries containing field names. In fact, Query by Example does not require to write queries using JPA-QL at all. + +An `Example` takes a data object (usually the entity object or a subtype of it) and a specification how to match properties. You can use Query by Example with JPA Repositories. + +This example contains a test class to illustrate Query-by-Example with a Repository in `UserRepositoryIntegrationTests`. diff --git a/jpa/query-by-example/pom.xml b/jpa/query-by-example/pom.xml new file mode 100644 index 000000000..0a2ee1437 --- /dev/null +++ b/jpa/query-by-example/pom.xml @@ -0,0 +1,14 @@ + + 4.0.0 + + + org.springframework.data.examples + spring-data-jpa-examples + 1.0.0.BUILD-SNAPSHOT + + + spring-data-jpa-query-by-example + Spring Data JPA - Query-by-Example (QBE) + + diff --git a/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/ApplicationConfiguration.java b/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/ApplicationConfiguration.java new file mode 100644 index 000000000..2f3006ffb --- /dev/null +++ b/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/ApplicationConfiguration.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.jpa.querybyexample; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.orm.jpa.EntityScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +/** + * @author Mark Paluch + */ +@Configuration +@EnableAutoConfiguration +@EntityScan(basePackageClasses = { ApplicationConfiguration.class }) +@EnableJpaAuditing +public class ApplicationConfiguration { + +} diff --git a/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/SpecialUser.java b/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/SpecialUser.java new file mode 100644 index 000000000..5b46915bb --- /dev/null +++ b/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/SpecialUser.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example.springdata.jpa.querybyexample; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; + +/** + * Sample class that extends {@link User}. + * + * @author Mark Paluch + */ +@Entity +@Data +@NoArgsConstructor +public class SpecialUser extends User { + + public SpecialUser(String firstname, String lastname, Integer age) { + super(firstname, lastname, age); + } +} diff --git a/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/User.java b/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/User.java new file mode 100644 index 000000000..fc5d7a1ac --- /dev/null +++ b/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/User.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.jpa.querybyexample; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Sample user class. + * + * @author Mark Paluch + */ +@Entity +@Data +@NoArgsConstructor +public class User { + + @Id @GeneratedValue // + private Long id; + private String firstname; + private String lastname; + private Integer age; + + public User(String firstname, String lastname, Integer age) { + + super(); + + this.firstname = firstname; + this.lastname = lastname; + this.age = age; + } + +} diff --git a/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/UserRepository.java b/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/UserRepository.java new file mode 100644 index 000000000..f5decc44b --- /dev/null +++ b/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/UserRepository.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.jpa.querybyexample; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.QueryByExampleExecutor; + +/** + * Simple repository interface for {@link User} instances. The interface implements {@link QueryByExampleExecutor} and + * allows execution of methods accepting {@link org.springframework.data.domain.Example}. + * + * @author Mark Paluch + */ +public interface UserRepository extends CrudRepository, QueryByExampleExecutor { + +} diff --git a/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/package-info.java b/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/package-info.java new file mode 100644 index 000000000..39edfd595 --- /dev/null +++ b/jpa/query-by-example/src/main/java/example/springdata/jpa/querybyexample/package-info.java @@ -0,0 +1,6 @@ +/** + * Sample showing Query-by-Example related features of Spring Data JPA. + * + * @author Mark Paluch + */ +package example.springdata.jpa.querybyexample; diff --git a/jpa/query-by-example/src/main/resources/application.properties b/jpa/query-by-example/src/main/resources/application.properties new file mode 100644 index 000000000..d98d035f1 --- /dev/null +++ b/jpa/query-by-example/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.datasource.separator=/; \ No newline at end of file diff --git a/jpa/query-by-example/src/main/resources/logback.xml b/jpa/query-by-example/src/main/resources/logback.xml new file mode 100644 index 000000000..40a8f3394 --- /dev/null +++ b/jpa/query-by-example/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + + + \ No newline at end of file diff --git a/jpa/query-by-example/src/test/java/example/springdata/jpa/querybyexample/UserRepositoryInheritanceIntegrationTests.java b/jpa/query-by-example/src/test/java/example/springdata/jpa/querybyexample/UserRepositoryInheritanceIntegrationTests.java new file mode 100644 index 000000000..1d8f2cf41 --- /dev/null +++ b/jpa/query-by-example/src/test/java/example/springdata/jpa/querybyexample/UserRepositoryInheritanceIntegrationTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example.springdata.jpa.querybyexample; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.data.domain.ExampleSpec.GenericPropertyMatchers.*; +import static org.springframework.data.domain.ExampleSpec.GenericPropertyMatchers.startsWith; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleSpec; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration test showing the usage of JPA Query-by-Example support through Spring Data repositories. + * + * @author Mark Paluch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@Transactional +@SpringApplicationConfiguration(classes = ApplicationConfiguration.class) +public class UserRepositoryInheritanceIntegrationTests { + + @Autowired UserRepository repository; + + User skyler, walter, flynn; + + @Before + public void setUp() { + + repository.deleteAll(); + + this.skyler = repository.save(new User("Skyler", "White", 45)); + this.walter = repository.save(new SpecialUser("Walter", "White", 50)); + this.flynn = repository.save(new SpecialUser("Walter Jr. (Flynn)", "White", 17)); + } + + /** + * @see DATAJPA-218 + */ + @Test + public void countBySimpleExample() { + + Example example = Example.of(new SpecialUser(null, "White", null)); + + assertThat(repository.count(example), is(3L)); + } + + /** + * @see DATAJPA-218 + */ + @Test + public void countUserByTypedExample() { + + Example example = Example.of(new SpecialUser(null, "White", null), // + ExampleSpec.typed(User.class)); + + assertThat(repository.count(example), is(3L)); + } + + /** + * @see DATAJPA-218 + */ + @Test + public void countSpecialUserByTypedExample() { + + Example example = Example.of(new SpecialUser(null, "White", null), // + ExampleSpec.typed(SpecialUser.class)); + + assertThat(repository.count(example), is(2L)); + } + +} diff --git a/jpa/query-by-example/src/test/java/example/springdata/jpa/querybyexample/UserRepositoryIntegrationTests.java b/jpa/query-by-example/src/test/java/example/springdata/jpa/querybyexample/UserRepositoryIntegrationTests.java new file mode 100644 index 000000000..4a784f2b2 --- /dev/null +++ b/jpa/query-by-example/src/test/java/example/springdata/jpa/querybyexample/UserRepositoryIntegrationTests.java @@ -0,0 +1,134 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.springdata.jpa.querybyexample; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.data.domain.ExampleSpec.GenericPropertyMatchers.*; +import static org.springframework.data.domain.ExampleSpec.GenericPropertyMatchers.startsWith; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleSpec; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration test showing the usage of JPA Query-by-Example support through Spring Data repositories. + * + * @author Mark Paluch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@Transactional +@SpringApplicationConfiguration(classes = ApplicationConfiguration.class) +public class UserRepositoryIntegrationTests { + + @Autowired + UserRepository repository; + + User skyler, walter, flynn, marie, hank; + + @Before + public void setUp() { + + repository.deleteAll(); + + this.skyler = repository.save(new User("Skyler", "White", 45)); + this.walter = repository.save(new User("Walter", "White", 50)); + this.flynn = repository.save(new User("Walter Jr. (Flynn)", "White", 17)); + this.marie = repository.save(new User("Marie", "Schrader", 38)); + this.hank = repository.save(new User("Hank", "Schrader", 43)); + } + + /** + * @see DATAJPA-218 + */ + @Test + public void countBySimpleExample() { + + Example example = Example.of(new User(null, "White", null)); + + assertThat(repository.count(example), is(3L)); + } + + /** + * @see DATAJPA-218 + */ + @Test + public void ignorePropertiesAndMatchByAge() { + + ExampleSpec exampleSpec = ExampleSpec.untyped(). // + withIgnorePaths("firstname", "lastname"); + + assertThat(repository.findOne(Example.of(flynn, exampleSpec)), is(flynn)); + } + + /** + * @see DATAJPA-218 + */ + @Test + public void substringMatching() { + + ExampleSpec exampleSpec = ExampleSpec.untyped().// + withStringMatcherEnding(); + + assertThat(repository.findAll(Example.of(new User("er", null, null), exampleSpec)), hasItems(skyler, walter)); + } + + /** + * @see DATAJPA-218 + */ + @Test + public void matchStartingStringsIgnoreCase() { + + ExampleSpec exampleSpec = ExampleSpec.untyped(). // + withIgnorePaths("age").// + withMatcher("firstname", startsWith()).// + withMatcher("lastname", ignoreCase()); + + assertThat(repository.findAll(Example.of(new User("Walter", "WHITE", null), exampleSpec)), hasItems(flynn, walter)); + } + + /** + * @see DATAJPA-218 + */ + @Test + public void configuringMatchersUsingLambdas() { + + ExampleSpec exampleSpec = ExampleSpec.untyped().withIgnorePaths("age"). // + withMatcher("firstname", matcher -> matcher.startsWith()). // + withMatcher("lastname", matcher -> matcher.ignoreCase()); + + assertThat(repository.findAll(Example.of(new User("Walter", "WHITE", null), exampleSpec)), hasItems(flynn, walter)); + } + + /** + * @see DATAJPA-218 + */ + @Test + public void valueTransformer() { + + ExampleSpec exampleSpec = ExampleSpec.untyped(). // + withMatcher("age", matcher -> matcher.transform(value -> Integer.valueOf(50))); + + assertThat(repository.findAll(Example.of(new User(null, "White", 99), exampleSpec)), hasItems(walter)); + } + +} diff --git a/mongodb/pom.xml b/mongodb/pom.xml index 0c2854e2d..35e2bab4a 100644 --- a/mongodb/pom.xml +++ b/mongodb/pom.xml @@ -23,6 +23,7 @@ java8 security geo-json + query-by-example diff --git a/mongodb/query-by-example/README.md b/mongodb/query-by-example/README.md new file mode 100644 index 000000000..01ad499cb --- /dev/null +++ b/mongodb/query-by-example/README.md @@ -0,0 +1,12 @@ +# Spring Data MongoDB - Query-by-Example (QBE) example + +This project contains samples of Query-by-Example of Spring Data MongoDB. + +## Support for Query-by-Example + +Query by Example (QBE) is a user-friendly querying technique with a simple interface. It allows dynamic query creation and does not require to write queries containing field names. In fact, Query by Example does not require to write queries using JPA-QL at all. + +An `Example` takes a data object (usually the entity object or a subtype of it) and a specification how to match properties. You can use Query by Example with `MongoOperations` and Repositories. + +This example contains two test classes to illustrate Query-by-Example with `MongoOperations` in `MongoOperationsIntegrationTests` and the usage with a Repository in `UserRepositoryIntegrationTests`. + diff --git a/mongodb/query-by-example/pom.xml b/mongodb/query-by-example/pom.xml new file mode 100644 index 000000000..a0652f4cb --- /dev/null +++ b/mongodb/query-by-example/pom.xml @@ -0,0 +1,14 @@ + + 4.0.0 + + + org.springframework.data.examples + spring-data-mongodb-examples + 1.0.0.BUILD-SNAPSHOT + + + spring-data-mongodb-query-by-example + Spring Data MongoDB - Query-by-Example (QBE) + + diff --git a/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/ApplicationConfiguration.java b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/ApplicationConfiguration.java new file mode 100644 index 000000000..3b121ff68 --- /dev/null +++ b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/ApplicationConfiguration.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example.springdata.mongodb.querybyexample; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Mark Paluch + */ +@SpringBootApplication +public class ApplicationConfiguration { + +} diff --git a/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/Contact.java b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/Contact.java new file mode 100644 index 000000000..90d4651ed --- /dev/null +++ b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/Contact.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example.springdata.mongodb.querybyexample; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * Sample contact class. + * + * @author Mark Paluch + */ +@Data +@RequiredArgsConstructor +@Document(collection = "collectionStoringTwoTypes") +public class Contact { + + @Id // + private ObjectId id; + private final String firstname; + private final String lastname; + private final Integer age; + +} diff --git a/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/ContactRepository.java b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/ContactRepository.java new file mode 100644 index 000000000..6c7083e54 --- /dev/null +++ b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/ContactRepository.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example.springdata.mongodb.querybyexample; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.QueryByExampleExecutor; + +/** + * Simple repository interface for {@link Contact} instances. The interface implements {@link QueryByExampleExecutor} and + * allows execution of methods accepting {@link org.springframework.data.domain.Example}. + * + * @author Mark Paluch + */ +public interface ContactRepository extends CrudRepository, QueryByExampleExecutor { + +} diff --git a/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/User.java b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/User.java new file mode 100644 index 000000000..56b84fe74 --- /dev/null +++ b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/User.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example.springdata.mongodb.querybyexample; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; + +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * Sample user class. + * + * @author Mark Paluch + */ +@Data +@RequiredArgsConstructor +@Document(collection = "collectionStoringTwoTypes") +public class User { + + @Id // + private ObjectId id; + private final String firstname; + private final String lastname; + private final Integer age; + +} diff --git a/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/UserRepository.java b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/UserRepository.java new file mode 100644 index 000000000..14241543c --- /dev/null +++ b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/UserRepository.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example.springdata.mongodb.querybyexample; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.QueryByExampleExecutor; + +/** + * Simple repository interface for {@link User} instances. The interface implements {@link QueryByExampleExecutor} and + * allows execution of methods accepting {@link org.springframework.data.domain.Example}. + * + * @author Mark Paluch + */ +public interface UserRepository extends CrudRepository, QueryByExampleExecutor { + +} diff --git a/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/package-info.java b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/package-info.java new file mode 100644 index 000000000..3b8fa0cc2 --- /dev/null +++ b/mongodb/query-by-example/src/main/java/example/springdata/mongodb/querybyexample/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Sample showing Query-by-Example related features of Spring Data MongoDB. + * + * @author Mark Paluch + */ +package example.springdata.mongodb.querybyexample; diff --git a/mongodb/query-by-example/src/main/resources/logback.xml b/mongodb/query-by-example/src/main/resources/logback.xml new file mode 100644 index 000000000..40a8f3394 --- /dev/null +++ b/mongodb/query-by-example/src/main/resources/logback.xml @@ -0,0 +1,16 @@ + + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + + + \ No newline at end of file diff --git a/mongodb/query-by-example/src/test/java/example/springdata/mongodb/querybyexample/ContactRepositoryIntegrationTests.java b/mongodb/query-by-example/src/test/java/example/springdata/mongodb/querybyexample/ContactRepositoryIntegrationTests.java new file mode 100644 index 000000000..b3a87af6a --- /dev/null +++ b/mongodb/query-by-example/src/test/java/example/springdata/mongodb/querybyexample/ContactRepositoryIntegrationTests.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example.springdata.mongodb.querybyexample; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleSpec; +import org.springframework.data.domain.ExampleSpec.StringMatcher; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Integration test showing the usage of MongoDB Query-by-Example support through Spring Data repositories for a case where two domain types are stored in one collection. + * + * @author Mark Paluch + * @soundtrack Paul van Dyk - VONYC Sessions Episode 496 with guest Armin van Buuren + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = ApplicationConfiguration.class) +public class ContactRepositoryIntegrationTests { + + @Autowired UserRepository userRepository; + @Autowired ContactRepository contactRepository; + @Autowired MongoOperations mongoOperations; + + User skyler, walter, flynn; + Contact marie, hank; + + @Before + public void setUp() { + + userRepository.deleteAll(); + + this.skyler = userRepository.save(new User("Skyler", "White", 45)); + this.walter = userRepository.save(new User("Walter", "White", 50)); + this.flynn = userRepository.save(new User("Walter Jr. (Flynn)", "White", 17)); + this.marie = contactRepository.save(new Contact("Marie", "Schrader", 38)); + this.hank = contactRepository.save(new Contact("Hank", "Schrader", 43)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void countUsersByUntypedExample() { + + Example example = Example.of(new User(null, null, null)); + + // the count is 5 because untyped examples do not restrict the type + assertThat(userRepository.count(example), is(5L)); + assertThat(mongoOperations.count(new Query(), "collectionStoringTwoTypes"), is(5L)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void countByTypedExample() { + + Example example = Example.of(new User(null, null, null), ExampleSpec.typed(User.class)); + + assertThat(userRepository.count(example), is(3L)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void findAllUsersBySimpleExample() { + + Example example = Example.of(new User(".*", null, null), // + ExampleSpec.typed(User.class).withStringMatcher(StringMatcher.REGEX)); + + assertThat(userRepository.findAll(example), containsInAnyOrder(skyler, walter, flynn)); + assertThat(userRepository.findAll(example), not(containsInAnyOrder(hank, marie))); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void findAllContactsBySimpleExample() { + + Example example = Example.of(new Contact(".*", null, null), // + ExampleSpec.typed(Contact.class).withStringMatcher(StringMatcher.REGEX)); + + assertThat(contactRepository.findAll(example), containsInAnyOrder(hank, marie)); + assertThat(contactRepository.findAll(example), not(containsInAnyOrder(skyler, walter, flynn))); + } + +} diff --git a/mongodb/query-by-example/src/test/java/example/springdata/mongodb/querybyexample/MongoOperationsIntegrationTests.java b/mongodb/query-by-example/src/test/java/example/springdata/mongodb/querybyexample/MongoOperationsIntegrationTests.java new file mode 100644 index 000000000..fb2a2bded --- /dev/null +++ b/mongodb/query-by-example/src/test/java/example/springdata/mongodb/querybyexample/MongoOperationsIntegrationTests.java @@ -0,0 +1,142 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example.springdata.mongodb.querybyexample; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.data.domain.ExampleSpec.GenericPropertyMatchers.*; +import static org.springframework.data.domain.ExampleSpec.GenericPropertyMatchers.startsWith; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleSpec; +import org.springframework.data.domain.TypedExampleSpec; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Integration test showing the usage of MongoDB Query-by-Example support through Spring Data repositories. + * + * @author Mark Paluch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = ApplicationConfiguration.class) +public class MongoOperationsIntegrationTests { + + @Autowired MongoOperations operations; + + User skyler, walter, flynn, marie, hank; + + @Before + public void setUp() { + + operations.remove(new Query(), User.class); + + this.skyler = new User("Skyler", "White", 45); + this.walter = new User("Walter", "White", 50); + this.flynn = new User("Walter Jr. (Flynn)", "White", 17); + this.marie = new User("Marie", "Schrader", 38); + this.hank = new User("Hank", "Schrader", 43); + + operations.save(this.skyler); + operations.save(this.walter); + operations.save(this.flynn); + operations.save(this.marie); + operations.save(this.hank); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void ignoreNullProperties() { + + assertThat(operations.findByExample(new User(null, null, 17)), hasItems(flynn)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void substringMatching() { + + TypedExampleSpec exampleSpec = ExampleSpec.typed(User.class).// + withStringMatcherEnding(); + + assertThat(operations.findByExample(Example.of(new User("er", null, null), exampleSpec)), hasItems(skyler, walter)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void regexMatching() { + + TypedExampleSpec exampleSpec = ExampleSpec.typed(User.class).// + withMatcher("firstname", matcher -> matcher.regex()); + + assertThat(operations.findByExample(Example.of(new User("(Skyl|Walt)er", null, null), exampleSpec)), + hasItems(skyler, walter)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void matchStartingStringsIgnoreCase() { + + TypedExampleSpec exampleSpec = ExampleSpec.typed(User.class). // + withIgnorePaths("age").// + withMatcher("firstname", startsWith()).// + withMatcher("lastname", ignoreCase()); + + assertThat(operations.findByExample(Example.of(new User("Walter", "WHITE", null), exampleSpec)), + hasItems(flynn, walter)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void configuringMatchersUsingLambdas() { + + TypedExampleSpec exampleSpec = ExampleSpec.typed(User.class).withIgnorePaths("age"). // + withMatcher("firstname", matcher -> matcher.startsWith()). // + withMatcher("lastname", matcher -> matcher.ignoreCase()); + + assertThat(operations.findByExample(Example.of(new User("Walter", "WHITE", null), exampleSpec)), + hasItems(flynn, walter)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void valueTransformer() { + + TypedExampleSpec exampleSpec = ExampleSpec.typed(User.class). // + withMatcher("age", matcher -> matcher.transform(value -> Integer.valueOf(50))); + + assertThat(operations.findByExample(Example.of(new User(null, "White", 99), exampleSpec)), hasItems(walter)); + } + +} diff --git a/mongodb/query-by-example/src/test/java/example/springdata/mongodb/querybyexample/UserRepositoryIntegrationTests.java b/mongodb/query-by-example/src/test/java/example/springdata/mongodb/querybyexample/UserRepositoryIntegrationTests.java new file mode 100644 index 000000000..575fb2b27 --- /dev/null +++ b/mongodb/query-by-example/src/test/java/example/springdata/mongodb/querybyexample/UserRepositoryIntegrationTests.java @@ -0,0 +1,145 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package example.springdata.mongodb.querybyexample; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.data.domain.ExampleSpec.GenericPropertyMatchers.ignoreCase; +import static org.springframework.data.domain.ExampleSpec.GenericPropertyMatchers.startsWith; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleSpec; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Integration test showing the usage of MongoDB Query-by-Example support through Spring Data repositories. + * + * @author Mark Paluch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = ApplicationConfiguration.class) +public class UserRepositoryIntegrationTests { + + @Autowired UserRepository repository; + + User skyler, walter, flynn, marie, hank; + + @Before + public void setUp() { + + repository.deleteAll(); + + this.skyler = repository.save(new User("Skyler", "White", 45)); + this.walter = repository.save(new User("Walter", "White", 50)); + this.flynn = repository.save(new User("Walter Jr. (Flynn)", "White", 17)); + this.marie = repository.save(new User("Marie", "Schrader", 38)); + this.hank = repository.save(new User("Hank", "Schrader", 43)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void countBySimpleExample() { + + Example example = Example.of(new User(null, "White", null)); + + assertThat(repository.count(example), is(3L)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void ignorePropertiesAndMatchByAge() { + + ExampleSpec exampleSpec = ExampleSpec.untyped(). // + withIgnorePaths("firstname", "lastname"); + + assertThat(repository.findOne(Example.of(flynn, exampleSpec)), is(flynn)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void substringMatching() { + + ExampleSpec exampleSpec = ExampleSpec.untyped().// + withStringMatcherEnding(); + + assertThat(repository.findAll(Example.of(new User("er", null, null), exampleSpec)), hasItems(skyler, walter)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void regexMatching() { + + ExampleSpec exampleSpec = ExampleSpec.untyped().// + withMatcher("firstname", matcher -> matcher.regex()); + + assertThat(repository.findAll(Example.of(new User("(Skyl|Walt)er", null, null), exampleSpec)), + hasItems(skyler, walter)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void matchStartingStringsIgnoreCase() { + + ExampleSpec exampleSpec = ExampleSpec.untyped(). // + withIgnorePaths("age").// + withMatcher("firstname", startsWith()).// + withMatcher("lastname", ignoreCase()); + + assertThat(repository.findAll(Example.of(new User("Walter", "WHITE", null), exampleSpec)), hasItems(flynn, walter)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void configuringMatchersUsingLambdas() { + + ExampleSpec exampleSpec = ExampleSpec.untyped().withIgnorePaths("age"). // + withMatcher("firstname", matcher -> matcher.startsWith()). // + withMatcher("lastname", matcher -> matcher.ignoreCase()); + + assertThat(repository.findAll(Example.of(new User("Walter", "WHITE", null), exampleSpec)), hasItems(flynn, walter)); + } + + /** + * @see DATAMONGO-1245 + */ + @Test + public void valueTransformer() { + + ExampleSpec exampleSpec = ExampleSpec.untyped(). // + withMatcher("age", matcher -> matcher.transform(value -> Integer.valueOf(50))); + + assertThat(repository.findAll(Example.of(new User(null, "White", 99), exampleSpec)), hasItems(walter)); + } + +}