diff --git a/jpa/jpa21/README.md b/jpa/jpa21/README.md index 50718eb4..89be21ee 100644 --- a/jpa/jpa21/README.md +++ b/jpa/jpa21/README.md @@ -2,6 +2,28 @@ This project contains samples of JPA 2.1 specific features of Spring Data JPA. +## Support for declarative Fetch Graphs customization + +You can customize the loading of entity associations via EntityGraphs. JPA 2.1 provides the `NamedEntityGraph` annotation +that allows you define fetching behavior in a flexible way. + +In Spring Data JPA we support to specify which fetch-graph to use for a repository query method via the `EntityGraph` annotation. + +You can refer to a fetch graph by name like in the following example. +```java +@EntityGraph("product-with-tags") +Product findOneById(Long id); +``` + +We also offer an alternative and more concise way to declarativly specify a fetch graph for a repository query method in an +ad-hoc manner: +```java +@EntityGraph(attributePaths = "tags") +Product getOneById(Long id); +``` +By explicitly specifying which associations to fetch via the `attributePaths` attribute you don't need to specify a +`NamedEntityGraph` annotation on your entity :) + ## Support for stored procedure execution You can execute stored procedures either predefined using the JPA 2.1 mapping annotations or dynamically let the stored procedure definition be derived from the repository method name. diff --git a/jpa/jpa21/pom.xml b/jpa/jpa21/pom.xml index a446e7fc..ae798bfe 100644 --- a/jpa/jpa21/pom.xml +++ b/jpa/jpa21/pom.xml @@ -16,6 +16,10 @@ org.hibernate hibernate-entitymanager + + org.springframework.boot + spring-boot-autoconfigure + diff --git a/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/FetchGraphConfiguration.java b/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/FetchGraphConfiguration.java new file mode 100644 index 00000000..56b54d18 --- /dev/null +++ b/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/FetchGraphConfiguration.java @@ -0,0 +1,24 @@ +/* + * 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.fetchgraph; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Thomas Darimont + */ +@SpringBootApplication +class FetchGraphConfiguration {} diff --git a/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/Product.java b/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/Product.java new file mode 100644 index 00000000..019c079d --- /dev/null +++ b/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/Product.java @@ -0,0 +1,54 @@ +/* + * 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.fetchgraph; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedEntityGraphs; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Thomas Darimont + */ +@Data +@NoArgsConstructor +@Entity +@NamedEntityGraphs(@NamedEntityGraph(name = "product-with-tags", attributeNodes = { @NamedAttributeNode("tags") })) +public class Product { + + @Id @GeneratedValue// + Long id; + + String name; + + @ManyToMany(fetch = FetchType.LAZY, targetEntity = Tag.class, cascade = CascadeType.ALL)// + Set tags = new HashSet<>(); + + public Product(String name) { + this.name = name; + } +} diff --git a/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/ProductRepository.java b/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/ProductRepository.java new file mode 100644 index 00000000..1a552a9e --- /dev/null +++ b/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/ProductRepository.java @@ -0,0 +1,48 @@ +/* + * 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.fetchgraph; + +import javax.persistence.FetchType; +import javax.persistence.NamedEntityGraph; + +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * @author Thomas Darimont + */ +public interface ProductRepository extends JpaRepository { + + /** + * Here we use the {@link EntityGraph} annotation to specify that we want to use the {@link NamedEntityGraph} + * product-with-tags specified on the {@link Product} entity. + * + * @param id + * @return + */ + @EntityGraph("product-with-tags") + Product findWithNamedEntityGraphById(Long id); + + /** + * Here we use the {@link EntityGraph} annotation to specify that we want the {@link Product#tags} association which + * is marked as {@link FetchType#LAZY} to be fetched eagerly. + * + * @param id + * @return + */ + @EntityGraph(attributePaths = "tags") + Product findWithAdhocEntityGraphById(Long id); +} diff --git a/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/Tag.java b/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/Tag.java new file mode 100644 index 00000000..d2ebf600 --- /dev/null +++ b/jpa/jpa21/src/main/java/example/springdata/jpa/fetchgraph/Tag.java @@ -0,0 +1,41 @@ +/* + * 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.fetchgraph; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Thomas Darimont + */ +@Data +@NoArgsConstructor +@Entity +public class Tag { + + @Id @GeneratedValue// + Long id; + + String name; + + public Tag(String name) { + this.name = name; + } +} diff --git a/jpa/jpa21/src/test/java/example/springdata/jpa/fetchgraph/FetchGraphIntegrationTests.java b/jpa/jpa21/src/test/java/example/springdata/jpa/fetchgraph/FetchGraphIntegrationTests.java new file mode 100644 index 00000000..12d165e5 --- /dev/null +++ b/jpa/jpa21/src/test/java/example/springdata/jpa/fetchgraph/FetchGraphIntegrationTests.java @@ -0,0 +1,100 @@ +/* + * 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.fetchgraph; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Collections; + +import javax.persistence.EntityManager; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration test showing the usage of JPA 2.1 fetch graph support through Spring Data JPA repositories. + * + * @author Thomas Darimont + */ +@RunWith(SpringRunner.class) +@Transactional +@SpringBootTest(classes = FetchGraphConfiguration.class) +public class FetchGraphIntegrationTests { + + @Autowired EntityManager em; + + @Autowired ProductRepository repository; + + @Test + public void shouldFetchAssociationMarkedAsLazyViaNamedEntityFetchGraph() { + + Product xps = new Product("Dell XPS 15"); + Collections.addAll(xps.getTags(), new Tag("cool"), new Tag("macbook-killer"), new Tag("speed")); + + xps = repository.save(xps); + repository.flush(); + + em.detach(xps); + + Product loadedXps = repository.findById(xps.getId()).get(); + em.detach(loadedXps); + + try { + loadedXps.getTags().toString(); + fail("Expected LazyInitializationException to occur when trying to access uninitialized association 'tags'."); + } catch (Exception expected) { + System.out.println(expected.getMessage()); + } + + // here we use the findOneById that uses a NamedEntityGraph + Product loadedXpsWithFetchGraph = repository.findWithNamedEntityGraphById(xps.getId()); + + Assert.assertThat(loadedXpsWithFetchGraph.getTags(), hasSize(3)); + } + + @Test + public void shouldFetchAssociationMarkedAsLazyViaCustomEntityFetchGraph() { + + Product xps = new Product("Dell XPS 15"); + Collections.addAll(xps.getTags(), new Tag("cool"), new Tag("macbook-killer"), new Tag("speed")); + + xps = repository.save(xps); + repository.flush(); + + em.detach(xps); + + Product loadedXps = repository.findById(xps.getId()).get(); + em.detach(loadedXps); + + try { + loadedXps.getTags().toString(); + fail("Expected LazyInitializationException to occur when trying to access uninitialized association 'tags'."); + } catch (Exception expected) { + System.out.println(expected.getMessage()); + } + + // here we use getOneById which uses an ad-hoc declarative fetch graph definition + Product loadedXpsWithFetchGraph = repository.findWithAdhocEntityGraphById(xps.getId()); + + Assert.assertThat(loadedXpsWithFetchGraph.getTags(), hasSize(3)); + } +} diff --git a/jpa/jpa21/src/test/java/example/springdata/jpa/storedprocedures/UserRepositoryIntegrationTests.java b/jpa/jpa21/src/test/java/example/springdata/jpa/storedprocedures/UserRepositoryIntegrationTests.java index 65696f81..4501d8bb 100644 --- a/jpa/jpa21/src/test/java/example/springdata/jpa/storedprocedures/UserRepositoryIntegrationTests.java +++ b/jpa/jpa21/src/test/java/example/springdata/jpa/storedprocedures/UserRepositoryIntegrationTests.java @@ -15,8 +15,8 @@ */ package example.springdata.jpa.storedprocedures; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; import javax.persistence.EntityManager; import javax.persistence.ParameterMode; @@ -30,7 +30,7 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; /** - * Intergration test showing the usage of JPA 2.1 stored procedures support through Spring Data repositories. + * Integration test showing the usage of JPA 2.1 stored procedures support through Spring Data repositories. * * @author Thomas Darimont * @author Oliver Gierke