HHH-10573 - Add discussion of @ValueGenerationType to UserGuide

This commit is contained in:
Vlad Mihalcea 2016-03-01 11:25:01 +02:00
parent 5e30c2bf16
commit 38a6f08897
5 changed files with 322 additions and 27 deletions

View File

@ -1,6 +1,7 @@
[[basic]]
=== Basic Types
:sourcedir: extras
:sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping
:extrasdir: extras
Basic value types usually map a single database column, to a single, non-aggregated Java type.
Hibernate provides a number of built-in basic types, which follow the natural mappings recommended by the JDBC specifications.
@ -96,7 +97,7 @@ Both of the following examples are ultimately the same.
====
[source,java]
----
include::{sourcedir}/basic/ex1.java[]
include::{extrasdir}/basic/ex1.java[]
----
====
@ -104,7 +105,7 @@ include::{sourcedir}/basic/ex1.java[]
====
[source,java]
----
include::{sourcedir}/basic/ex2.java[]
include::{extrasdir}/basic/ex2.java[]
----
====
@ -153,7 +154,7 @@ If that implicit naming rule does not meet your requirements, you can explicitly
====
[source,java]
----
include::{sourcedir}/basic/ExplicitColumnNaming.java[]
include::{extrasdir}/basic/ExplicitColumnNaming.java[]
----
====
@ -204,7 +205,7 @@ In these cases you must explicitly tell Hibernate the `BasicType` to use, via th
====
[source,java]
----
include::{sourcedir}/basic/explicitType.java[]
include::{extrasdir}/basic/explicitType.java[]
----
====
@ -235,12 +236,12 @@ The first approach is to directly implement the BasicType interface.
====
[source,java]
----
include::{sourcedir}/basic/FizzywigType1.java[]
include::{extrasdir}/basic/FizzywigType1.java[]
----
[source,java]
----
include::{sourcedir}/basic/FizzywigType1_reg.java[]
include::{extrasdir}/basic/FizzywigType1_reg.java[]
----
====
@ -250,12 +251,12 @@ The second approach is to implement the UserType interface.
====
[source,java]
----
include::{sourcedir}/basic/FizzywigType2.java[]
include::{extrasdir}/basic/FizzywigType2.java[]
----
[source,java]
----
include::{sourcedir}/basic/FizzywigType2_reg.java[]
include::{extrasdir}/basic/FizzywigType2_reg.java[]
----
====
@ -277,7 +278,7 @@ The original JPA-compliant way to map enums was via the `@Enumerated` and `@MapK
====
[source,java]
----
include::{sourcedir}/basic/EnumeratedOrdinal.java[]
include::{extrasdir}/basic/EnumeratedOrdinal.java[]
----
====
@ -291,7 +292,7 @@ In the ORDINAL example, the gender column is defined as an (nullable) INTEGER ty
====
[source,java]
----
include::{sourcedir}/basic/EnumeratedString.java[]
include::{extrasdir}/basic/EnumeratedString.java[]
----
====
@ -311,7 +312,7 @@ Let's revisit the Gender enum example, but instead we want to store the more sta
====
[source,java]
----
include::{sourcedir}/basic/EnumAttributeConverter.java[]
include::{extrasdir}/basic/EnumAttributeConverter.java[]
----
====
@ -335,7 +336,7 @@ Let's again revisit the Gender enum example, this time using a custom Type to st
====
[source,java]
----
include::{sourcedir}/basic/EnumCustomType.java[]
include::{extrasdir}/basic/EnumCustomType.java[]
----
====
@ -376,7 +377,7 @@ For a first look, let's assume we have a CLOB column that we would like to map (
====
[source,sql]
----
include::{sourcedir}/basic/Clob.sql[]
include::{extrasdir}/basic/Clob.sql[]
----
====
@ -386,7 +387,7 @@ Let's first map this using the JDBC locator.
====
[source,java]
----
include::{sourcedir}/basic/ClobLocator.java[]
include::{extrasdir}/basic/ClobLocator.java[]
----
====
@ -396,7 +397,7 @@ We could also map a materialized form.
====
[source,java]
----
include::{sourcedir}/basic/ClobMaterialized.java[]
include::{extrasdir}/basic/ClobMaterialized.java[]
----
====
@ -414,7 +415,7 @@ We might even want the materialized data as a char array (for some crazy reason)
====
[source,java]
----
include::{sourcedir}/basic/ClobMaterializedCharArray.java[]
include::{extrasdir}/basic/ClobMaterializedCharArray.java[]
----
====
@ -424,7 +425,7 @@ We'd map BLOB data in a similar fashion.
====
[source,sql]
----
include::{sourcedir}/basic/Blob.sql[]
include::{extrasdir}/basic/Blob.sql[]
----
====
@ -434,7 +435,7 @@ Let's first map this using the JDBC locator.
====
[source,java]
----
include::{sourcedir}/basic/BlobLocator.java[]
include::{extrasdir}/basic/BlobLocator.java[]
----
====
@ -444,7 +445,7 @@ We could also map a materialized BLOB form.
====
[source,java]
----
include::{sourcedir}/basic/BlobMaterialized.java[]
include::{extrasdir}/basic/BlobMaterialized.java[]
----
====
@ -465,7 +466,7 @@ To map a specific attribute to a nationalized variant data type, Hibernate defin
====
[source,java]
----
include::{sourcedir}/basic/NVARCHAR.java[]
include::{extrasdir}/basic/NVARCHAR.java[]
----
====
@ -473,7 +474,7 @@ include::{sourcedir}/basic/NVARCHAR.java[]
====
[source,java]
----
include::{sourcedir}/basic/NCLOB_locator.java[]
include::{extrasdir}/basic/NCLOB_locator.java[]
----
====
@ -481,7 +482,7 @@ include::{sourcedir}/basic/NCLOB_locator.java[]
====
[source,java]
----
include::{sourcedir}/basic/NCLOB_materialized.java[]
include::{extrasdir}/basic/NCLOB_materialized.java[]
----
====
@ -552,7 +553,7 @@ Considering the following entity:
====
[source,java]
----
include::{sourcedir}/basic/DateTemporal.java[]
include::{extrasdir}/basic/DateTemporal.java[]
----
====
@ -675,7 +676,7 @@ In the following example, the `java.util.Period` is going to be mapped to a `VAR
====
[source,java]
----
include::{sourcedir}/basic/PeriodStringConverter.java[]
include::{extrasdir}/basic/PeriodStringConverter.java[]
----
====
@ -685,7 +686,7 @@ To make use of this custom converter, the `@Convert` annotation must decorate th
====
[source,java]
----
include::{sourcedir}/basic/PeriodStringConvert.java[]
include::{extrasdir}/basic/PeriodStringConvert.java[]
----
====
@ -695,7 +696,7 @@ When persisting such entity, Hibernate will do the type conversion based on the
====
[source,sql]
----
include::{sourcedir}/basic/PeriodStringConvert.sql[]
include::{extrasdir}/basic/PeriodStringConvert.sql[]
----
====
@ -740,6 +741,68 @@ Only `@Version` and `@Basic` types can be marked as generated.
To mark a property as generated, use The Hibernate specific `@Generated` annotation.
[[mapping-generated-ValueGenerationType]]
===== @ValueGenerationType meta-annotation
Hibernate 4.3 introduced the `@ValueGenerationType` meta-annotation, which is a new approach to declaring generated attributes or customizing generators.
`@Generated` has been retrofitted to use the `@ValueGenerationType` meta-annotation.
But `@ValueGenerationType` exposes more features than what `@Generated` currently supports, and,
to leverage some of those features, you'd simply wire up a new generator annotation.
As you'll see in the following examples, the `@ValueGenerationType` meta-annotation is used when declaring the custom annotation used to mark the entity properties that need a specific generation strategy.
The actual generation logic must be implemented in class that implements the `AnnotationValueGeneration` interface.
[[mapping-database-generated-value]]
====== Database-generated values
For example, let's say we want the timestamps to be generated by calls to the standard ANSI SQL function `current_timestamp` (rather than triggers or DEFAULT values):
[[mapping-database-generated-value-example]]
.A `ValueGenerationType` mapping for database generation
====
[source, JAVA, indent=0]
----
include::{sourcedir}/generated/DatabaseValueGenerationTest.java[tags=mapping-database-generated-value-example]
----
====
When persisting an `Event` entity, Hibernate generates the following SQL statement:
====
[source, SQL, indent=0]
----
include::{extrasdir}/basic/mapping-database-generated-value-example.sql[]
----
====
As you can see, the `current_timestamp` value was used for assigning the `timestamp` column value.
[[mapping-in-memory-generated-value]]
====== In-memory-generated values
If the timestamp value needs to be generated in-memory, the following mapping must be used instead:
[[mapping-in-memory-generated-value-example]]
.A `ValueGenerationType` mapping for in-memory value generation
====
[source, JAVA, indent=0]
----
include::{sourcedir}/generated/InMemoryValueGenerationTest.java[tags=mapping-in-memory-generated-value-example]
----
====
When persisting an `Event` entity, Hibernate generates the following SQL statement:
====
[source, SQL, indent=0]
----
include::{extrasdir}/basic/mapping-in-memory-generated-value-example.sql[]
----
====
As you can see, the `new Date()` object value was used for assigning the `timestamp` column value.
[[mapping-column-read-and-write]]
==== Column transformers: read and write expressions

