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.
|
||||
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]]
|
||||
==== `@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.
|
||||
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]
|
||||
====
|
||||
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.
|
||||
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.
|
||||
+
|
||||
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]]
|
||||
==== `@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.
|
||||
|
||||
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]]
|
||||
==== [line-through]#`@Sort`#
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[[entity]]
|
||||
=== Entity types
|
||||
:sourcedir: extras
|
||||
:sourcedir: ../../../../../test/java/org/hibernate/userguide/locking
|
||||
:extrasdir: extras
|
||||
|
||||
.Usage of the word _entity_
|
||||
[NOTE]
|
||||
|
@ -99,7 +100,7 @@ The placement of the `@Id` annotation marks the <<chapters/domain/access.adoc#ac
|
|||
====
|
||||
[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]
|
||||
----
|
||||
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]
|
||||
----
|
||||
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]
|
||||
----
|
||||
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]
|
||||
----
|
||||
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]
|
||||
----
|
||||
include::{sourcedir}/entity/listing2.java[]
|
||||
include::{extrasdir}/entity/listing2.java[]
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{sourcedir}/entity/listing4.java[]
|
||||
include::{extrasdir}/entity/listing4.java[]
|
||||
----
|
||||
====
|
||||
|
||||
|
@ -199,7 +200,7 @@ Consider yet another case:
|
|||
====
|
||||
[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]
|
||||
----
|
||||
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]
|
||||
----
|
||||
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]
|
||||
----
|
||||
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]
|
||||
----
|
||||
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`
|
||||
* `java.sql.Timestamp`
|
||||
|
||||
.Version
|
||||
[[entity-pojo-optlock-version-example]]
|
||||
.`@Version` annotation mapping
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
include::{sourcedir}/entity/Version.java[]
|
||||
include::{extrasdir}/entity/Version.java[]
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{sourcedir}/entity/Timestamp.java[]
|
||||
include::{extrasdir}/entity/Timestamp.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".
|
||||
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.
|
||||
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:
|
||||
|
||||
`NONE`:: optimistic locking is disabled even if there is a `@Version` annotation present
|
||||
`VERSION` (the default):: 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.
|
||||
`NONE`::
|
||||
optimistic locking is disabled even if there is a `@Version` annotation present
|
||||
`VERSION` (the default)::
|
||||
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