Document entity graphs in Fetching chapter

This commit is contained in:
vladmihalcea 2016-01-28 10:24:23 +02:00
parent 5e5ae9dacb
commit 03eb9fd276
4 changed files with 331 additions and 39 deletions

View File

@ -121,8 +121,33 @@ include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-dynamic-fetching
In this example we have an `Employee `and their `Projects` loaded in a single query shown both as an HQL query and a JPA Criteria query. In this example we have an `Employee `and their `Projects` loaded in a single query shown both as an HQL query and a JPA Criteria query.
In both cases, this resolves to exactly one database query to get all that information. In both cases, this resolves to exactly one database query to get all that information.
[[fetching-strategies-dynamic-fetching-entity-graph]]
=== Dynamic fetching via JPA entity graph
JPA 2.1 introduced entity graphs so the application developer has more control over fetch plans.
[[fetching-strategies-dynamic-fetching-entity-graph-example]]
.Fetch graph example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/GraphFetchingTest.java[tags=fetching-strategies-dynamic-fetching-entity-graph-mapping-example]
----
[source, JAVA, indent=0]
----
include::{sourcedir}/GraphFetchingTest.java[tags=fetching-strategies-dynamic-fetching-entity-graph-example]
----
====
[NOTE]
====
Entity graphs are the way to override the EAGER fetching associations at runtime.
With JPQL, if an EAGER association is omitted, Hibernate will issue a secondary select for every association needed to be fetched eagerly.
====
[[fetching-strategies-dynamic-fetching-profile]] [[fetching-strategies-dynamic-fetching-profile]]
=== Dynamic fetching via profiles === Dynamic fetching via Hibernate profiles
Suppose we wanted to leverage loading by natural-id to obtain the `Employee` information in the "projects for and employee" use-case. Suppose we wanted to leverage loading by natural-id to obtain the `Employee` information in the "projects for and employee" use-case.
Loading by natural-id uses the statically defined fetching strategies, but does not expose a means to define load-specific fetching. Loading by natural-id uses the statically defined fetching strategies, but does not expose a means to define load-specific fetching.
@ -133,20 +158,15 @@ So we would leverage a fetch profile.
==== ====
[source, JAVA, indent=0] [source, JAVA, indent=0]
---- ----
include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-dynamic-fetching-profile-mapping-example] include::{sourcedir}/ProfileFetchingTest.java[tags=fetching-strategies-dynamic-fetching-profile-mapping-example]
---- ----
[source, JAVA, indent=0] [source, JAVA, indent=0]
---- ----
include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-dynamic-fetching-profile-example] include::{sourcedir}/ProfileFetchingTest.java[tags=fetching-strategies-dynamic-fetching-profile-example]
---- ----
==== ====
Here the `Employee `is obtained by natural-id lookup and the Employee's `Project` data is fetched eagerly. Here the `Employee `is obtained by natural-id lookup and the Employee's `Project` data is fetched eagerly.
If the `Employee `data is resolved from cache, the `Project` data is resolved on its own. If the `Employee `data is resolved from cache, the `Project` data is resolved on its own.
However, if the `Employee` data is not resolved in cache, the `Employee `and `Project` data is resolved in one SQL query via join as we saw above. However, if the `Employee` data is not resolved in cache, the `Employee `and `Project` data is resolved in one SQL query via join as we saw above.
[[fetching-strategies-dynamic-fetching-entity-graph]]
=== Dynamic fetching via JPA entity graph
TODO

View File

