HHH-5037 - Improve documentation for immutable/read-only entity and immutable collection functionality
This commit is contained in:
parent
416fe7e6a8
commit
fc1a49b13b
|
@ -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[]
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
||||||
|
====
|
|
@ -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]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
|
@ -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'
|
|
@ -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[]
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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[]
|
||||||
|
}
|
|
@ -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[]
|
||||||
|
}
|
Loading…
Reference in New Issue