HHH-11186 - Add examples for all Hibernate annotations
Document more annotations: - @DynamicInsert - @DynamicUpdate - OptimisticLockType - @SelectBeforeUpdate
This commit is contained in:
parent
d215abbaea
commit
4960da8471
|
@ -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`#
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
====
|
||||||
|
|
|
@ -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]
|
|
@ -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]
|
|
@ -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[]
|
||||||
|
}
|
|
@ -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[]
|
||||||
|
}
|
Loading…
Reference in New Issue