View File

@ -0,0 +1,2 @@
INSERT INTO Event ("timestamp", id)
VALUES (current_timestamp, 1)

View File

@ -0,0 +1,2 @@
INSERT INTO Event ("timestamp", id)
VALUES ('Tue Mar 01 10:58:18 EET 2016', 1)

View File

@ -0,0 +1,114 @@
/*
* 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.mapping.generated;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.annotations.ValueGenerationType;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.ValueGenerator;
import org.junit.Test;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
/**
* @author Vlad Mihalcea
*/
public class DatabaseValueGenerationTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Event.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Event dateEvent = new Event( );
entityManager.persist( dateEvent );
} );
}
//tag::mapping-database-generated-value-example[]
@Entity(name = "Event")
public static class Event {
@Id
@GeneratedValue
private Long id;
@Column(name = "`timestamp`")
@FunctionCreationTimestamp
private Date timestamp;
public Event() {}
public Long getId() {
return id;
}
public Date getTimestamp() {
return timestamp;
}
}
@ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface FunctionCreationTimestamp {}
public static class FunctionCreationValueGeneration
implements AnnotationValueGeneration<FunctionCreationTimestamp> {
@Override
public void initialize(FunctionCreationTimestamp annotation, Class<?> propertyType) {
}
/**
* Generate value on INSERT
* @return when to generate the value
*/
public GenerationTiming getGenerationTiming() {
return GenerationTiming.INSERT;
}
/**
* Returns null because the value is generated by the database.
* @return null
*/
public ValueGenerator<?> getValueGenerator() {
return null;
}
/**
* Returns true because the value is generated by the database.
* @return true
*/
public boolean referenceColumnInSql() {
return true;
}
/**
* Returns the database-generated value
* @return database-generated value
*/
public String getDatabaseGeneratedReferencedColumnValue() {
return "current_timestamp";
}
}
//end::mapping-database-generated-value-example[]
}

