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
|
||||
|
||||
Coming soon
|
||||
* For information on generated (non-identifier) values, see the <<generated/GeneratedValues.adoc#,Generated Values Guide>>
|
||||
* Others coming soon
|
||||
|
||||
== Tooling
|
||||
|
||||
|
|
Loading…
Reference in New Issue