Migrate User Guide Fetching chapter extras to test folder

This commit is contained in:
vladmihalcea 2016-01-27 16:26:20 +02:00
parent 418b7fe926
commit d30bdcef0d
11 changed files with 283 additions and 116 deletions

View File

@ -1,6 +1,6 @@
[[fetching]]
== Fetching
:sourcedir: extras
:sourcedir: ../../../../../test/java/org/hibernate/userguide/fetching
Fetching, essentially, is the process of grabbing data from the database and making it available to the application.
Tuning how an application does fetching is one of the biggest factors in determining how an application will perform.
@ -9,6 +9,7 @@ adds unnecessary overhead in terms of both JDBC communication and ResultSet proc
Fetching too little data might cause additional fetching to be needed.
Tuning how an application fetches data presents a great opportunity to influence the application's overall performance.
[[fetching-basics]]
=== The basics
The concept of fetching breaks down into two different questions.
@ -23,37 +24,37 @@ The concept of fetching breaks down into two different questions.
There are a number of scopes for defining fetching:
_static_:: Static definition of fetching strategies is done in the mappings.
The statically-defined fetch strategies is used in the absence of any dynamically defined strategies
_static_::
Static definition of fetching strategies is done in the mappings.
The statically-defined fetch strategies is used in the absence of any dynamically defined strategies
SELECT:::
Performs a separate SQL select to load the data. This can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).
This is the strategy generally termed N+1.
Performs a separate SQL select to load the data. This can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).
This is the strategy generally termed N+1.
JOIN:::
Inherently an EAGER style of fetching. The data to be fetched is obtained through the use of an SQL outer join.
Inherently an EAGER style of fetching. The data to be fetched is obtained through the use of an SQL outer join.
BATCH:::
Performs a separate SQL select to load a number of related data items using an IN-restriction as part of the SQL WHERE-clause based on a batch size.
Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).
Performs a separate SQL select to load a number of related data items using an IN-restriction as part of the SQL WHERE-clause based on a batch size.
Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).
SUBSELECT:::
Performs a separate SQL select to load associated data based on the SQL restriction used to load the owner.
Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).
_dynamic_ (sometimes referred to as runtime):: Dynamic definition is really use-case centric. There are multiple ways to define dynamic fetching:
Performs a separate SQL select to load associated data based on the SQL restriction used to load the owner.
Again, this can either be EAGER (the second select is issued immediately) or LAZY (the second select is delayed until the data is needed).
_dynamic_ (sometimes referred to as runtime)::
Dynamic definition is really use-case centric. There are multiple ways to define dynamic fetching:
_fetch profiles_::: defined in mappings, but can be enabled/disabled on the `Session`.
HQL/JPQL::: and both Hibernate and JPA Criteria queries have the ability to specify fetching, specific to said query.
entity graphs::: Starting in Hibernate 4.2 (JPA 2.1) this is also an option.
[[fetching-strategies]]
=== Applying fetch strategies
Let's consider these topics as it relates to an simple domain model and a few use cases.
[[fetching-strategies-domain-model-example]]
.Sample domain model
====
[source,java]
[source, JAVA, indent=0]
----
include::{sourcedir}/Employee.java[]
include::{sourcedir}/Department.java[]
include::{sourcedir}/Project.java[]
include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-domain-model-example]
----
====
@ -64,72 +65,88 @@ This is unfortunately at odds with the JPA specification which defines that all
Hibernate, as a JPA provider, honors that default.
====
[[fetching-strategies-no-fetching]]
=== No fetching
For the first use case, consider the application's login process for an `Employee`.
Let's assume that login only requires access to the `Employee` information, not `Project` nor `Department` information.
[[fetching-strategies-no-fetching-example]]
.No fetching example
====
[source,java]
[source, JAVA, indent=0]
----
include::{sourcedir}/Login.java[]
include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-no-fetching-example]
----
====
In this example, the application gets the `Employee` data.
However, because all associations from `Employee `are declared as LAZY (JPA defines the default for collections as LAZY) no other data is fetched.
If the login process does not need access to the `Employee `information specifically, another fetching optimization here would be to limit the width of the query results.
If the login process does not need access to the `Employee` information specifically, another fetching optimization here would be to limit the width of the query results.
[[fetching-strategies-no-fetching-scalar-example]]
.No fetching (scalar) example
====
[source,java]
[source, JAVA, indent=0]
----
include::{sourcedir}/LoginScalar.java[]
include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-no-fetching-scalar-example]
----
====
[[fetching-strategies-dynamic-fetching]]
=== Dynamic fetching via queries
For the second use case, consider a screen displaying the `Projects` for an `Employee`.
Certainly access to the `Employee `is needed, as is the collection of `Projects` for that Employee. Information about `Departments`, other `Employees` or other `Projects` is not needed.
[[fetching-strategies-dynamic-fetching-jpql-example]]
.Dynamic JPQL fetching example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-dynamic-fetching-jpql-example]
----
====
[[fetching-strategies-dynamic-fetching-criteria-example]]
.Dynamic query fetching example
====
[source,java]
[source, JAVA, indent=0]
----
include::{sourcedir}/ProjectsForAnEmployeeHql.java[]
----
[source,java]
----
include::{sourcedir}/ProjectsForAnEmployeeCriteria.java[]
include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-dynamic-fetching-criteria-example]
----
====
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.
[[fetching-strategies-dynamic-fetching-profile]]
=== Dynamic fetching via profiles
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.
So we would leverage a fetch profile.
[[fetching-strategies-dynamic-fetching-profile-example]]
.Fetch profile example
====
[source,java]
[source, JAVA, indent=0]
----
include::{sourcedir}/FetchOverrides.java[]
include::{sourcedir}/FetchingTest.java[tags=fetching-strategies-dynamic-fetching-profile-mapping-example]
----
[source,java]
[source, JAVA, indent=0]
----
include::{sourcedir}/ProjectsForAnEmployeeFetchProfile.java[]
include::{sourcedir}/FetchingTest.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.
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.
[[fetching-strategies-dynamic-fetching-entity-graph]]
=== Dynamic fetching via JPA entity graph
TODO

