HHH-11186 - Add examples for all Hibernate annotations

Document more annotations:

- @DynamicInsert
- @DynamicUpdate
- OptimisticLockType
- @SelectBeforeUpdate
This commit is contained in:
Vlad Mihalcea 2016-11-15 18:41:35 +02:00
parent d215abbaea
commit 4960da8471
6 changed files with 402 additions and 25 deletions

View File

@ -722,6 +722,8 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern
By default, Hibernate uses a cached `INSERT` statement that sets all table columns. By default, Hibernate uses a cached `INSERT` statement that sets all table columns.
When the entity is annotated with the `@DynamicInsert` annotation, the `PreparedStatement` is going to include only the non-null columns. When the entity is annotated with the `@DynamicInsert` annotation, the `PreparedStatement` is going to include only the non-null columns.
See the <<chapters/domain/basic_types.adoc#mapping-generated-CreationTimestamp,`@CreationTimestamp` mapping>> section for more info on how `@DynamicInsert` works.
[[annotations-hibernate-dynamicupdate]] [[annotations-hibernate-dynamicupdate]]
==== `@DynamicUpdate` ==== `@DynamicUpdate`
@ -730,6 +732,8 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern
By default, Hibernate uses a cached `UPDATE` statement that sets all table columns. By default, Hibernate uses a cached `UPDATE` statement that sets all table columns.
When the entity is annotated with the `@DynamicUpdate` annotation, the `PreparedStatement` is going to include only the columns whose values have been changed. When the entity is annotated with the `@DynamicUpdate` annotation, the `PreparedStatement` is going to include only the columns whose values have been changed.
See the <<chapters/domain/entity.adoc#locking-optimistic-lock-type-dirty-example, `OptimisticLockType.DIRTY` mapping>> section for more info on how `@DynamicUpdate` works.
[NOTE] [NOTE]
==== ====
For reattachment of detached entities, the dynamic update is not possible without having the <<annotations-hibernate-selectbeforeupdate>> annotation as well. For reattachment of detached entities, the dynamic update is not possible without having the <<annotations-hibernate-selectbeforeupdate>> annotation as well.
@ -1010,8 +1014,8 @@ NONE:: The implicit optimistic locking mechanism is disabled.
VERSION:: The implicit optimistic locking mechanism is using a dedicated version column. VERSION:: The implicit optimistic locking mechanism is using a dedicated version column.
ALL:: The implicit optimistic locking mechanism is using *all* attributes as part of an expanded WHERE clause restriction for the `UPDATE` and `DELETE` SQL statements. ALL:: The implicit optimistic locking mechanism is using *all* attributes as part of an expanded WHERE clause restriction for the `UPDATE` and `DELETE` SQL statements.
DIRTY:: The implicit optimistic locking mechanism is using the *dirty* attributes (the attributes that were modified) as part of an expanded WHERE clause restriction for the `UPDATE` and `DELETE` SQL statements. DIRTY:: The implicit optimistic locking mechanism is using the *dirty* attributes (the attributes that were modified) as part of an expanded WHERE clause restriction for the `UPDATE` and `DELETE` SQL statements.
+
When using `DIRTY`, you should also add the <<annotations-hibernate-dynamicupdate>> annotation and also <<annotations-hibernate-selectbeforeupdate>> so that detached entities are properly handled as well. See the <<chapters/domain/entity.adoc#entity-pojo-optlock-versionless, Versionless optimistic locking>> section for more info.
[[annotations-hibernate-orderby]] [[annotations-hibernate-orderby]]
==== `@OrderBy` ==== `@OrderBy`
@ -1075,6 +1079,8 @@ According to Oracle documentation, `ROWID` is the fastest way to access a single
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SelectBeforeUpdate.html[`@SelectBeforeUpdate`] annotation is used to specify that the current annotated entity state be selected from the database when determining whether to perform an update when the detached entity is reattached. The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/SelectBeforeUpdate.html[`@SelectBeforeUpdate`] annotation is used to specify that the current annotated entity state be selected from the database when determining whether to perform an update when the detached entity is reattached.
See the <<chapters/domain/entity.adoc#locking-optimistic-lock-type-dirty-example, `OptimisticLockType.DIRTY` mapping>> section for more info on how `@SelectBeforeUpdate` works.
[[annotations-hibernate-sort]] [[annotations-hibernate-sort]]
==== [line-through]#`@Sort`# ==== [line-through]#`@Sort`#

