HHH-5037 - Improve documentation for immutable/read-only entity and immutable collection functionality

This commit is contained in:
Vlad Mihalcea 2016-10-12 13:12:57 +03:00
parent 416fe7e6a8
commit fc1a49b13b
9 changed files with 486 additions and 2 deletions

View File

@ -28,3 +28,4 @@ include::collections.adoc[]
include::natural_id.adoc[] include::natural_id.adoc[]
include::dynamic_model.adoc[] include::dynamic_model.adoc[]
include::inheritance.adoc[] include::inheritance.adoc[]
include::immutability.adoc[]

View File

@ -0,0 +1,10 @@
SELECT b.id AS id1_0_0_,
b.name AS name2_0_0_
FROM Batch b
WHERE b.id = 1
-- Change batch name
UPDATE batch
SET name = 'Proposed change request'
WHERE id = 1

View File

@ -0,0 +1,13 @@
SELECT e.id AS id1_0_0_,
e.createdOn AS createdO2_0_0_,
e.message AS message3_0_0_
FROM event e
WHERE e.id = 1
-- Change event message
SELECT e.id AS id1_0_0_,
e.createdOn AS createdO2_0_0_,
e.message AS message3_0_0_
FROM event e
WHERE e.id = 1

View File

@ -0,0 +1,111 @@
[[entity-immutability]]
=== Immutability
:sourcedir: ../../../../../test/java/org/hibernate/userguide/immutability
:extrasdir: extras/immutability
Immutability can be specified for both entities and collections.
==== Entity immutability
If a specific entity is immutable, it is good practice to mark it with the `@Immutable` annotation.
.Immutable entity
====
[source, JAVA, indent=0]
----
include::{sourcedir}/EntityImmutabilityTest.java[tags=entity-immutability-example]
----
====
Internally, Hibernate is going to perform several optimizations, such as:
- reducing memory footprint since there is no need to retain the dehydrated state for the dirty checking mechanism
- speeding-up the Persistence Context flushing phase since immutable entities can skip the dirty checking process
Considering the following entity is persisted in the database:
.Persisting an immutable entity
====
[source, JAVA, indent=0]
----
include::{sourcedir}/EntityImmutabilityTest.java[tags=entity-immutability-persist-example]
----
====
When loading the entity and trying to change its state,
Hibernate will skip any modification, therefore no SQL `UPDATE` statement is executed.
.The immutable entity ignores any update
====
[source, JAVA, indent=0]
----
include::{sourcedir}/EntityImmutabilityTest.java[tags=entity-immutability-update-example]
----
[source, SQL, indent=0]
----
include::{extrasdir}/entity-immutability-update-example.sql[]
----
====
==== Collection immutability
Just like entities, collections can also be marked with the `@Immutable` annotation.
Considering the following entity mappings:
.Immutable collection
====
[source, JAVA, indent=0]
----
include::{sourcedir}/CollectionImmutabilityTest.java[tags=collection-immutability-example]
----
====
This time, not only the `Event` entity is immutable, but the `Event` collection stored by the `Batch` parent entity.
Once the immutable collection is created, it can never be modified.
.Persisting an immutable collection
====
[source, JAVA, indent=0]
----
include::{sourcedir}/CollectionImmutabilityTest.java[tags=collection-immutability-persist-example]
----
====
The `Batch` entity is mutable. Only the `events` collection is immutable.
For instance, we can still modify the entity name:
.Changing the mutable entity
====
[source, JAVA, indent=0]
----
include::{sourcedir}/CollectionImmutabilityTest.java[tags=collection-entity-update-example]
----
[source, SQL, indent=0]
----
include::{extrasdir}/collection-entity-update-example.sql[]
----
====
However, when trying to modify the `events` collection:
.Immutable collections cannot be modified
====
[source, JAVA, indent=0]
----
include::{sourcedir}/CollectionImmutabilityTest.java[tags=collection-immutability-update-example]
----
[source, bash, indent=0]
----
include::{extrasdir}/collection-immutability-update-example.log[]
----
====
[TIP]
====
While immutable entity changes are simply discarded, modifying an immutable collection end up in a `HibernateException` being thrown.
====

View File

@ -1779,3 +1779,38 @@ Null values can be placed in front or at the end of the sorted set using `NULLS
include::{sourcedir}/HQLTest.java[tags=hql-order-by-example] include::{sourcedir}/HQLTest.java[tags=hql-order-by-example]
---- ----
==== ====
[[hql-read-only-entities]]
=== Read-only entities
As explained in <<chapters/domain/immutability.adoc#entity-immutability,entity immutability>> section, fetching entities in read-only mode is much more efficient than fetching read-write entities.
Even if the entities are mutable, you can still fetch them in read-only mode, and benefit from reducing the memory footprint and sepeding up the flushing process.
Read-only entities are skipped by the dirty checking mechanism as illustrated by the following example:
[[hql-read-only-entities-example]]
.Read-only entities query example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-read-only-entities-example]
----
[source, SQL, indent=0]
----
include::{extrasdir}/hql-read-only-entities-example.sql[]
----
====
As you can see, there is no SQL `UPDATE` being executed.
The Hibernate native API offers a `Query#setReadOnly` method, as an alternative to using a JPA query hint:
[[hql-read-only-entities-native-example]]
.Read-only entities native query example
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-read-only-entities-native-example]
----
====

View File

