HHH-11186 - Add examples for all Hibernate annotations

Document more annotations:

- @BatchSize
- @Check
This commit is contained in:
Vlad Mihalcea 2016-11-10 18:44:13 +02:00
parent 0a2bb3c811
commit 78f8619a7d
7 changed files with 438 additions and 3 deletions

View File

@ -624,6 +624,8 @@ However, if this annotation is used with either value="property" or value="field
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/BatchSize.html[`@BatchSize`] annotation is used to specify the size for batch loading the entries of a lazy collection.
See the <<chapters/fetching/Fetching.adoc#fetching-batch, Batch fetching>> section for more info.
[[annotations-hibernate-cache]]
==== `@Cache`
@ -643,7 +645,9 @@ When combining both JPA and Hibernate `CascadeType` strategies, Hibernate will m
[[annotations-hibernate-check]]
==== `@Check`
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Check.html[`@Check`] annotation is used to specify an arbitrary SQL CHECK constraint which can be defined at the class, property or collection level.
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Check.html[`@Check`] annotation is used to specify an arbitrary SQL CHECK constraint which can be defined at the class level.
See the <<chapters/schema/Schema.adoc#schema-generation-database-checks,Database-level checks>> chapter for more info.
[[annotations-hibernate-collectionid]]
==== `@CollectionId`

View File

@ -1,6 +1,7 @@
[[fetching]]
== Fetching
:sourcedir: ../../../../../test/java/org/hibernate/userguide/fetching
:extrasdir: extras
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.
@ -169,4 +170,51 @@ include::{sourcedir}/ProfileFetchingTest.java[tags=fetching-strategies-dynamic-f
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.
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-batch]]
=== Batch fetching
Hibernate offers the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/BatchSize.html[`@BatchSize`] annotation,
which can be used when fetching uninitialized entity proxies.
Considering the following entity mapping:
[[fetching-batch-mapping-example]]
.`@BatchSize` mapping example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/BatchFetchingTest.java[tags=fetching-batch-mapping-example]
----
====
Considering that we have previously fetched several `Department` entities,
and now we need to initialize the `employees` entity collection for each particular `Department`,
the `@BatchSize` annotations allows us to load multiple `Employee` entities in a single database roundtrip.
[[fetching-batch-fetching-example]]
.`@BatchSize` fetching example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/BatchFetchingTest.java[tags=fetching-batch-fetching-example]
----
[source, SQL, indent=0]
----
include::{extrasdir}/fetching-batch-fetching-example.sql[]
----
====
As you can see in the example above, there are only two SQL statements used to fetch the `Employee` entities associated to multiple `Department` entities.
[TIP]
====
Without `@BatchSize`, you'd run into a N+1 query issue, so,
instead of 2 SQL statements, there would be 10 queries needed for fetching the `Employee` child entities.
However, although `@BatchSize` is better than running into an N+1 query issue,
most of the time, a DTO projection or a `JOIN FETCH` is a much better alternative since
it allows you to fetch all the required data with a single query.
====

View File

@ -0,0 +1,33 @@
SELECT
d.id as id1_0_
FROM
Department d
INNER JOIN
Employee employees1_
ON d.id=employees1_.department_id
SELECT
e.department_id as departme3_1_1_,
e.id as id1_1_1_,
e.id as id1_1_0_,
e.department_id as departme3_1_0_,
e.name as name2_1_0_
FROM
Employee e
WHERE
e.department_id IN (
0, 2, 3, 4, 5
)
SELECT
e.department_id as departme3_1_1_,
e.id as id1_1_1_,
e.id as id1_1_0_,
e.department_id as departme3_1_0_,
e.name as name2_1_0_
FROM
Employee e
WHERE
e.department_id IN (
6, 7, 8, 9, 1
)

View File

@ -38,6 +38,7 @@ include::{extrasdir}/sql-schema-generation-domain-model-example.sql[]
----
====
[[schema-generation-script-files]]
=== Importing script files
To customize the schema generation process, the `hibernate.hbm2ddl.import_files` configuration property must be used to provide other scripts files that Hibernate can use when the `SessionFactory` is started.
@ -68,6 +69,7 @@ If we configure Hibernate to import the script above:
Hibernate is going to execute the script file after the schema is automatically generated.
[[schema-generation-database-objects]]
=== Database objects
Hibernate allows you to customize the schema generation process via the HBM `database-object` element.
@ -83,5 +85,40 @@ include::{sourcedir}/SchemaGenerationTest.hbm.xml[]
----
====
When the `SessionFactory` is bootstrapped, Hibernate is going to execute the `database-object`, therefore creating the `sp_count_books` funtion.
When the `SessionFactory` is bootstrapped, Hibernate is going to execute the `database-object`, therefore creating the `sp_count_books` function.
[[schema-generation-database-checks]]
=== Database-level checks
Hibernate offers the `@Check` annotation so that you can specify an arbitrary SQL CHECK constraint which can be defined as follows:
[[schema-generation-database-checks-example]]
.Database check entity mapping example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/CheckTest.java[tag=schema-generation-database-checks-example]
----
====
Now, if you try to add a `Book` entity with an `isbn` attribute whose length is not 13 characters,
a `ConstraintViolationException` is going to be thrown.
[[stag::schema-generation-database-checks-persist-example]]
.Database check failure example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/CheckTest.java[tag=schema-generation-database-checks-persist-example]
----
[source, SQL, indent=0]
----
include::{extrasdir}/schema-generation-database-checks-persist-example.sql[]
----
====

