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::dynamic_model.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]
|
||||
----
|
||||
====
|
||||
|
||||
[[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.Oracle8iDialect;
|
||||
import org.hibernate.dialect.PostgreSQL81Dialect;
|
||||
import org.hibernate.jpa.QueryHints;
|
||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
import org.hibernate.type.StringType;
|
||||
import org.hibernate.userguide.model.AddressType;
|
||||
|
@ -2306,4 +2307,41 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
|
|||
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