diff --git a/jpa/example/src/main/java/example/springdata/jpa/projections/Customer.java b/jpa/example/src/main/java/example/springdata/jpa/projections/Customer.java new file mode 100644 index 00000000..8a006c17 --- /dev/null +++ b/jpa/example/src/main/java/example/springdata/jpa/projections/Customer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015 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.projections; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +/** + * @author Oliver Gierke + */ +@Data +@Entity +@RequiredArgsConstructor +public class Customer { + + private @GeneratedValue @Id Long id; + private final String firstname, lastname; + + protected Customer() { + this.firstname = null; + this.lastname = null; + } +} diff --git a/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerDto.java b/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerDto.java new file mode 100644 index 00000000..9a61b788 --- /dev/null +++ b/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerDto.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015 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.projections; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +/** + * @author Oliver Gierke + */ +@Data +@RequiredArgsConstructor +public class CustomerDto { + + private final String firstname; +} diff --git a/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerProjection.java b/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerProjection.java new file mode 100644 index 00000000..567e6867 --- /dev/null +++ b/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerProjection.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015 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.projections; + +/** + * + * @author Oliver Gierke + */ +public interface CustomerProjection { + + String getFirstname(); +} diff --git a/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerRepository.java b/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerRepository.java new file mode 100644 index 00000000..3890c654 --- /dev/null +++ b/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerRepository.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015 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.projections; + +import java.util.Collection; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; + +/** + * @author Oliver Gierke + */ +public interface CustomerRepository extends CrudRepository { + + /** + * Uses a projection interface to indicate the fields to be returned. As the projection doesn't use any dynamic + * fields, the query execution will be restricted to only the fields needed by the projection. + * + * @return + */ + Collection findAllProjectedBy(); + + /** + * When a projection is used that contains dynamic properties (i.e. SpEL expressions in an {@link Value} annotation), + * the normal target entity will be loaded but dynamically projected so that the target can be referred to in the + * expression. + * + * @return + */ + Collection findAllSummarizedBy(); + + /** + * Projection interfaces can be used with manually declared queries, too. Make sure you alias the projects matching + * the projection fields. + * + * @return + */ + @Query("select c.firstname as firstname, c.lastname as lastname from Customer c") + Collection findsByProjectedColumns(); + + /** + * Uses a concrete DTO type to indicate the fields to be returned. This gets translated into a constructor expression + * in the query. + * + * @return + */ + Collection findAllDtoedBy(); + + /** + * Passes in the projection type dynamically (either interface or DTO). + * + * @param firstname + * @param projection + * @return + */ + Collection findByFirstname(String firstname, Class projection); + + /** + * Projection for a single entity. + * + * @param id + * @return + */ + CustomerProjection findProjectedById(Long id); + + /** + * Dynamic projection for a single entity. + * + * @param id + * @param projection + * @return + */ + T findProjectedById(Long id, Class projection); +} diff --git a/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerSummary.java b/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerSummary.java new file mode 100644 index 00000000..be6a6817 --- /dev/null +++ b/jpa/example/src/main/java/example/springdata/jpa/projections/CustomerSummary.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 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.projections; + +import org.springframework.beans.factory.annotation.Value; + +/** + * @author Oliver Gierke + */ +public interface CustomerSummary { + + @Value("#{target.firstname + ' ' + target.lastname}") + String getFullName(); +} diff --git a/jpa/example/src/test/java/example/springdata/jpa/projections/CustomerRepositoryIntegrationTest.java b/jpa/example/src/test/java/example/springdata/jpa/projections/CustomerRepositoryIntegrationTest.java new file mode 100644 index 00000000..7d7157ca --- /dev/null +++ b/jpa/example/src/test/java/example/springdata/jpa/projections/CustomerRepositoryIntegrationTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015 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.projections; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Collection; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.projection.TargetAware; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integaration tests for {@link CustomerRepository} to show projection capabilities. + * + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@Transactional +public class CustomerRepositoryIntegrationTest { + + @Configuration + @EnableAutoConfiguration + static class Config {} + + @Autowired CustomerRepository customers; + + Customer dave, carter; + + @Before + public void setUp() { + + this.dave = customers.save(new Customer("Dave", "Matthews")); + this.carter = customers.save(new Customer("Carter", "Beauford")); + } + + @Test + public void projectsEntityIntoInterface() { + + Collection result = customers.findAllProjectedBy(); + + assertThat(result, hasSize(2)); + assertThat(result.iterator().next().getFirstname(), is("Dave")); + } + + @Test + public void projectsMapIntoInterface() { + + Collection result = customers.findsByProjectedColumns(); + + assertThat(result, hasSize(2)); + assertThat(result.iterator().next().getFirstname(), is("Dave")); + } + + @Test + public void projectsToDto() { + + Collection result = customers.findAllDtoedBy(); + + assertThat(result, hasSize(2)); + assertThat(result.iterator().next().getFirstname(), is("Dave")); + } + + @Test + public void projectsDynamically() { + + Collection result = customers.findByFirstname("Dave", CustomerProjection.class); + + assertThat(result, hasSize(1)); + assertThat(result.iterator().next().getFirstname(), is("Dave")); + } + + @Test + public void projectsIndividualDynamically() { + + CustomerSummary result = customers.findProjectedById(dave.getId(), CustomerSummary.class); + + assertThat(result.getFullName(), is("Dave Matthews")); + + // Proxy backed by original instance as the projection uses dynamic elements + assertThat(((TargetAware) result).getTarget(), is(instanceOf(Customer.class))); + } + + @Test + public void projectIndividualInstance() { + + CustomerProjection projectedDave = customers.findProjectedById(dave.getId()); + + assertThat(projectedDave.getFirstname(), is("Dave")); + assertThat(((TargetAware) projectedDave).getTarget(), is(instanceOf(Map.class))); + } +} diff --git a/mongodb/example/pom.xml b/mongodb/example/pom.xml index 4f3e62df..f36cbb28 100644 --- a/mongodb/example/pom.xml +++ b/mongodb/example/pom.xml @@ -20,7 +20,7 @@ ${apt.version} - com.mysema.querydsl + com.querydsl querydsl-apt ${querydsl.version} diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/projections/Customer.java b/mongodb/example/src/main/java/example/springdata/mongodb/projections/Customer.java new file mode 100644 index 00000000..c61c1112 --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/projections/Customer.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-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.projections; + +import lombok.Value; + +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * @author Oliver Gierke + */ +@Value +@Document +class Customer { + + @Id ObjectId id = new ObjectId(); + String firstname, lastname; +} diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerDto.java b/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerDto.java new file mode 100644 index 00000000..cc39c8fb --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerDto.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-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.projections; + +import lombok.Value; + +/** + * A sample DTO only containing the firstname. + * + * @author Oliver Gierke + */ +@Value +class CustomerDto { + + String firstname; +} diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerProjection.java b/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerProjection.java new file mode 100644 index 00000000..f81bc5c7 --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerProjection.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-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.projections; + +/** + * An example projection interface containing only the firstname. + * + * @author Oliver Gierke + */ +interface CustomerProjection { + + String getFirstname(); +} diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerRepository.java b/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerRepository.java new file mode 100644 index 00000000..8e55256e --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerRepository.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015-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.projections; + +import java.util.Collection; + +import org.bson.types.ObjectId; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.repository.CrudRepository; + +/** + * Sample repository managing customers to show projecting functionality of Spring Data MongoDB. + * + * @author Oliver Gierke + */ +interface CustomerRepository extends CrudRepository { + + /** + * Uses a projection interface to indicate the fields to be returned. As the projection doesn't use any dynamic + * fields, the query execution will be restricted to only the fields needed by the projection. + * + * @return + */ + Collection findAllProjectedBy(); + + /** + * When a projection is used that contains dynamic properties (i.e. SpEL expressions in an {@link Value} annotation), + * the normal target entity will be loaded but dynamically projected so that the target can be referred to in the + * expression. + * + * @return + */ + Collection findAllSummarizedBy(); + + /** + * Uses a concrete DTO type to indicate the fields to be returned. This will cause the original object being loaded + * and the properties copied over into the DTO. + * + * @return + */ + Collection findAllDtoedBy(); + + /** + * Passes in the projection type dynamically (either interface or DTO). + * + * @param firstname + * @param projection + * @return + */ + Collection findByFirstname(String firstname, Class projection); + + /** + * Projection for a single entity. + * + * @param id + * @return + */ + CustomerProjection findProjectedById(ObjectId id); + + /** + * Dynamic projection for a single entity. + * + * @param id + * @param projection + * @return + */ + T findProjectedById(ObjectId id, Class projection); +} diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerSummary.java b/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerSummary.java new file mode 100644 index 00000000..c5a4cb26 --- /dev/null +++ b/mongodb/example/src/main/java/example/springdata/mongodb/projections/CustomerSummary.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015-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.projections; + +import org.springframework.beans.factory.annotation.Value; + +/** + * @author Oliver Gierke + */ +interface CustomerSummary { + + @Value("#{target.firstname + ' ' + target.lastname}") + String getFullName(); +} diff --git a/mongodb/example/src/test/java/example/springdata/mongodb/projections/CustomerRepositoryIntegrationTest.java b/mongodb/example/src/test/java/example/springdata/mongodb/projections/CustomerRepositoryIntegrationTest.java new file mode 100644 index 00000000..5a4d7b20 --- /dev/null +++ b/mongodb/example/src/test/java/example/springdata/mongodb/projections/CustomerRepositoryIntegrationTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015-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.projections; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Collection; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.projection.TargetAware; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests for {@link CustomerRepository} to show projection capabilities. + * + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration +@Transactional +public class CustomerRepositoryIntegrationTest { + + @Configuration + @EnableAutoConfiguration + static class Config {} + + @Autowired CustomerRepository customers; + + Customer dave, carter; + + @Before + public void setUp() { + + customers.deleteAll(); + + this.dave = customers.save(new Customer("Dave", "Matthews")); + this.carter = customers.save(new Customer("Carter", "Beauford")); + } + + @Test + public void projectsEntityIntoInterface() { + + Collection result = customers.findAllProjectedBy(); + + assertThat(result, hasSize(2)); + assertThat(result.iterator().next().getFirstname(), is("Dave")); + } + + @Test + public void projectsToDto() { + + Collection result = customers.findAllDtoedBy(); + + assertThat(result, hasSize(2)); + assertThat(result.iterator().next().getFirstname(), is("Dave")); + } + + @Test + public void projectsDynamically() { + + Collection result = customers.findByFirstname("Dave", CustomerProjection.class); + + assertThat(result, hasSize(1)); + assertThat(result.iterator().next().getFirstname(), is("Dave")); + } + + @Test + public void projectsIndividualDynamically() { + + CustomerSummary result = customers.findProjectedById(dave.getId(), CustomerSummary.class); + + assertThat(result, is(notNullValue())); + assertThat(result.getFullName(), is("Dave Matthews")); + + // Proxy backed by original instance as the projection uses dynamic elements + assertThat(((TargetAware) result).getTarget(), is(instanceOf(Customer.class))); + } + + @Test + public void projectIndividualInstance() { + + CustomerProjection result = customers.findProjectedById(dave.getId()); + + assertThat(result, is(notNullValue())); + assertThat(result.getFirstname(), is("Dave")); + assertThat(((TargetAware) result).getTarget(), is(instanceOf(Customer.class))); + } +} diff --git a/mongodb/pom.xml b/mongodb/pom.xml index eee53492..0c2854e2 100644 --- a/mongodb/pom.xml +++ b/mongodb/pom.xml @@ -33,7 +33,7 @@ - com.mysema.querydsl + com.querydsl querydsl-mongodb ${querydsl.version}