HHH-8692 - Document value generation feature

This commit is contained in:
Steve Ebersole 2013-11-19 17:29:22 -06:00
parent 475aba7c0f
commit 4e6f3a9753
2 changed files with 278 additions and 1 deletions

View File

@ -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;
}
----
====

View File

@ -7,7 +7,8 @@ guides and try to give some direction on where to look.
== User Guides
Coming soon
* For information on generated (non-identifier) values, see the <<generated/GeneratedValues.adoc#,Generated Values Guide>>
* Others coming soon
== Tooling