@ -45,14 +45,14 @@ public class FetchingTest extends BaseEntityManagerFunctionalTestCase {
@Override @Override
protected Class<?>[] getAnnotatedClasses() { protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { return new Class<?>[] {
Department.class, Department.class,
Employee.class, Employee.class,
Project.class Project.class
}; };
} }
@Test @Test
public void testFlushSQL() { public void test() {
doInJPA( this::entityManagerFactory, entityManager -> { doInJPA( this::entityManagerFactory, entityManager -> {
Department department = new Department(); Department department = new Department();
department.id = 1L; department.id = 1L;
@ -150,20 +150,6 @@ public void testFlushSQL() {
//end::fetching-strategies-dynamic-fetching-criteria-example[] //end::fetching-strategies-dynamic-fetching-criteria-example[]
assertNotNull(employee); assertNotNull(employee);
} ); } );
doInJPA( this::entityManagerFactory, entityManager -> {
String username = "user1";
String password = "3fabb4de8f1ee2e97d7793bab2db1116";
Session session = entityManager.unwrap( Session.class );
//tag::fetching-strategies-dynamic-fetching-profile-example[]
session.enableFetchProfile( "employee.projects" );
Employee employee = session.bySimpleNaturalId( Employee.class ).load( username );
//end::fetching-strategies-dynamic-fetching-profile-example[]
assertNotNull(employee);
} );
} }
//tag::fetching-strategies-domain-model-example[] //tag::fetching-strategies-domain-model-example[]
@ -179,19 +165,7 @@ public static class Department {
//Getters and setters omitted for brevity //Getters and setters omitted for brevity
} }
//tag::fetching-strategies-dynamic-fetching-profile-mapping-example[]
@Entity(name = "Employee") @Entity(name = "Employee")
@FetchProfile(
name = "employee.projects",
fetchOverrides = {
@FetchProfile.FetchOverride(
entity = Employee.class,
association = "projects",
mode = FetchMode.JOIN
)
}
)
//end::fetching-strategies-dynamic-fetching-profile-mapping-example[]
public static class Employee { public static class Employee {
@Id @Id

View File

@ -0,0 +1,147 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.userguide.fetching;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.OneToMany;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.NaturalId;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
import static org.junit.Assert.assertNotNull;
/**
* @author Vlad Mihalcea
*/
public class GraphFetchingTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( GraphFetchingTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Department.class,
Employee.class,
Project.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Department department = new Department();
department.id = 1L;
entityManager.persist( department );
Employee employee1 = new Employee();
employee1.id = 1L;
employee1.username = "user1";
employee1.password = "3fabb4de8f1ee2e97d7793bab2db1116";
employee1.accessLevel = 0;
employee1.department = department;
entityManager.persist( employee1 );
Employee employee2 = new Employee();
employee2.id = 2L;
employee2.username = "user2";
employee2.password = "3fabb4de8f1ee2e97d7793bab2db1116";
employee2.accessLevel = 1;
employee2.department = department;
entityManager.persist( employee2 );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Long userId = 1L;
//tag::fetching-strategies-dynamic-fetching-entity-graph-example[]
Employee employee = entityManager.find(
Employee.class,
userId,
Collections.singletonMap(
"javax.persistence.fetchgraph",
entityManager.getEntityGraph( "employee.projects" )
)
);
//end::fetching-strategies-dynamic-fetching-entity-graph-example[]
assertNotNull(employee);
} );
}
@Entity(name = "Department")
public static class Department {
@Id
private Long id;
@OneToMany(mappedBy = "department")
private List<Employee> employees = new ArrayList<>();
//Getters and setters omitted for brevity
}
//tag::fetching-strategies-dynamic-fetching-entity-graph-mapping-example[]
@Entity(name = "Employee")
@NamedEntityGraph(name = "employee.projects",
attributeNodes = @NamedAttributeNode("projects")
)
//end::fetching-strategies-dynamic-fetching-entity-graph-mapping-example[]
public static class Employee {
@Id
private Long id;
@NaturalId
private String username;
@Column(name = "pswd")
@ColumnTransformer(
read = "decrypt( 'AES', '00', pswd )",
write = "encrypt('AES', '00', ?)"
)
private String password;
private int accessLevel;
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
@ManyToMany(mappedBy = "employees")
private List<Project> projects = new ArrayList<>();
//Getters and setters omitted for brevity
}
@Entity(name = "Project")
public class Project {
@Id
private Long id;
@ManyToMany
private List<Employee> employees = new ArrayList<>();
//Getters and setters omitted for brevity
}
}

View File

@ -0,0 +1,151 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.userguide.fetching;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import org.hibernate.Session;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.FetchProfile;
import org.hibernate.annotations.NaturalId;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
import static org.junit.Assert.assertNotNull;
/**
* @author Vlad Mihalcea
*/
public class ProfileFetchingTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( ProfileFetchingTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Department.class,
Employee.class,
Project.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Department department = new Department();
department.id = 1L;
entityManager.persist( department );
Employee employee1 = new Employee();
employee1.id = 1L;
employee1.username = "user1";
employee1.password = "3fabb4de8f1ee2e97d7793bab2db1116";
employee1.accessLevel = 0;
employee1.department = department;
entityManager.persist( employee1 );
Employee employee2 = new Employee();
employee2.id = 2L;
employee2.username = "user2";
employee2.password = "3fabb4de8f1ee2e97d7793bab2db1116";
employee2.accessLevel = 1;
employee2.department = department;
entityManager.persist( employee2 );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
String username = "user1";
String password = "3fabb4de8f1ee2e97d7793bab2db1116";
Session session = entityManager.unwrap( Session.class );
//tag::fetching-strategies-dynamic-fetching-profile-example[]
session.enableFetchProfile( "employee.projects" );
Employee employee = session.bySimpleNaturalId( Employee.class ).load( username );
//end::fetching-strategies-dynamic-fetching-profile-example[]
assertNotNull(employee);
} );
}
@Entity(name = "Department")
public static class Department {
@Id
private Long id;
@OneToMany(mappedBy = "department")
private List<Employee> employees = new ArrayList<>();
//Getters and setters omitted for brevity
}
//tag::fetching-strategies-dynamic-fetching-profile-mapping-example[]
@Entity(name = "Employee")
@FetchProfile(
name = "employee.projects",
fetchOverrides = {
@FetchProfile.FetchOverride(
entity = Employee.class,
association = "projects",
mode = FetchMode.JOIN
)
}
)
//end::fetching-strategies-dynamic-fetching-profile-mapping-example[]
public static class Employee {
@Id
private Long id;
@NaturalId
private String username;
@Column(name = "pswd")
@ColumnTransformer(
read = "decrypt( 'AES', '00', pswd )",
write = "encrypt('AES', '00', ?)"
)
private String password;
private int accessLevel;
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
@ManyToMany(mappedBy = "employees")
private List<Project> projects = new ArrayList<>();
//Getters and setters omitted for brevity
}
@Entity(name = "Project")
public class Project {
@Id
private Long id;
@ManyToMany
private List<Employee> employees = new ArrayList<>();
//Getters and setters omitted for brevity
}
}