View File

@ -0,0 +1,5 @@
INSERT INTO Book (isbn, price, title, id)
VALUES ('11-11-2016', 49.99, 'High-Performance Java Persistence', 1)
-- WARN SqlExceptionHelper:129 - SQL Error: 0, SQLState: 23514
-- ERROR SqlExceptionHelper:131 - ERROR: new row for relation "book" violates check constraint "book_isbn_check"

View File

@ -0,0 +1,135 @@
/*
* 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.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.NaturalId;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
/**
* @author Vlad Mihalcea
*/
public class BatchFetchingTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( BatchFetchingTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Department.class,
Employee.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
for ( long i = 0; i < 10; i++ ) {
Department department = new Department();
department.id = i;
entityManager.persist( department );
for ( int j = 0; j < Math.random() * 5; j++ ) {
Employee employee = new Employee();
employee.id = (i * 5) + j;
employee.name = String.format( "John %d", employee.getId() );
employee.department = department;
entityManager.persist( employee );
department.employees.add( employee );
}
entityManager.flush();
}
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::fetching-batch-fetching-example[]
List<Department> departments = entityManager.createQuery(
"select d " +
"from Department d " +
"inner join d.employees e " +
"where e.name like 'John%'", Department.class)
.getResultList();
for ( Department department : departments ) {
log.infof(
"Department %d has {} employees",
department.getId(),
department.getEmployees().size()
);
}
//end::fetching-batch-fetching-example[]
} );
}
//tag::fetching-batch-mapping-example[]
@Entity(name = "Department")
public static class Department {
@Id
private Long id;
@OneToMany(mappedBy = "department")
//@BatchSize(size = 5)
private List<Employee> employees = new ArrayList<>();
//Getters and setters omitted for brevity
//end::fetching-batch-mapping-example[]
public Long getId() {
return id;
}
public List<Employee> getEmployees() {
return employees;
}
//tag::fetching-batch-mapping-example[]
}
@Entity(name = "Employee")
public static class Employee {
@Id
private Long id;
@NaturalId
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
//Getters and setters omitted for brevity
//end::fetching-batch-mapping-example[]
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Department getDepartment() {
return department;
}
//tag::fetching-batch-mapping-example[]
}
//end::fetching-batch-mapping-example[]
}

View File

@ -0,0 +1,173 @@
/*
* 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.schema;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.PersistenceException;
import org.hibernate.annotations.Check;
import org.hibernate.annotations.NaturalId;
import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.RequiresDialect;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect( PostgreSQL81Dialect.class )
public class CheckTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
Book.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Book book = new Book();
book.setId( 0L );
book.setTitle( "Hibernate in Action" );
book.setPrice( 49.99d );
entityManager.persist( book );
} );
try {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::schema-generation-database-checks-persist-example[]
Book book = new Book();
book.setId( 1L );
book.setPrice( 49.99d );
book.setTitle( "High-Performance Java Persistence" );
book.setIsbn( "11-11-2016" );
entityManager.persist( book );
//end::schema-generation-database-checks-persist-example[]
} );
fail("Should fail because the ISBN is not of the right length!");
}
catch ( PersistenceException e ) {
assertEquals( ConstraintViolationException.class, e.getCause().getCause().getClass() );
}
try {
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person();
person.setId( 1L );
person.setName( "John Doe" );
person.setCode( 0L );
entityManager.persist( person );
} );
fail("Should fail because the code is 0!");
}
catch ( PersistenceException e ) {
assertEquals( ConstraintViolationException.class, e.getCause().getCause().getClass() );
}
}
@Entity(name = "Person")
@Check( constraints = "code > 0" )
public static class Person {
@Id
private Long id;
private String name;
// This one does not work! Only the entity-level annotation works.
// @Check( constraints = "code > 0" )
private Long code;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getCode() {
return code;
}
public void setCode(Long code) {
this.code = code;
}
}
//tag::schema-generation-database-checks-example[]
@Entity(name = "Book")
@Check( constraints = "CASE WHEN isbn IS NOT NULL THEN LENGTH(isbn) = 13 ELSE true END")
public static class Book {
@Id
private Long id;
private String title;
@NaturalId
private String isbn;
private Double price;
//Getters and setters omitted for brevity
//end::schema-generation-database-checks-example[]
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
//tag::schema-generation-database-checks-example[]
}
//end::schema-generation-database-checks-example[]
}