View File

@ -1,6 +1,7 @@
[[entity]] [[entity]]
=== Entity types === Entity types
:sourcedir: extras :sourcedir: ../../../../../test/java/org/hibernate/userguide/locking
:extrasdir: extras
.Usage of the word _entity_ .Usage of the word _entity_
[NOTE] [NOTE]
@ -99,7 +100,7 @@ The placement of the `@Id` annotation marks the <<chapters/domain/access.adoc#ac
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/Identifier.java[] include::{extrasdir}/entity/Identifier.java[]
---- ----
==== ====
@ -116,7 +117,7 @@ By default, the entity name represents the unqualified name of the entity class
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/SimpleEntity.java[] include::{extrasdir}/entity/SimpleEntity.java[]
---- ----
==== ====
@ -129,7 +130,7 @@ To explicitly give the name of the table or to specify other information about t
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/SimpleEntityWithTable.java[] include::{extrasdir}/entity/SimpleEntityWithTable.java[]
---- ----
==== ====
@ -162,7 +163,7 @@ So if we ask a Hibernate `Session` to load that specific Person multiple times w
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/listing1.java[] include::{extrasdir}/entity/listing1.java[]
---- ----
==== ====
@ -172,7 +173,7 @@ Consider another example using a persistent `java.util.Set`:
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/listing3.java[] include::{extrasdir}/entity/listing3.java[]
---- ----
==== ====
@ -182,12 +183,12 @@ However, the semantic changes when we mix instances loaded from different Sessio
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/listing2.java[] include::{extrasdir}/entity/listing2.java[]
---- ----
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/listing4.java[] include::{extrasdir}/entity/listing4.java[]
---- ----
==== ====
@ -199,7 +200,7 @@ Consider yet another case:
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/listing5.java[] include::{extrasdir}/entity/listing5.java[]
---- ----
==== ====
@ -212,7 +213,7 @@ A common initial approach is to use the entity's identifier attribute as the bas
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/listing6.java[] include::{extrasdir}/entity/listing6.java[]
---- ----
==== ====
@ -222,7 +223,7 @@ It turns out that this still breaks when adding transient instance of `Person` t
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/listing7.java[] include::{extrasdir}/entity/listing7.java[]
---- ----
==== ====
@ -239,7 +240,7 @@ Another option is to force the identifier to be generated and set prior to addin
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/listing8.java[] include::{extrasdir}/entity/listing8.java[]
---- ----
==== ====
@ -251,7 +252,7 @@ The final approach is to use a "better" equals/hashCode implementation, making u
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/listing9.java[] include::{extrasdir}/entity/listing9.java[]
---- ----
==== ====
@ -281,33 +282,129 @@ According to JPA, the valid types for these attributes are limited to:
* `long` or `Long` * `long` or `Long`
* `java.sql.Timestamp` * `java.sql.Timestamp`
.Version [[entity-pojo-optlock-version-example]]
.`@Version` annotation mapping
==== ====
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/Version.java[] include::{extrasdir}/entity/Version.java[]
---- ----
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/Timestamp.java[] include::{extrasdir}/entity/Timestamp.java[]
---- ----
[source,java] [source,java]
---- ----
include::{sourcedir}/entity/Instant.java[] include::{extrasdir}/entity/Instant.java[]
---- ----
==== ====
[[entity-pojo-optlock-versionless]]
===== Versionless optimistic locking
Although the default `@Version` property optimistic locking mechanism is sufficient in many situations,
sometimes, you need rely on the actual database row column values to prevent *lost updates*.
Hibernate supports a form of optimistic locking that does not require a dedicated "version attribute". Hibernate supports a form of optimistic locking that does not require a dedicated "version attribute".
This is intended mainly for use with modeling legacy schemas. This is also useful for use with modeling legacy schemas.
The idea is that you can get Hibernate to perform "version checks" using either all of the entity's attributes, or just the attributes that have changed. The idea is that you can get Hibernate to perform "version checks" using either all of the entity's attributes, or just the attributes that have changed.
This is achieved through the use of the `org.hibernate.annotations.OptimisticLocking` annotation which defines a single attribute of type `org.hibernate.annotations.OptimisticLockType`. This is achieved through the use of the
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLocking.html[`@OptimisticLocking`]
annotation which defines a single attribute of type
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/OptimisticLockType.html[`org.hibernate.annotations.OptimisticLockType`].
There are 4 available OptimisticLockTypes: There are 4 available OptimisticLockTypes:
`NONE`:: optimistic locking is disabled even if there is a `@Version` annotation present `NONE`::
`VERSION` (the default):: performs optimistic locking based on a `@Version` as described above optimistic locking is disabled even if there is a `@Version` annotation present
`ALL`:: performs optimistic locking based on _all_ fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements `VERSION` (the default)::
`DIRTY`:: performs optimistic locking based on _dirty_ fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements. performs optimistic locking based on a `@Version` as described above
`ALL`::
performs optimistic locking based on _all_ fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements
`DIRTY`::
performs optimistic locking based on _dirty_ fields as part of an expanded WHERE clause restriction for the UPDATE/DELETE SQL statements.
[[entity-pojo-optlock-versionless-all]]
====== Versionless optimistic locking using `OptimisticLockType.ALL`
[[locking-optimistic-lock-type-all-example]]
.`OptimisticLockType.ALL` mapping example
====
[source,java]
----
include::{sourcedir}/OptimisticLockTypeAllTest.java[tag=locking-optimistic-lock-type-all-example,indent=0]
----
====
When you need to modify the `Person` entity above:
[[locking-optimistic-lock-type-all-update-example]]
.`OptimisticLockType.ALL` update example
====
[source,java]
----
include::{sourcedir}/OptimisticLockTypeAllTest.java[tag=locking-optimistic-lock-type-all-update-example,indent=0]
----
[source,SQL]
----
include::{extrasdir}/locking/locking-optimistic-lock-type-all-update-example.sql[]
----
====
As you can see, all the columns of the associated database row are used in the `WHERE` clause.
If any column has changed after the row was loaded, there won't be any match, and a `StaleStateException` or an `OptimisticLockException`
is going to be thrown.
[NOTE]
====
When using `OptimisticLockType.ALL`, you should also use `@DynamicUpdate` because the `UPDATE` statement must take into consideration all the entity property values.
====
[[entity-pojo-optlock-versionless-dirty]]
====== Versionless optimistic locking using `OptimisticLockType.DIRTY`
The `OptimisticLockType.DIRTY` differs from `OptimisticLockType.ALL`
in that it only takes into consideration the entity properties that have changed
since the entity was loaded in the currently running Persistence Context.
[[locking-optimistic-lock-type-dirty-example]]
.`OptimisticLockType.DIRTY` mapping example
====
[source,java]
----
include::{sourcedir}/OptimisticLockTypeDirtyTest.java[tag=locking-optimistic-lock-type-dirty-example,indent=0]
----
====
When you need to modify the `Person` entity above:
[[locking-optimistic-lock-type-dirty-update-example]]
.`OptimisticLockType.DIRTY` update example
====
[source,java]
----
include::{sourcedir}/OptimisticLockTypeDirtyTest.java[tag=locking-optimistic-lock-type-dirty-update-example,indent=0]
----
[source,SQL]
----
include::{extrasdir}/locking/locking-optimistic-lock-type-dirty-update-example.sql[]
----
====
This time, only the database column that has changed was used in the `WHERE` clause.
[NOTE]
====
The main advantage of `OptimisticLockType.DIRTY` over `OptimisticLockType.ALL`
and the default `OptimisticLockType.VERSION` used implicitly along with the `@Version` mapping,
is that it allows you to minimize the risk of `OptimisticLockException` across non-overlapping entity property changes.
When using `OptimisticLockType.DIRTY`, you should also use `@DynamicUpdate` because the `UPDATE` statement must take into consideration all the dirty entity property values,
and also the `@SelectBeforeUpdate` annotation so that detached entities are properly handled by the
https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/Session.html#update-java.lang.Object-[`Session#update(entity)`] operation.
====