View File

@ -1,11 +0,0 @@
@Entity
public class Department {
@Id
private Long id;
@OneToMany( mappedBy = "department" )
private List<Employees> employees;
...
}

View File

@ -1,25 +0,0 @@
@Entity
public class Employee {
@Id
private Long id;
@NaturalId
private String userid;
@Column( name = "pswd" )
@ColumnTransformer( read = "decrypt( pswd )"write= "encrypt(?)")
private String password;
private int accessLevel;
@ManyToOne( fetch = LAZY )
@JoinColumn
private Department department;
@ManyToMany( mappedBy = "employees" )
@JoinColumn
private Set<Project> projects;
...
}

View File

@ -1,10 +0,0 @@
@FetchProfile(
name = "employee.projects",
fetchOverrides = {
@FetchOverride(
entity = Employee.class,
association = "projects",
mode = JOIN
)
}
)

View File

@ -1,4 +0,0 @@
Employee employee=( Employee )session.createQuery(
"select e from Employee e where e.userid = :userid and e.password = :password" )
...
.uniqueResult();

View File

@ -1,4 +0,0 @@
Employee employee = ( Employee )session.createQuery(
"select e.accessLevel from Employee e where e.userid = :userid and e.password = :password" )
...
.uniqueResult();

View File

@ -1,11 +0,0 @@
@Entity
public class Project {
@Id
private Long id;
@ManyToMany
private Set<Employee> employees;
...
}

View File

@ -1,10 +0,0 @@
String userid = ...;
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee>criteria = cb.createQuery( Employee.class );
Root<Employee>root = criteria.from( Employee.class );
root.fetch( Employee_.projects );
criteria.select( root );
criteria.where(
cb.equal( root.get(Employee_.userid ), cb.literal( userid ) )
);
Employee e = entityManager.createQuery( criteria ).getSingleResult();

View File

@ -1,3 +0,0 @@
String userid = ...;
session.enableFetchProfile( "employee.projects" );
Employee e = ( Employee ) session.bySimpleNaturalId( Employee.class ).load( userid );

View File

@ -1,5 +0,0 @@
String userid = ...;
Employee e = ( Employee )session.createQuery(
"select e from Employee e join fetch e.projects where e.userid = :userid" )
.setParameter( "userid", userid )
.uniqueResult();

View File

@ -0,0 +1,233 @@
/*
* 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 javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Root;
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.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author Vlad Mihalcea
*/
public class FetchingTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( FetchingTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Department.class,
Employee.class,
Project.class
};
}
@Test
public void testFlushSQL() {
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";
//tag::fetching-strategies-no-fetching-example[]
Employee employee = entityManager.createQuery(
"select e " +
"from Employee e " +
"where " +
" e.username = :username and " +
" e.password = :password",
Employee.class)
.setParameter( "username", username)
.setParameter( "password", password)
.getSingleResult();
//end::fetching-strategies-no-fetching-example[]
assertNotNull(employee);
} );
doInJPA( this::entityManagerFactory, entityManager -> {
String username = "user1";
String password = "3fabb4de8f1ee2e97d7793bab2db1116";
//tag::fetching-strategies-no-fetching-scalar-example[]
Integer accessLevel = entityManager.createQuery(
"select e.accessLevel " +
"from Employee e " +
"where " +
" e.username = :username and " +
" e.password = :password",
Integer.class)
.setParameter( "username", username)
.setParameter( "password", password)
.getSingleResult();
//end::fetching-strategies-no-fetching-scalar-example[]
assertEquals( Integer.valueOf(0), accessLevel);
} );
doInJPA( this::entityManagerFactory, entityManager -> {
String username = "user1";
String password = "3fabb4de8f1ee2e97d7793bab2db1116";
//tag::fetching-strategies-dynamic-fetching-jpql-example[]
Employee employee = entityManager.createQuery(
"select e " +
"from Employee e " +
"left join fetch e.projects " +
"where " +
" e.username = :username and " +
" e.password = :password",
Employee.class)
.setParameter( "username", username)
.setParameter( "password", password)
.getSingleResult();
//end::fetching-strategies-dynamic-fetching-jpql-example[]
assertNotNull(employee);
} );
doInJPA( this::entityManagerFactory, entityManager -> {
String username = "user1";
String password = "3fabb4de8f1ee2e97d7793bab2db1116";
//tag::fetching-strategies-dynamic-fetching-criteria-example[]
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> query = builder.createQuery( Employee.class );
Root<Employee> root = query.from( Employee.class );
root.fetch( "projects", JoinType.LEFT);
query.select(root).where(
builder.and(
builder.equal(root.get("username"), username),
builder.equal(root.get("password"), password)
)
);
Employee employee = entityManager.createQuery( query ).getSingleResult();
//end::fetching-strategies-dynamic-fetching-criteria-example[]
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[]
@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
}
//end::fetching-strategies-domain-model-example[]
}