Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,25 @@
* {@link GeneratedColumn} annotation, so that Hibernate automatically
* generates the correct DDL.
* </ul>
* <p>
* A {@code @Generated} field may be generated on
* {@linkplain EventType#INSERT inserts}, on
* {@linkplain EventType#UPDATE updates}, or on both inserts and updates,
* as specified by the {@link #event} member.
* By default, {@code @Generated} fields are not immutable, and so a field
* which is generated on insert may later be explicitly assigned a new value
* by the application program, resulting in its value being updated in the
* database. If this is not desired, the {@link Immutable @Immutable}
* annotation may be used in conjunction with {@code @Generated} to specify
* that the field may never be updated after initial generation of its value.
*
* @author Emmanuel Bernard
*
* @see jakarta.persistence.GeneratedValue
* @see ColumnDefault
* @see GeneratedColumn
* @see Formula
* @see Immutable
*/
@ValueGenerationType( generatedBy = GeneratedGeneration.class )
@IdGeneratorType( GeneratedGeneration.class )
Expand Down Expand Up @@ -84,8 +97,12 @@
/**
* Determines if the value currently assigned to the annotated property
* is included in SQL {@code insert} and {@code update} statements. This
* is useful if the generated value is obtained by transforming the
* assigned property value as it is being written.
* is useful if:
* <ul>
* <li>the generated value is obtained by transforming the assigned
* property value as it is being written, or
* <li>assigning a value disables generation of a value.
* </ul>
* <p>
* Often used in combination with {@link SQLInsert}, {@link SQLUpdate},
* or {@link ColumnTransformer#write()}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ default boolean isAllowRefreshDetachedEntity() {

BaselineSessionEventsListenerBuilder getBaselineSessionEventsListenerBuilder();

/**
* Should generated identifiers be reset after entity removal?
*
* @see org.hibernate.cfg.AvailableSettings#USE_IDENTIFIER_ROLLBACK
*/
boolean isIdentifierRollbackEnabled();

boolean isCheckNullability();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ public interface AvailableSettings
/**
* When enabled, specifies that the generated identifier of an entity is unset
* when the entity is {@linkplain org.hibernate.Session#remove(Object) deleted}.
* If the entity is versioned, the version is also reset to its default value.
*
* @settingDefault {@code false} - generated identifiers are not unset
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ public Boolean isUnsaved(@Nullable Object version) throws MappingException {
if ( version == null ) {
return Boolean.TRUE;
}
if ( version instanceof Number ) {
return ((Number) version).longValue() < 0L;
if ( version instanceof Number number ) {
return number.longValue() < 0L;
}
throw new MappingException( "unsaved-value NEGATIVE may only be used with short, int and long types" );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ protected Object saveWithGeneratedId(
final EntityPersister persister = source.getEntityPersister( entityName, entity );
final Generator generator = persister.getGenerator();
final boolean generatedOnExecution = generator.generatedOnExecution( entity, source );
final boolean generatedBeforeExecution = generator.generatedBeforeExecution( entity, source );
final Object generatedId;
if ( generatedOnExecution ) {
// the id gets generated by the database
Expand All @@ -114,7 +115,7 @@ else if ( !generator.generatesOnInsert() ) {
// the @PrePersist callback to happen first
generatedId = null;
}
else {
else if ( generatedBeforeExecution ) {
// go ahead and generate id, and then set it to
// the entity instance, so it will be available
// to the entity in the @PrePersist callback
Expand All @@ -124,6 +125,11 @@ else if ( !generator.generatesOnInsert() ) {
}
persister.setIdentifier( entity, generatedId, source );
}
else {
// the generator is refusing to generate anything
// so use the identifier currently assigned
generatedId = persister.getIdentifier( entity, source );
}
final boolean delayIdentityInserts =
!source.isTransactionInProgress()
&& !requiresImmediateIdAccess
Expand Down
16 changes: 16 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/generator/Assigned.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org.hibernate.generator;

import org.hibernate.Internal;
import org.hibernate.engine.spi.SharedSessionContractImplementor;

import java.util.EnumSet;

Expand All @@ -29,11 +30,26 @@ public boolean generatedOnExecution() {
return false;
}

@Override
public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) {
return false;
}

@Override
public boolean generatedBeforeExecution(Object entity, SharedSessionContractImplementor session) {
return false;
}

@Override
public boolean allowAssignedIdentifiers() {
return true;
}

@Override
public boolean allowMutation() {
return true;
}

@Override
public EnumSet<EventType> getEventTypes() {
return EventTypeSets.NONE;
Expand Down
57 changes: 46 additions & 11 deletions hibernate-core/src/main/java/org/hibernate/generator/Generator.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@
* SQL {@code select}, though in certain cases this additional round trip may be avoided.
* An important example is id generation using an identity column.
* </ul>
* A Generator may implement both interfaces and determine the timing of ID generation at runtime.
* Furthermore, this condition can be based on the state of the owner entity, see
* {@link #generatedOnExecution(Object, SharedSessionContractImplementor) generatedOnExecution}.
* A {@code Generator} may implement both interfaces and determine the timing of identifier
* generation at runtime. Furthermore, this condition can be based on the state of the owner entity,
* see {@link #generatedOnExecution(Object, SharedSessionContractImplementor) generatedOnExecution} and
* {@link #generatedBeforeExecution(Object, SharedSessionContractImplementor) generatedBeforeExecution}.
* <p>
* Generically, a generator may be integrated with the program using the meta-annotation
* {@link org.hibernate.annotations.ValueGenerationType}, which associates the generator with
Expand Down Expand Up @@ -95,30 +96,51 @@ public interface Generator extends Serializable {
boolean generatedOnExecution();

/**
* Determines if the property value is generated when a row is written to the database,
* or in Java code that executes before the row is written.
* Determines if the property value is generated when a row is written to the database.
* <p>
* Defaults to {@link #generatedOnExecution()}, but can be overloaded allowing conditional
* value generation timing (on/before execution) based on the current state of the owner entity.
* Note that a generator <b>must</b> implement both {@link BeforeExecutionGenerator} and
* {@link OnExecutionGenerator} to achieve this behavior.
* Defaults to {@link #generatedOnExecution()}, but may be overridden to allow conditional
* on-execution value generation based on the current state of the owner entity.
*
* @param entity The instance of the entity owning the attribute for which we are generating a value.
* @param session The session from which the request originates.
*
* @return {@code true} if the value is generated by the database as a side effect of
* the execution of an {@code insert} or {@code update} statement, or false if
* it is generated in Java code before the statement is executed via JDBC.
* the execution of an {@code insert} or {@code update} statement.
*
* @see #generatedOnExecution()
* @see BeforeExecutionGenerator
* @see OnExecutionGenerator
*
* @since 6.4
*/
default boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) {
return generatedOnExecution();
}

/**
* Determines if the property value is generated before in Java code that executes before
* the row is written.
* <p>
* Defaults to {@link #generatedOnExecution() !generatedOnExecution()}, but may be overridden
* to allow conditional before-execution value generation based on the current state of the
* owner entity.
*
* @param entity The instance of the entity owning the attribute for which we are generating a value.
* @param session The session from which the request originates.
*
* @return {@code true} if the value is generated in Java code before the statement is
* executed via JDBC.
*
* @see #generatedOnExecution()
* @see BeforeExecutionGenerator
* @see OnExecutionGenerator
*
* @since 7.0
*/
default boolean generatedBeforeExecution(Object entity, SharedSessionContractImplementor session) {
return !generatedOnExecution();
}

/**
* The {@linkplain EventType event types} for which this generator should be called
* to produce a new value.
Expand All @@ -143,6 +165,19 @@ default boolean allowAssignedIdentifiers() {
return false;
}

/**
* Determine if this generator allows generated fields to be manually assigned a value on
* {@linkplain #getEventTypes events} which do <em>not</em> trigger value generation.
*
* @return {@code true} if this generator allows manually assigned values,
* {@code false} otherwise (default).
*
* @since 7.0
*/
default boolean allowMutation() {
return false;
}

default boolean generatesSometimes() {
return !getEventTypes().isEmpty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
*/
package org.hibernate.generator.internal;

import org.hibernate.AnnotationException;
import org.hibernate.annotations.Generated;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.EventType;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.persister.entity.EntityPersister;
Expand All @@ -26,7 +26,7 @@
* @author Steve Ebersole
* @author Gunnar Morling
*/
public class GeneratedGeneration implements OnExecutionGenerator, BeforeExecutionGenerator {
public class GeneratedGeneration implements OnExecutionGenerator {

private final EnumSet<EventType> eventTypes;
private final boolean writable;
Expand All @@ -41,7 +41,10 @@ public GeneratedGeneration(EnumSet<EventType> eventTypes) {
public GeneratedGeneration(Generated annotation) {
eventTypes = fromArray( annotation.event() );
sql = isEmpty( annotation.sql() ) ? null : new String[] { annotation.sql() };
writable = annotation.writable() || sql != null;
writable = annotation.writable();
if ( sql != null && writable ) {
throw new AnnotationException( "A field marked '@Generated(writable=true)' may not specify explicit 'sql'" );
}
}

@Override
Expand All @@ -51,7 +54,9 @@ public EnumSet<EventType> getEventTypes() {

@Override
public boolean referenceColumnsInSql(Dialect dialect) {
return writable;
// include the column in when the field is writable,
// or when there is an explicit SQL expression
return writable || sql != null;
}

@Override
Expand All @@ -61,34 +66,32 @@ public String[] getReferencedColumnValues(Dialect dialect) {

@Override
public boolean writePropertyValue() {
return writable && sql==null;
}

@Override
public boolean generatedOnExecution() {
return true;
// include a ? parameter when the field is writable,
// but there is no explicit SQL expression
return writable;
}

@Override
public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) {
if ( !writable ) {
if ( writable ) {
// When this is the identifier generator and writable is true, allow pre-assigned identifiers
final EntityPersister entityPersister = session.getEntityPersister( null, entity );
return entityPersister.getGenerator() != this
|| entityPersister.getIdentifier( entity, session ) == null;
}
else {
return true;
}

// When this is the identifier generator and writable is true, allow pre-assigned identifiers
final EntityPersister entityPersister = session.getEntityPersister( null, entity );
return entityPersister.getGenerator() != this || entityPersister.getIdentifier( entity, session ) == null;
}

@Override
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
final EntityPersister entityPersister = session.getEntityPersister( null, owner );
assert entityPersister.getGenerator() == this;
return entityPersister.getIdentifier( owner, session );
public boolean allowAssignedIdentifiers() {
return writable;
}

@Override
public boolean allowAssignedIdentifiers() {
return writable;
public boolean allowMutation() {
// the user may specify @Immutable if mutation should be disallowed
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,32 +146,47 @@ public Object insert(String entityName, Object entity) {
}
}
final Generator generator = persister.getGenerator();
if ( !generator.generatedOnExecution( entity, this ) ) {
if ( generator.generatesOnInsert() ) {
id = ( (BeforeExecutionGenerator) generator).generate( this, entity, null, INSERT );
}
else {
id = persister.getIdentifier( entity, this );
if ( id == null ) {
throw new IdentifierGenerationException( "Identifier of entity '" + persister.getEntityName() + "' must be manually assigned before calling 'insert()'" );
}
if ( generator.generatedBeforeExecution( entity, this ) ) {
if ( !generator.generatesOnInsert() ) {
throw new IdentifierGenerationException( "Identifier generator must generate on insert" );
}
id = ( (BeforeExecutionGenerator) generator).generate( this, entity, null, INSERT );
if ( firePreInsert(entity, id, state, persister) ) {
return id;
}
getInterceptor().onInsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() );
persister.getInsertCoordinator().insert( entity, id, state, this );
else {
getInterceptor().onInsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() );
persister.getInsertCoordinator().insert( entity, id, state, this );
persister.setIdentifier( entity, id, this );
}
}
else {
else if ( generator.generatedOnExecution( entity, this ) ) {
if ( !generator.generatesOnInsert() ) {
throw new IdentifierGenerationException( "Identifier generator must generate on insert" );
}
if ( firePreInsert(entity, null, state, persister) ) {
return null;
}
getInterceptor()
.onInsert( entity, null, state, persister.getPropertyNames(), persister.getPropertyTypes() );
final GeneratedValues generatedValues = persister.getInsertCoordinator().insert( entity, state, this );
id = castNonNull( generatedValues ).getGeneratedValue( persister.getIdentifierMapping() );
else {
getInterceptor().onInsert( entity, null, state, persister.getPropertyNames(), persister.getPropertyTypes() );
final GeneratedValues generatedValues = persister.getInsertCoordinator().insert( entity, state, this );
id = castNonNull( generatedValues ).getGeneratedValue( persister.getIdentifierMapping() );
persister.setIdentifier( entity, id, this );
}
}
else { // assigned identifier
id = persister.getIdentifier( entity, this );
if ( id == null ) {
throw new IdentifierGenerationException( "Identifier of entity '" + persister.getEntityName() + "' must be manually assigned before calling 'insert()'" );
}
if ( firePreInsert(entity, id, state, persister) ) {
return id;
}
else {
getInterceptor().onInsert( entity, id, state, persister.getPropertyNames(), persister.getPropertyTypes() );
persister.getInsertCoordinator().insert( entity, id, state, this );
}
}
persister.setIdentifier( entity, id, this );
forEachOwnedCollection( entity, id, persister,
(descriptor, collection) -> {
descriptor.recreate( collection, id, this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ public int getPropertyIndex() {

@Override
public Object execute(SharedSessionContractImplementor session, Object incomingObject) {
if ( !generator.generatedOnExecution( incomingObject, session ) ) {
if ( generator.generatedBeforeExecution( incomingObject, session ) ) {
return generator.generate( session, incomingObject, null, INSERT );
}
else {
Expand Down
Loading
Loading