View File

@ -0,0 +1,17 @@
UPDATE
Person
SET
city=?
WHERE
id=?
AND city=?
AND country=?
AND created_on=?
AND "name"=?
-- binding parameter [1] as [VARCHAR] - [Washington D.C.]
-- binding parameter [2] as [BIGINT] - [1]
-- binding parameter [3] as [VARCHAR] - [New York]
-- binding parameter [4] as [VARCHAR] - [US]
-- binding parameter [5] as [TIMESTAMP] - [2016-11-16 16:05:12.876]
-- binding parameter [6] as [VARCHAR] - [John Doe]

View File

@ -0,0 +1,11 @@
UPDATE
Person
SET
city=?
WHERE
id=?
and city=?
-- binding parameter [1] as [VARCHAR] - [Washington D.C.]
-- binding parameter [2] as [BIGINT] - [1]
-- binding parameter [3] as [VARCHAR] - [New York]

View File

@ -0,0 +1,122 @@
/*
* 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.locking;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.OptimisticLockType;
import org.hibernate.annotations.OptimisticLocking;
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 OptimisticLockTypeAllTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( OptimisticLockTypeAllTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person( );
person.setId( 1L );
person.setName( "John Doe" );
person.setCountry( "US" );
person.setCity( "New York" );
person.setCreatedOn( new Timestamp( System.currentTimeMillis() ) );
entityManager.persist( person );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::locking-optimistic-lock-type-all-update-example[]
Person person = entityManager.find( Person.class, 1L );
person.setCity( "Washington D.C." );
//end::locking-optimistic-lock-type-all-update-example[]
} );
}
//tag::locking-optimistic-lock-type-all-example[]
@Entity(name = "Person")
@OptimisticLocking(type = OptimisticLockType.ALL)
@DynamicUpdate
public static class Person {
@Id
private Long id;
@Column(name = "`name`")
private String name;
private String country;
private String city;
@Column(name = "created_on")
private Timestamp createdOn;
//Getters and setters are omitted for brevity
//end::locking-optimistic-lock-type-all-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 String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Timestamp getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Timestamp createdOn) {
this.createdOn = createdOn;
}
//tag::locking-optimistic-lock-type-all-example[]
}
//end::locking-optimistic-lock-type-all-example[]
}

View File

@ -0,0 +1,124 @@
/*
* 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.locking;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.OptimisticLockType;
import org.hibernate.annotations.OptimisticLocking;
import org.hibernate.annotations.SelectBeforeUpdate;
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 OptimisticLockTypeDirtyTest extends BaseEntityManagerFunctionalTestCase {
private static final Logger log = Logger.getLogger( OptimisticLockTypeDirtyTest.class );
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Person.class,
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person( );
person.setId( 1L );
person.setName( "John Doe" );
person.setCountry( "US" );
person.setCity( "New York" );
person.setCreatedOn( new Timestamp( System.currentTimeMillis() ) );
entityManager.persist( person );
} );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::locking-optimistic-lock-type-dirty-update-example[]
Person person = entityManager.find( Person.class, 1L );
person.setCity( "Washington D.C." );
//end::locking-optimistic-lock-type-dirty-update-example[]
} );
}
//tag::locking-optimistic-lock-type-dirty-example[]
@Entity(name = "Person")
@OptimisticLocking(type = OptimisticLockType.DIRTY)
@DynamicUpdate
@SelectBeforeUpdate
public static class Person {
@Id
private Long id;
@Column(name = "`name`")
private String name;
private String country;
private String city;
@Column(name = "created_on")
private Timestamp createdOn;
//Getters and setters are omitted for brevity
//end::locking-optimistic-lock-type-dirty-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 String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Timestamp getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Timestamp createdOn) {
this.createdOn = createdOn;
}
//tag::locking-optimistic-lock-type-dirty-example[]
}
//end::locking-optimistic-lock-type-dirty-example[]
}