@ -0,0 +1,7 @@
SELECT c.id AS id1_5_ ,
c.duration AS duration2_5_ ,
c.phone_id AS phone_id4_5_ ,
c.call_timestamp AS call_tim3_5_
FROM phone_call c
INNER JOIN phone p ON c.phone_id = p.id
WHERE p.phone_number = '123-456-7890'

View File

@ -27,6 +27,7 @@ import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.MySQL5Dialect;
import org.hibernate.dialect.Oracle8iDialect; import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.dialect.PostgreSQL81Dialect;
import org.hibernate.jpa.QueryHints;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.type.StringType; import org.hibernate.type.StringType;
import org.hibernate.userguide.model.AddressType; import org.hibernate.userguide.model.AddressType;
@ -2306,4 +2307,41 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
assertEquals(1, personTotalCallDurations.size()); assertEquals(1, personTotalCallDurations.size());
}); });
} }
@Test
public void test_hql_read_only_entities_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-read-only-entities-example[]
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"join c.phone p " +
"where p.number = :phoneNumber ", Call.class )
.setParameter( "phoneNumber", "123-456-7890" )
.setHint( "org.hibernate.readOnly", true )
.getResultList();
calls.forEach( c -> c.setDuration( 0 ) );
//end::hql-read-only-entities-example[]
});
}
@Test
public void test_hql_read_only_entities_native_example() {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::hql-read-only-entities-native-example[]
List<Call> calls = entityManager.createQuery(
"select c " +
"from Call c " +
"join c.phone p " +
"where p.number = :phoneNumber ", Call.class )
.setParameter( "phoneNumber", "123-456-7890" )
.unwrap( org.hibernate.query.Query.class )
.setReadOnly( true )
.getResultList();
//end::hql-read-only-entities-native-example[]
});
}
} }

View File

@ -0,0 +1,165 @@
/*
* 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.immutability;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import org.hibernate.annotations.Immutable;
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 CollectionImmutabilityTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( CollectionImmutabilityTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Batch.class,
Event.class
};
}
@Test
public void test() {
//tag::collection-immutability-persist-example[]
doInJPA( this::entityManagerFactory, entityManager -> {
Batch batch = new Batch();
batch.setId( 1L );
batch.setName( "Change request" );
Event event1 = new Event();
event1.setId( 1L );
event1.setCreatedOn( new Date( ) );
event1.setMessage( "Update Hibernate User Guide" );
Event event2 = new Event();
event2.setId( 2L );
event2.setCreatedOn( new Date( ) );
event2.setMessage( "Update Hibernate Getting Started Guide" );
batch.getEvents().add( event1 );
batch.getEvents().add( event2 );
entityManager.persist( batch );
} );
//end::collection-immutability-persist-example[]
//tag::collection-entity-update-example[]
doInJPA( this::entityManagerFactory, entityManager -> {
Batch batch = entityManager.find( Batch.class, 1L );
log.info( "Change batch name" );
batch.setName( "Proposed change request" );
} );
//end::collection-entity-update-example[]
//tag::collection-immutability-update-example[]
try {
doInJPA( this::entityManagerFactory, entityManager -> {
Batch batch = entityManager.find( Batch.class, 1L );
batch.getEvents().clear();
} );
}
catch ( Exception e ) {
log.error( "Immutable collections cannot be modified" );
}
//end::collection-immutability-update-example[]
}
//tag::collection-immutability-example[]
@Entity(name = "Batch")
public static class Batch {
@Id
private Long id;
private String name;
@OneToMany(cascade = CascadeType.ALL)
@Immutable
private List<Event> events = new ArrayList<>( );
//Getters and setters are omitted for brevity
//end::collection-immutability-example[]
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 List<Event> getEvents() {
return events;
}
//tag::collection-immutability-example[]
}
@Entity(name = "Event")
@Immutable
public static class Event {
@Id
private Long id;
private Date createdOn;
private String message;
//Getters and setters are omitted for brevity
//end::collection-immutability-example[]
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
//tag::collection-immutability-example[]
}
//end::collection-immutability-example[]
}

View File

@ -0,0 +1,104 @@
/*
* 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.immutability;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Immutable;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Test;
import org.jboss.logging.Logger;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
public class EntityImmutabilityTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( EntityImmutabilityTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Event.class
};
}
@Test
public void test() {
//tag::entity-immutability-persist-example[]
doInJPA( this::entityManagerFactory, entityManager -> {
Event event = new Event();
event.setId( 1L );
event.setCreatedOn( new Date( ) );
event.setMessage( "Hibernate User Guide rocks!" );
entityManager.persist( event );
} );
//end::entity-immutability-persist-example[]
//tag::entity-immutability-update-example[]
doInJPA( this::entityManagerFactory, entityManager -> {
Event event = entityManager.find( Event.class, 1L );
log.info( "Change event message" );
event.setMessage( "Hibernate User Guide" );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Event event = entityManager.find( Event.class, 1L );
assertEquals("Hibernate User Guide rocks!", event.getMessage());
} );
//end::entity-immutability-update-example[]
}
//tag::entity-immutability-example[]
@Entity(name = "Event")
@Immutable
public static class Event {
@Id
private Long id;
private Date createdOn;
private String message;
//Getters and setters are omitted for brevity
//end::entity-immutability-example[]
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
//tag::entity-immutability-example[]
}
//end::entity-immutability-example[]
}