View File

@ -0,0 +1,114 @@
/*
* 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.mapping.generated;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.annotations.ValueGenerationType;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.ValueGenerator;
import org.junit.Test;
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
/**
* @author Vlad Mihalcea
*/
public class InMemoryValueGenerationTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Event.class
};
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Event dateEvent = new Event( );
entityManager.persist( dateEvent );
} );
}
//tag::mapping-in-memory-generated-value-example[]
@Entity(name = "Event")
public static class Event {
@Id
@GeneratedValue
private Long id;
@Column(name = "`timestamp`")
@FunctionCreationTimestamp
private Date timestamp;
public Event() {}
public Long getId() {
return id;
}
public Date getTimestamp() {
return timestamp;
}
}
@ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface FunctionCreationTimestamp {}
public static class FunctionCreationValueGeneration
implements AnnotationValueGeneration<FunctionCreationTimestamp> {
@Override
public void initialize(FunctionCreationTimestamp annotation, Class<?> propertyType) {
}
/**
* Generate value on INSERT
* @return when to generate the value
*/
public GenerationTiming getGenerationTiming() {
return GenerationTiming.INSERT;
}
/**
* Returns the in-memory generated value
* @return {@code true}
*/
public ValueGenerator<?> getValueGenerator() {
return (session, owner) -> new Date( );
}
/**
* Returns false because the value is generated by the database.
* @return false
*/
public boolean referenceColumnInSql() {
return false;
}
/**
* Returns null because the value is generated in-memory.
* @return null
*/
public String getDatabaseGeneratedReferencedColumnValue() {
return null;
}
}
//end::mapping-in-memory-generated-value-example[]
}