HHH-8692 - Document value generation feature
This commit is contained in:
parent
475aba7c0f
commit
4e6f3a9753
|
@ -0,0 +1,276 @@
|
||||||
|
= Generated Values
|
||||||
|
:toc:
|
||||||
|
|
||||||
|
This guide discusses the Hibernate feature of generating non-identifier values. JPA allows for the generation of
|
||||||
|
identifier values. Beyond generation of identifier values, Hibernate allows for the generation of other types of
|
||||||
|
values. At a high level Hibernate supports both in-database and in-memory generation. In-database
|
||||||
|
refers to generation strategy where the value is actually generated in the database as part of an INSERT/UPDATE
|
||||||
|
execution, and must be re-read by SELECT afterwards to access the generated value. On the other hand, in-memory refers
|
||||||
|
to strategies where the value is generated in memory and then passed along to the database via the INSERT/UPDATE
|
||||||
|
statement.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== Legacy support (in-database generation)
|
||||||
|
|
||||||
|
Historically applications would have to manually refresh the state of any entities that included such generated values
|
||||||
|
to account for these generated values. Starting in version 3.2, however, Hibernate began allowing the aplication to
|
||||||
|
mark attributes as generated, which allows the application to delegate this responsibility to Hibernate. When
|
||||||
|
Hibernate issues an SQL INSERT or UPDATE for an entity that has defined in-database value generation, it immediately
|
||||||
|
issues a select afterwards to retrieve the generated values.
|
||||||
|
|
||||||
|
To mark an attribute as generated, applications used `@Generated`:
|
||||||
|
|
||||||
|
[[legacy-syntax-example]]
|
||||||
|
.Legacy Syntax
|
||||||
|
====
|
||||||
|
[source, JAVA]
|
||||||
|
----
|
||||||
|
@Entity
|
||||||
|
public class Document {
|
||||||
|
...
|
||||||
|
@Generated(INSERT)
|
||||||
|
private Date created;
|
||||||
|
@Generated(ALWAYS)
|
||||||
|
private Date lastModified;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
The assumption above is that the values of the 2 attributes are generated during the execution of the SQL INSERT/UPDATE
|
||||||
|
statements. `@Generated` tells Hibernate to read those values back after the INSERT/UPDATE execution.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== @ValueGenerationType meta-annotation
|
||||||
|
|
||||||
|
Version 4.3 expands on that support by adding a new approach to declaring generated attributes and even custom
|
||||||
|
generators via the `@ValueGenerationType` meta-annotation, which is used to annotate other annotations that describe
|
||||||
|
generated attributes. For discussion purposes, I here copy all the contracts used in value generation:
|
||||||
|
|
||||||
|
[source, JAVA]
|
||||||
|
----
|
||||||
|
@Target( value = ElementType.ANNOTATION_TYPE )
|
||||||
|
@Retention( RetentionPolicy.RUNTIME )
|
||||||
|
public @interface ValueGenerationType {
|
||||||
|
/**
|
||||||
|
* The type of value generation associated with the annotated value generator
|
||||||
|
* annotation type. The referenced generation type must be parameterized with
|
||||||
|
* the type of the given generator annotation.
|
||||||
|
*
|
||||||
|
* @return the value generation type
|
||||||
|
*/
|
||||||
|
Class<? extends AnnotationValueGeneration<?>> generatedBy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface AnnotationValueGeneration<A extends Annotation>
|
||||||
|
extends ValueGeneration {
|
||||||
|
/**
|
||||||
|
* Initializes this generation strategy for the given annotation instance.
|
||||||
|
*
|
||||||
|
* @param annotation an instance of the strategy's annotation type. Typically
|
||||||
|
* implementations will retrieve the annotation's attribute values and store
|
||||||
|
* them in fields.
|
||||||
|
* @param propertyType the type of the property annotated with the generator
|
||||||
|
* annotation. Implementations may use the type to determine the right
|
||||||
|
* {@link ValueGenerator} to be applied.
|
||||||
|
*
|
||||||
|
* @throws HibernateException in case an error occurred during initialization,
|
||||||
|
* e.g. if an implementation can't create a value for the given property type.
|
||||||
|
*/
|
||||||
|
void initialize(A annotation, Class<?> propertyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ValueGeneration {
|
||||||
|
/**
|
||||||
|
* When is this value generated : NEVER, INSERT, ALWAYS (INSERT+UPDATE)
|
||||||
|
*
|
||||||
|
* @return When the value is generated.
|
||||||
|
*/
|
||||||
|
public GenerationTiming getGenerationTiming();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain the in-VM value generator.
|
||||||
|
* <p/>
|
||||||
|
* May return {@code null}. In fact for values that are generated
|
||||||
|
* "in the database" via execution of the INSERT/UPDATE statement, the
|
||||||
|
* expectation is that {@code null} be returned here
|
||||||
|
*
|
||||||
|
* @return The strategy for performing in-VM value generation
|
||||||
|
*/
|
||||||
|
public ValueGenerator<?> getValueGenerator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For values which are generated in the database (i.e,
|
||||||
|
* {@link #getValueGenerator()} == {@code null}), should the column
|
||||||
|
* be referenced in the INSERT / UPDATE SQL?
|
||||||
|
* <p/>
|
||||||
|
* This will be false most often to have a DDL-defined DEFAULT value
|
||||||
|
* be applied on INSERT
|
||||||
|
*
|
||||||
|
* @return {@code true} indicates the column should be included in the SQL.
|
||||||
|
*/
|
||||||
|
public boolean referenceColumnInSql();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For values which are generated in the database (i.e.,
|
||||||
|
* {@link #getValueGenerator} == {@code null}), if the column will be
|
||||||
|
* referenced in the SQL (i.e.,
|
||||||
|
* {@link #referenceColumnInSql()} == {@code true}), what value should be
|
||||||
|
* used in the SQL as the column value.
|
||||||
|
* <p/>
|
||||||
|
* Generally this will be a function call or a marker (DEFAULTS).
|
||||||
|
* <p/>
|
||||||
|
* NOTE : for in-VM generation, this will not be called and the column
|
||||||
|
* value will implicitly be a JDBC parameter ('?')
|
||||||
|
*
|
||||||
|
* @return The column value to be used in the SQL.
|
||||||
|
*/
|
||||||
|
public String getDatabaseGeneratedReferencedColumnValue();
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Ultimately, `ValueGeneration` is the thing seen by the Hibernate internals. But `AnnotationValueGeneration` gives
|
||||||
|
the opportunity to configure the `ValueGeneration` via the annotation that named it. As mentioned, these contracts
|
||||||
|
cater to both in-database and in-memory scenarios. Below we'll look at some specific examples of usage in both
|
||||||
|
in-database and in-memory scenarios as a means of illustration.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== In-database Generation
|
||||||
|
|
||||||
|
`@Generated` has been retrofitted to use `@ValueGenerationType`. But `@ValueGenerationType` exposes more features
|
||||||
|
than what `@Generated` currently supports. To leverage some of those features, you'd simply wire up a new
|
||||||
|
generator annotation. For example, lets say we want the timestamps to be generated by calls to the standard
|
||||||
|
ANSI SQL function `current_timestamp` (rather than triggers or DEFAULT values):
|
||||||
|
|
||||||
|
[[in-database-example]]
|
||||||
|
.In-database Custom Annotation Example
|
||||||
|
====
|
||||||
|
[source, JAVA]
|
||||||
|
----
|
||||||
|
@ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface FunctionCreationTimestamp {
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FunctionCreationValueGeneration
|
||||||
|
implements AnnotationValueGeneration<FunctionCreationTimestamp> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(FunctionCreationTimestamp annotation, Class<?> propertyType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenerationTiming getGenerationTiming() {
|
||||||
|
// its creation...
|
||||||
|
return GenerationTiming.INSERT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueGenerator<?> getValueGenerator() {
|
||||||
|
// no in-memory generation
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean referenceColumnInSql() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDatabaseGeneratedReferencedColumnValue() {
|
||||||
|
return "current_timestamp";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class ErrorReport {
|
||||||
|
...
|
||||||
|
@FunctionCreationTimestamp
|
||||||
|
private Date created;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== In-memory Generation
|
||||||
|
|
||||||
|
Going back to the earlier <<legacy-syntax-example,Document example>> we can use some of the new pre-defined
|
||||||
|
annotations to make the code a little cleaner and easier to understand:
|
||||||
|
|
||||||
|
[[in-memory-example1]]
|
||||||
|
.In-memory Generation Example
|
||||||
|
====
|
||||||
|
[source, JAVA]
|
||||||
|
----
|
||||||
|
@Entity
|
||||||
|
public class Document {
|
||||||
|
...
|
||||||
|
@CreationTimestamp
|
||||||
|
private Date created;
|
||||||
|
@UpdateTimestamp
|
||||||
|
private Date lastModified;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
Both `@CreationTimestamp` and `@UpdateTimestamp` perform in-memory generation of the timestamp (using the VM time).
|
||||||
|
|
||||||
|
Let's also add an annotation for tracking the username who last modified the entity:
|
||||||
|
|
||||||
|
[[in-memory-example2]]
|
||||||
|
.Another In-memory Generation Example
|
||||||
|
====
|
||||||
|
[source, JAVA]
|
||||||
|
----
|
||||||
|
@ValueGenerationType(generatedBy = ModifiedByValueGeneration.class)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ModifiedBy {
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModifiedByValueGeneration
|
||||||
|
implements AnnotationValueGeneration<ModifiedBy> {
|
||||||
|
private final ValueGenerator<String> generator = new ValueGenerator<String>() {
|
||||||
|
public String generateValue(Session session, Object owner) {
|
||||||
|
// lets use a custom Service in the Hibernate ServiceRegistry to keep this
|
||||||
|
// look up contextual and portable..
|
||||||
|
UserService userService = ( (SessionImplementor) session ).getFactory()
|
||||||
|
.getServiceRegistry()
|
||||||
|
.getService( UserService.class );
|
||||||
|
return userService.getCurrentUserName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(ModifiedBy annotation, Class<?> propertyType) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenerationTiming getGenerationTiming() {
|
||||||
|
return GenerationTiming.ALWAYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueGenerator<?> getValueGenerator() {
|
||||||
|
return generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean referenceColumnInSql() {
|
||||||
|
// n/a
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDatabaseGeneratedReferencedColumnValue() {
|
||||||
|
// n/a
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Entity
|
||||||
|
public class Document {
|
||||||
|
...
|
||||||
|
@CreationTimestamp
|
||||||
|
private Date created;
|
||||||
|
@UpdateTimestamp
|
||||||
|
private Date lastModified;
|
||||||
|
@ModifiedBy
|
||||||
|
private String lastModifiedBy;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
|
@ -7,7 +7,8 @@ guides and try to give some direction on where to look.
|
||||||
|
|
||||||
== User Guides
|
== User Guides
|
||||||
|
|
||||||
Coming soon
|
* For information on generated (non-identifier) values, see the <<generated/GeneratedValues.adoc#,Generated Values Guide>>
|
||||||
|
* Others coming soon
|
||||||
|
|
||||||
== Tooling
|
== Tooling
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue