HHH-17406 Retrieve arbitrary on-execution generated values efficiently

This commit is contained in:
Marco Belladelli 2023-11-07 13:12:02 +01:00 committed by Christian Beikov
parent 58173f92ee
commit d72856fef0
88 changed files with 3635 additions and 973 deletions

View File

@ -45,8 +45,12 @@ import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.internal.TableInsertStandard;
import org.hibernate.sql.model.internal.TableUpdateStandard;
import org.hibernate.type.SqlTypes;
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
/**
* A SQL AST translator for DB2.
*
@ -419,6 +423,50 @@ public class DB2LegacySqlAstTranslator<T extends JdbcOperation> extends Abstract
return true;
}
@Override
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
final List<ColumnReference> returningColumns = tableInsert.getReturningColumns();
if ( isNotEmpty( returningColumns ) ) {
appendSql( "select " );
for ( int i = 0; i < returningColumns.size(); i++ ) {
if ( i > 0 ) {
appendSql( ", " );
}
appendSql( returningColumns.get( i ).getColumnExpression() );
}
appendSql( " from new table ( " ); // 'from final table' does not seem to play well with triggers
super.visitStandardTableInsert( tableInsert );
appendSql( ")" );
}
else {
super.visitStandardTableInsert( tableInsert );
}
}
@Override
public void visitStandardTableUpdate(TableUpdateStandard tableUpdate) {
final List<ColumnReference> returningColumns = tableUpdate.getReturningColumns();
if ( isNotEmpty( returningColumns ) ) {
appendSql( "select " );
for ( int i = 0; i < returningColumns.size(); i++ ) {
if ( i > 0 ) {
appendSql( ", " );
}
appendSql( returningColumns.get( i ).getColumnExpression() );
}
appendSql( " from final table ( " );
super.visitStandardTableUpdate( tableUpdate );
appendSql( ")" );
}
else {
super.visitStandardTableUpdate( tableUpdate );
}
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
if ( getDB2Version().isSameOrAfter( 11, 1 ) ) {

View File

@ -44,6 +44,9 @@ import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.internal.TableInsertStandard;
import org.hibernate.sql.model.internal.TableUpdateStandard;
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
/**
* A legacy SQL AST translator for H2.
@ -60,14 +63,49 @@ public class H2LegacySqlAstTranslator<T extends JdbcOperation> extends AbstractS
@Override
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
if ( CollectionHelper.isNotEmpty( tableInsert.getReturningColumns() ) ) {
visitReturningInsertStatement( tableInsert );
if ( getDialect().getVersion().isSameOrAfter( 2 )
|| CollectionHelper.isEmpty( tableInsert.getReturningColumns() ) ) {
final boolean closeWrapper = renderReturningClause( tableInsert.getReturningColumns() );
super.visitStandardTableInsert( tableInsert );
if ( closeWrapper ) {
appendSql( ')' );
}
}
else {
super.visitStandardTableInsert( tableInsert );
visitReturningInsertStatement( tableInsert );
}
}
@Override
public void visitStandardTableUpdate(TableUpdateStandard tableUpdate) {
final boolean closeWrapper = renderReturningClause( tableUpdate.getReturningColumns() );
super.visitStandardTableUpdate( tableUpdate );
if ( closeWrapper ) {
appendSql( ')' );
}
}
protected boolean renderReturningClause(List<ColumnReference> returningColumns) {
if ( isEmpty( returningColumns ) ) {
return false;
}
appendSql( "select " );
for ( int i = 0; i < returningColumns.size(); i++ ) {
if ( i > 0 ) {
appendSql( ", " );
}
appendSql( returningColumns.get( i ).getColumnExpression() );
}
appendSql( " from final table (" );
return true;
}
@Override
protected void visitReturningColumns(List<ColumnReference> returningColumns) {
// do nothing - this is handled via `#renderReturningClause`
}
public void visitReturningInsertStatement(TableInsertStandard tableInsert) {
assert tableInsert.getReturningColumns() != null
&& !tableInsert.getReturningColumns().isEmpty();
@ -145,11 +183,6 @@ public class H2LegacySqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
}
@Override
protected void visitReturningColumns(List<ColumnReference> returningColumns) {
// do nothing - this is handled via `#visitReturningInsertStatement`
}
@Override
public void visitCteContainer(CteContainer cteContainer) {
// H2 has various bugs in different versions that make it impossible to use CTEs with parameters reliably

View File

@ -133,11 +133,13 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
nullifyTransientReferencesIfNotAlready();
final Object version = getVersion( getState(), getPersister() );
final PersistenceContext persistenceContextInternal = getSession().getPersistenceContextInternal();
persistenceContextInternal.addEntity(
persistenceContextInternal.addEntity( getEntityKey(), getInstance() );
persistenceContextInternal.addEntry(
getInstance(),
( getPersister().isMutable() ? Status.MANAGED : Status.READ_ONLY ),
getState(),
getEntityKey(),
getRowId(),
getEntityKey().getIdentifier(),
version,
LockMode.WRITE,
isExecuted,
@ -230,6 +232,8 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
*/
protected abstract EntityKey getEntityKey();
protected abstract Object getRowId();
@Override
public void afterDeserialize(EventSource session) {
super.afterDeserialize( session );

View File

@ -18,9 +18,12 @@ import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.event.spi.PreInsertEvent;
import org.hibernate.event.spi.PreInsertEventListener;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.spi.StatisticsImplementor;
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
/**
* The action for performing entity insertions when entity is using IDENTITY column identifier generation
*
@ -32,6 +35,7 @@ public class EntityIdentityInsertAction extends AbstractEntityInsertAction {
private final EntityKey delayedEntityKey;
private EntityKey entityKey;
private Object generatedId;
private Object rowId;
/**
* Constructs an EntityIdentityInsertAction
@ -78,14 +82,25 @@ public class EntityIdentityInsertAction extends AbstractEntityInsertAction {
// else inserted the same pk first, the insert would fail
if ( !isVeto() ) {
generatedId = persister.insert( getState(), instance, session );
final GeneratedValues generatedValues = persister.getInsertCoordinator().insert(
instance,
getState(),
session
);
generatedId = castNonNull( generatedValues ).getGeneratedValue( persister.getIdentifierMapping() );
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
if ( persister.getRowIdMapping() != null ) {
rowId = generatedValues.getGeneratedValue( persister.getRowIdMapping() );
if ( rowId != null && isDelayed ) {
persistenceContext.replaceEntityEntryRowId( getInstance(), rowId );
}
}
if ( persister.hasInsertGeneratedProperties() ) {
persister.processInsertGeneratedProperties( generatedId, instance, getState(), session );
persister.processInsertGeneratedProperties( generatedId, instance, getState(), generatedValues, session );
}
//need to do that here rather than in the save event listener to let
//the post insert events to have a id-filled entity when IDENTITY is used (EJB3)
persister.setIdentifier( instance, generatedId, session );
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
persistenceContext.registerInsertedKey( getPersister(), generatedId );
entityKey = session.generateEntityKey( generatedId, persister );
persistenceContext.checkUniqueness( entityKey, getInstance() );
@ -212,6 +227,11 @@ public class EntityIdentityInsertAction extends AbstractEntityInsertAction {
return entityKey != null ? entityKey : delayedEntityKey;
}
@Override
public Object getRowId() {
return rowId;
}
protected void setEntityKey(EntityKey entityKey) {
this.entityKey = entityKey;
}

View File

@ -26,6 +26,7 @@ import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.event.spi.PreInsertEvent;
import org.hibernate.event.spi.PreInsertEventListener;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.internal.StatsHelper;
import org.hibernate.stat.spi.StatisticsImplementor;
@ -83,6 +84,11 @@ public class EntityInsertAction extends AbstractEntityInsertAction {
return false;
}
@Override
protected Object getRowId() {
return null ;
}
@Override
protected EntityKey getEntityKey() {
return getSession().generateEntityKey( getId(), getPersister() );
@ -101,14 +107,14 @@ public class EntityInsertAction extends AbstractEntityInsertAction {
if ( !veto ) {
final EntityPersister persister = getPersister();
final Object instance = getInstance();
persister.insert( id, getState(), instance, session );
final GeneratedValues generatedValues = persister.insertReturning( id, getState(), instance, session );
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final EntityEntry entry = persistenceContext.getEntry( instance );
if ( entry == null ) {
throw new AssertionFailure( "possible non-threadsafe access to session" );
}
entry.postInsert( getState() );
handleGeneratedProperties( entry );
handleGeneratedProperties( entry, generatedValues, persistenceContext );
persistenceContext.registerInsertedKey( persister, getId() );
addCollectionsByKeyToPersistenceContext( persistenceContext, getState() );
}
@ -124,11 +130,14 @@ public class EntityInsertAction extends AbstractEntityInsertAction {
markExecuted();
}
private void handleGeneratedProperties(EntityEntry entry) {
private void handleGeneratedProperties(
EntityEntry entry,
GeneratedValues generatedValues,
PersistenceContext persistenceContext) {
final EntityPersister persister = getPersister();
if ( persister.hasInsertGeneratedProperties() ) {
final Object instance = getInstance();
persister.processInsertGeneratedProperties( getId(), instance, getState(), getSession() );
persister.processInsertGeneratedProperties( getId(), instance, getState(), generatedValues, getSession() );
if ( persister.isVersionPropertyGenerated() ) {
version = Versioning.getVersion( getState(), persister );
}
@ -138,6 +147,13 @@ public class EntityInsertAction extends AbstractEntityInsertAction {
version = Versioning.getVersion( getState(), persister );
entry.postInsert( version );
}
// Process row-id values when available early by replacing the entity entry
if ( generatedValues != null && persister.getRowIdMapping() != null ) {
final Object rowId = generatedValues.getGeneratedValue( persister.getRowIdMapping() );
if ( rowId != null ) {
persistenceContext.replaceEntityEntryRowId( getInstance(), rowId );
}
}
}
protected void putCacheIfNecessary() {

View File

@ -28,6 +28,7 @@ import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.event.spi.PreUpdateEvent;
import org.hibernate.event.spi.PreUpdateEventListener;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.internal.StatsHelper;
@ -165,12 +166,22 @@ public class EntityUpdateAction extends EntityAction {
final Object instance = getInstance();
final Object previousVersion = getPreviousVersion();
final Object ck = lockCacheItem( previousVersion );
persister.update( id, state, dirtyFields, hasDirtyCollection, previousState, previousVersion, instance, rowId, session );
final GeneratedValues generatedValues = persister.updateReturning(
id,
state,
dirtyFields,
hasDirtyCollection,
previousState,
previousVersion,
instance,
rowId,
session
);
final EntityEntry entry = session.getPersistenceContextInternal().getEntry( instance );
if ( entry == null ) {
throw new AssertionFailure( "possible non thread safe access to session" );
}
handleGeneratedProperties( entry );
handleGeneratedProperties( entry, generatedValues );
handleDeleted( entry );
updateCacheItem( previousVersion, ck, entry );
handleNaturalIdResolutions( persister, session, id );
@ -228,7 +239,7 @@ public class EntityUpdateAction extends EntityAction {
|| session.getCacheMode() == CacheMode.IGNORE;
}
private void handleGeneratedProperties(EntityEntry entry) {
private void handleGeneratedProperties(EntityEntry entry, GeneratedValues generatedValues) {
final EntityPersister persister = getPersister();
if ( entry.getStatus() == Status.MANAGED || persister.isVersionPropertyGenerated() ) {
final SharedSessionContractImplementor session = getSession();
@ -247,7 +258,7 @@ public class EntityUpdateAction extends EntityAction {
if ( persister.hasUpdateGeneratedProperties() ) {
// this entity defines property generation, so process those generated
// values...
persister.processUpdateGeneratedProperties( id, instance, state, session );
persister.processUpdateGeneratedProperties( id, instance, state, generatedValues, session );
}
// have the entity entry doAfterTransactionCompletion post-update processing, passing it the
// update state and the new version (if one).

View File

@ -1055,6 +1055,11 @@ public class DB2Dialect extends Dialect {
return true;
}
@Override
public boolean supportsInsertReturningRowId() {
return false;
}
@Override
public boolean supportsValuesList() {
return true;

View File

@ -45,6 +45,7 @@ import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.internal.TableInsertStandard;
import org.hibernate.sql.model.internal.TableUpdateStandard;
import org.hibernate.type.SqlTypes;
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
@ -434,7 +435,7 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
appendSql( returningColumns.get( i ).getColumnExpression() );
}
appendSql( " from new table ( " ); // 'from final table' does not seem to play well with triggers
appendSql( " from new table (" ); // 'from final table' does not seem to play well with triggers
super.visitStandardTableInsert( tableInsert );
appendSql( ")" );
}
@ -443,6 +444,28 @@ public class DB2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAst
}
}
@Override
public void visitStandardTableUpdate(TableUpdateStandard tableUpdate) {
final List<ColumnReference> returningColumns = tableUpdate.getReturningColumns();
if ( isNotEmpty( returningColumns ) ) {
appendSql( "select " );
for ( int i = 0; i < returningColumns.size(); i++ ) {
if ( i > 0 ) {
appendSql( ", " );
}
appendSql( returningColumns.get( i ).getColumnExpression() );
}
appendSql( " from final table (" );
super.visitStandardTableUpdate( tableUpdate );
appendSql( ")" );
}
else {
super.visitStandardTableUpdate( tableUpdate );
}
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
if ( getDB2Version().isSameOrAfter( 11, 1 ) ) {

View File

@ -4042,7 +4042,6 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
* {@code false} if {@code InsertReturningDelegate} does not work, or only
* works for specialized identity/"autoincrement" columns
*
* @see org.hibernate.generator.OnExecutionGenerator#getGeneratedIdentifierDelegate
* @see org.hibernate.id.insert.InsertReturningDelegate
*
* @since 6.2
@ -4051,6 +4050,33 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
return false;
}
/**
* Does this dialect supports returning the {@link org.hibernate.annotations.RowId} column
* after execution of an {@code insert} statement, using native SQL syntax?
*
* @return {@code true} is the dialect supports returning the rowid column
*
* @see #supportsInsertReturning()
* @since 6.5
*/
public boolean supportsInsertReturningRowId() {
return supportsInsertReturning();
}
/**
* Does this dialect fully support returning arbitrary generated column values
* after execution of an {@code update} statement, using native SQL syntax?
* <p>
* Defaults to the value of {@link #supportsInsertReturning()} but can be overridden
* to explicitly disable this for updates.
*
* @see #supportsInsertReturning()
* @since 6.5
*/
public boolean supportsUpdateReturning() {
return supportsInsertReturning();
}
/**
* Does this dialect fully support returning arbitrary generated column values
* after execution of an {@code insert} statement, using the JDBC method
@ -4072,6 +4098,17 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
public boolean supportsInsertReturningGeneratedKeys() {
return false;
}
/**
* Does this dialect require unquoting identifiers when passing them to the
* {@link Connection#prepareStatement(String, String[])} JDBC method.
*
* @see Dialect#supportsInsertReturningGeneratedKeys()
*/
public boolean unquoteGetGeneratedKeys() {
return false;
}
/**
* Does this dialect support the given {@code FETCH} clause type.
*

View File

@ -834,6 +834,21 @@ public class H2Dialect extends Dialect {
return true;
}
@Override
public boolean supportsInsertReturningRowId() {
return false;
}
@Override
public boolean supportsInsertReturningGeneratedKeys() {
return true;
}
@Override
public boolean unquoteGetGeneratedKeys() {
return true;
}
@Override
public int registerResultSetOutParameter(CallableStatement statement, int position) throws SQLException {
return position;

View File

@ -17,6 +17,7 @@ import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.MutationStatement;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteContainer;
import org.hibernate.sql.ast.tree.cte.CteTableGroup;
@ -40,7 +41,9 @@ import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.ast.tree.update.UpdateStatement;
import org.hibernate.sql.exec.spi.JdbcOperation;
import org.hibernate.sql.model.internal.TableInsertStandard;
import org.hibernate.sql.model.internal.TableUpdateStandard;
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
/**
@ -58,14 +61,44 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslato
@Override
public void visitStandardTableInsert(TableInsertStandard tableInsert) {
if ( isNotEmpty( tableInsert.getReturningColumns() ) ) {
visitReturningInsertStatement( tableInsert );
}
else {
super.visitStandardTableInsert( tableInsert );
final boolean closeWrapper = renderReturningClause( tableInsert.getReturningColumns() );
super.visitStandardTableInsert( tableInsert );
if ( closeWrapper ) {
appendSql( ')' );
}
}
@Override
public void visitStandardTableUpdate(TableUpdateStandard tableUpdate) {
final boolean closeWrapper = renderReturningClause( tableUpdate.getReturningColumns() );
super.visitStandardTableUpdate( tableUpdate );
if ( closeWrapper ) {
appendSql( ')' );
}
}
protected boolean renderReturningClause(List<ColumnReference> returningColumns) {
if ( isEmpty( returningColumns ) ) {
return false;
}
appendSql( "select " );
for ( int i = 0; i < returningColumns.size(); i++ ) {
if ( i > 0 ) {
appendSql( ", " );
}
appendSql( returningColumns.get( i ).getColumnExpression() );
}
appendSql( " from final table (" );
return true;
}
@Override
protected void visitReturningColumns(List<ColumnReference> returningColumns) {
// do nothing - this is handled via `#renderReturningClause`
}
@Deprecated( forRemoval = true, since = "6.5" )
public void visitReturningInsertStatement(TableInsertStandard tableInsert) {
assert tableInsert.getReturningColumns() != null
&& !tableInsert.getReturningColumns().isEmpty();
@ -147,11 +180,6 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends SqlAstTranslato
}
}
@Override
protected void visitReturningColumns(List<ColumnReference> returningColumns) {
// do nothing - this is handled via `#visitReturningInsertStatement`
}
@Override
public void visitCteContainer(CteContainer cteContainer) {
// H2 has various bugs in different versions that make it impossible to use CTEs with parameters reliably

View File

@ -12,6 +12,8 @@ import java.sql.SQLException;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.identity.MariaDBIdentityColumnSupport;
import org.hibernate.dialect.sequence.MariaDBSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
@ -248,6 +250,16 @@ public class MariaDBDialect extends MySQLDialect {
return getVersion().isSameOrAfter( 10, 5 );
}
@Override
public boolean supportsUpdateReturning() {
return false;
}
@Override
public IdentityColumnSupport getIdentityColumnSupport() {
return MariaDBIdentityColumnSupport.INSTANCE;
}
@Override
public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() {
return FunctionalDependencyAnalysisSupportImpl.TABLE_GROUP_AND_CONSTANTS;

View File

@ -10,6 +10,7 @@ import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.id.insert.GetGeneratedKeysDelegate;
import org.hibernate.persister.entity.EntityPersister;
/**
* A set of operations providing support for identity columns
@ -55,7 +56,10 @@ public interface IdentityColumnSupport {
*
* @return The insert command with any necessary identity select
* clause attached.
*
* @deprecated Use {@link #appendIdentitySelectToInsert(String, String)} instead.
*/
@Deprecated( forRemoval = true, since = "6.5" )
String appendIdentitySelectToInsert(String insertString);
/**
@ -127,8 +131,23 @@ public interface IdentityColumnSupport {
* @param dialect The dialect against which to generate the delegate
*
* @return the dialect specific GetGeneratedKeys delegate
*
* @deprecated Use {@link #buildGetGeneratedKeysDelegate(EntityPersister, Dialect)} instead.
*/
GetGeneratedKeysDelegate buildGetGeneratedKeysDelegate(
@Deprecated( forRemoval = true, since = "6.5" )
default GetGeneratedKeysDelegate buildGetGeneratedKeysDelegate(
PostInsertIdentityPersister persister,
Dialect dialect);
Dialect dialect) {
return buildGetGeneratedKeysDelegate( persister );
}
/**
* The Delegate for dealing with IDENTITY columns using JDBC3 getGeneratedKeys
*
* @param persister The persister
* @param dialect The dialect against which to generate the delegate
*
* @return the dialect specific GetGeneratedKeys delegate
*/
GetGeneratedKeysDelegate buildGetGeneratedKeysDelegate(EntityPersister persister);
}

View File

@ -8,8 +8,9 @@ package org.hibernate.dialect.identity;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.generator.EventType;
import org.hibernate.id.insert.GetGeneratedKeysDelegate;
import org.hibernate.persister.entity.EntityPersister;
/**
* @author Andrea Boriero
@ -54,9 +55,7 @@ public class IdentityColumnSupportImpl implements IdentityColumnSupport {
}
@Override
public GetGeneratedKeysDelegate buildGetGeneratedKeysDelegate(
PostInsertIdentityPersister persister,
Dialect dialect) {
return new GetGeneratedKeysDelegate( persister, dialect, true );
public GetGeneratedKeysDelegate buildGetGeneratedKeysDelegate(EntityPersister persister) {
return new GetGeneratedKeysDelegate( persister, true, EventType.INSERT );
}
}

View File

@ -0,0 +1,19 @@
/*
* 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.dialect.identity;
/**
* @author Marco Belladelli
*/
public class MariaDBIdentityColumnSupport extends MySQLIdentityColumnSupport {
public static final MariaDBIdentityColumnSupport INSTANCE = new MariaDBIdentityColumnSupport();
@Override
public String appendIdentitySelectToInsert(String identityColumnName, String insertString) {
return insertString + " returning " + identityColumnName;
}
}

View File

@ -8,8 +8,10 @@ package org.hibernate.dialect.identity;
import org.hibernate.Remove;
import org.hibernate.dialect.Dialect;
import org.hibernate.generator.EventType;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.id.insert.GetGeneratedKeysDelegate;
import org.hibernate.persister.entity.EntityPersister;
/**
* @author Andrea Boriero
@ -18,7 +20,15 @@ import org.hibernate.id.insert.GetGeneratedKeysDelegate;
*/
@Deprecated(forRemoval = true) @Remove
public class Oracle12cGetGeneratedKeysDelegate extends GetGeneratedKeysDelegate {
/**
* @deprecated Use {@link #Oracle12cGetGeneratedKeysDelegate(EntityPersister)} instead.
*/
@Deprecated( forRemoval = true, since = "6.5" )
public Oracle12cGetGeneratedKeysDelegate(PostInsertIdentityPersister persister, Dialect dialect) {
super( persister, dialect, false );
this( persister );
}
public Oracle12cGetGeneratedKeysDelegate(EntityPersister persister) {
super( persister, false, EventType.INSERT );
}
}

View File

@ -7,8 +7,9 @@
package org.hibernate.dialect.identity;
import org.hibernate.dialect.Dialect;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.generator.EventType;
import org.hibernate.id.insert.GetGeneratedKeysDelegate;
import org.hibernate.persister.entity.EntityPersister;
/**
* @author Andrea Boriero
@ -28,9 +29,8 @@ public class Oracle12cIdentityColumnSupport extends IdentityColumnSupportImpl {
}
@Override
public GetGeneratedKeysDelegate buildGetGeneratedKeysDelegate(
PostInsertIdentityPersister persister, Dialect dialect) {
return new GetGeneratedKeysDelegate( persister, dialect, false );
public GetGeneratedKeysDelegate buildGetGeneratedKeysDelegate(EntityPersister persister) {
return new GetGeneratedKeysDelegate( persister, false, EventType.INSERT );
}
@Override

View File

@ -10,14 +10,13 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.id.insert.GetGeneratedKeysDelegate;
import org.hibernate.id.insert.SybaseJConnGetGeneratedKeysDelegate;
import org.hibernate.persister.entity.EntityPersister;
public class SybaseJconnIdentityColumnSupport extends AbstractTransactSQLIdentityColumnSupport {
public static final SybaseJconnIdentityColumnSupport INSTANCE = new SybaseJconnIdentityColumnSupport();
@Override
public GetGeneratedKeysDelegate buildGetGeneratedKeysDelegate(
PostInsertIdentityPersister persister,
Dialect dialect) {
return new SybaseJConnGetGeneratedKeysDelegate( persister, dialect );
public GetGeneratedKeysDelegate buildGetGeneratedKeysDelegate(EntityPersister persister) {
return new SybaseJConnGetGeneratedKeysDelegate( persister );
}
}

View File

@ -1753,6 +1753,23 @@ public class StatefulPersistenceContext implements PersistenceContext {
);
}
@Override
public void replaceEntityEntryRowId(Object entity, Object rowId) {
final EntityEntry oldEntry = entityEntryContext.removeEntityEntry( entity );
addEntry(
entity,
oldEntry.getStatus(),
oldEntry.getLoadedState(),
rowId,
oldEntry.getId(),
oldEntry.getVersion(),
oldEntry.getLockMode(),
oldEntry.isExistsInDatabase(),
oldEntry.getPersister(),
oldEntry.isBeingReplicated()
);
}
/**
* Used by the owning session to explicitly control serialization of the
* persistence context.

View File

@ -27,7 +27,7 @@ public final class HighlightingFormatter implements Formatter {
private static final Set<String> KEYWORDS_LOWERCASED = new HashSet<>( new AnsiSqlKeywords().sql2003() );
static {
// additional keywords not reserved by ANSI SQL 2003
KEYWORDS_LOWERCASED.addAll( Arrays.asList( "key", "sequence", "cascade", "increment", "boolean", "offset", "next" ) );
KEYWORDS_LOWERCASED.addAll( Arrays.asList( "key", "sequence", "cascade", "increment", "boolean", "offset", "next", "returning" ) );
}
public static final Formatter INSTANCE =

View File

@ -9,6 +9,7 @@ package org.hibernate.engine.jdbc.mutation;
import org.hibernate.Incubating;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.sql.model.ValuesAnalysis;
/**
@ -42,7 +43,7 @@ public interface MutationExecutor {
* @param resultChecker Custom result checking; pass {@code null} to perform
* the standard check using the statement's {@linkplain org.hibernate.jdbc.Expectation expectation}
*/
Object execute(
GeneratedValues execute(
Object modelReference,
ValuesAnalysis valuesAnalysis,
TableInclusionChecker inclusionChecker,

View File

@ -12,9 +12,12 @@ import org.hibernate.engine.jdbc.batch.spi.BatchKey;
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.MutationExecutor;
import org.hibernate.engine.jdbc.mutation.OperationResultChecker;
import org.hibernate.engine.jdbc.mutation.ParameterUsage;
import org.hibernate.engine.jdbc.mutation.TableInclusionChecker;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.persister.entity.mutation.EntityTableMapping;
import org.hibernate.sql.model.TableMapping;
import org.hibernate.sql.model.ValuesAnalysis;
@ -43,23 +46,31 @@ public abstract class AbstractMutationExecutor implements MutationExecutor {
* </ol>
*/
@Override
public final Object execute(
public final GeneratedValues execute(
Object modelReference,
ValuesAnalysis valuesAnalysis,
TableInclusionChecker inclusionChecker,
OperationResultChecker resultChecker,
SharedSessionContractImplementor session) {
performNonBatchedOperations( valuesAnalysis, inclusionChecker, resultChecker, session );
final GeneratedValues generatedValues = performNonBatchedOperations(
modelReference,
valuesAnalysis,
inclusionChecker,
resultChecker,
session
);
performSelfExecutingOperations( valuesAnalysis, inclusionChecker, session );
performBatchedOperations( valuesAnalysis, inclusionChecker );
return null;
return generatedValues;
}
protected void performNonBatchedOperations(
protected GeneratedValues performNonBatchedOperations(
Object modelReference,
ValuesAnalysis valuesAnalysis,
TableInclusionChecker inclusionChecker,
OperationResultChecker resultChecker,
SharedSessionContractImplementor session) {
return null;
}
protected void performSelfExecutingOperations(
@ -78,6 +89,7 @@ public abstract class AbstractMutationExecutor implements MutationExecutor {
*/
protected void performNonBatchedMutation(
PreparedStatementDetails statementDetails,
Object id,
JdbcValueBindings valueBindings,
TableInclusionChecker inclusionChecker,
OperationResultChecker resultChecker,
@ -97,6 +109,20 @@ public abstract class AbstractMutationExecutor implements MutationExecutor {
return;
}
if ( id != null ) {
assert !tableDetails.isIdentifierTable() : "Unsupported identifier table with generated id";
( (EntityTableMapping) tableDetails ).getKeyMapping().breakDownKeyJdbcValues(
id,
(jdbcValue, columnMapping) -> valueBindings.bindValue(
jdbcValue,
tableDetails.getTableName(),
columnMapping.getColumnName(),
ParameterUsage.SET
),
session
);
}
// If we get here the statement is needed - make sure it is resolved
session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() );
valueBindings.beforeStatement( statementDetails );

View File

@ -21,8 +21,8 @@ import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.MutationStatementPreparer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.jdbc.TooManyRowsAffectedException;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.sql.model.MutationTarget;
import org.hibernate.sql.model.MutationType;
import org.hibernate.sql.model.PreparableMutationOperation;
@ -97,6 +97,7 @@ public class ModelMutationHelper {
public static PreparedStatementGroup toPreparedStatementGroup(
MutationType mutationType,
MutationTarget<?> mutationTarget,
GeneratedValuesMutationDelegate delegate,
List<PreparableMutationOperation> mutations,
SharedSessionContractImplementor session) {
if ( mutations == null || mutations.isEmpty() ) {
@ -104,37 +105,32 @@ public class ModelMutationHelper {
}
if ( mutations.size() == 1 ) {
return new PreparedStatementGroupSingleTable( mutations.get( 0 ), session );
return new PreparedStatementGroupSingleTable( mutations.get( 0 ), delegate, session );
}
return new PreparedStatementGroupStandard( mutationType, mutationTarget, mutations, session );
return new PreparedStatementGroupStandard( mutationType, mutationTarget, delegate, mutations, session );
}
public static PreparedStatementDetails standardPreparation(
PreparableMutationOperation jdbcMutation,
GeneratedValuesMutationDelegate delegate,
SharedSessionContractImplementor session) {
return new PreparedStatementDetailsStandard(
jdbcMutation,
() -> standardStatementPreparation( jdbcMutation, session ),
() -> delegate != null ?
delegateStatementPreparation( jdbcMutation, delegate, session ) :
standardStatementPreparation( jdbcMutation, session ),
session.getJdbcServices()
);
}
public static PreparedStatementDetails identityPreparation(
public static PreparedStatement delegateStatementPreparation(
PreparableMutationOperation jdbcMutation,
GeneratedValuesMutationDelegate delegate,
SharedSessionContractImplementor session) {
return new PreparedStatementDetailsStandard(
jdbcMutation,
() -> {
final EntityMutationTarget target = (EntityMutationTarget) jdbcMutation.getMutationTarget();
final PreparedStatement statement = target
.getIdentityInsertDelegate()
.prepareStatement( jdbcMutation.getSqlString(), session );
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().register( null, statement );
return statement;
},
session.getJdbcServices()
);
final PreparedStatement statement = delegate.prepareStatement( jdbcMutation.getSqlString(), session );
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().register( null, statement );
return statement;
}
public static PreparedStatement standardStatementPreparation(

View File

@ -1,238 +0,0 @@
/*
* 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.engine.jdbc.mutation.internal;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.MutationExecutor;
import org.hibernate.engine.jdbc.mutation.OperationResultChecker;
import org.hibernate.engine.jdbc.mutation.ParameterUsage;
import org.hibernate.engine.jdbc.mutation.TableInclusionChecker;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementGroup;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.persister.entity.mutation.EntityTableMapping;
import org.hibernate.sql.model.EntityMutationOperationGroup;
import org.hibernate.sql.model.MutationOperation;
import org.hibernate.sql.model.MutationOperationGroup;
import org.hibernate.sql.model.MutationType;
import org.hibernate.sql.model.PreparableMutationOperation;
import org.hibernate.sql.model.SelfExecutingUpdateOperation;
import org.hibernate.sql.model.ValuesAnalysis;
import org.hibernate.sql.model.jdbc.JdbcValueDescriptor;
import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER;
/**
* Specialized executor for the case of more than one table operation, with the
* root table defining a post-insert id-generation strategy.
*
* @todo (mutation) : look to consolidate this into/with MutationExecutorStandard
* - aside from the special handling for the IDENTITY table insert,
* the code below is the same as MutationExecutorStandard.
* - consolidating this into MutationExecutorStandard would simplify
* creating "single table" variations - i.e. MutationExecutorStandard and
* StandardSingleTableExecutor. Otherwise we'd have MutationExecutorStandard,
* StandardSingleTableExecutor, MutationExecutorPostInsert and
* MutationExecutorPostInsertSingleTable variants
*
* @author Steve Ebersole
*/
public class MutationExecutorPostInsert implements MutationExecutor, JdbcValueBindingsImpl.JdbcValueDescriptorAccess {
protected final EntityMutationTarget mutationTarget;
protected final MutationOperationGroup mutationOperationGroup;
protected final PreparedStatementDetails identityInsertStatementDetails;
/**
* Any non-batched JDBC statements
*/
protected final PreparedStatementGroup secondaryTablesStatementGroup;
protected final JdbcValueBindingsImpl valueBindings;
public MutationExecutorPostInsert(EntityMutationOperationGroup mutationOperationGroup, SharedSessionContractImplementor session) {
this.mutationTarget = mutationOperationGroup.getMutationTarget();
this.valueBindings = new JdbcValueBindingsImpl(
MutationType.INSERT,
mutationTarget,
this,
session
);
this.mutationOperationGroup = mutationOperationGroup;
final PreparableMutationOperation identityInsertOperation = (PreparableMutationOperation) mutationOperationGroup.getOperation( mutationTarget.getIdentifierTableName() );
this.identityInsertStatementDetails = ModelMutationHelper.identityPreparation(
identityInsertOperation,
session
);
List<PreparableMutationOperation> secondaryTableMutations = null;
for ( int i = 0; i < mutationOperationGroup.getNumberOfOperations(); i++ ) {
final MutationOperation operation = mutationOperationGroup.getOperation( i );
if ( operation.getTableDetails().isIdentifierTable() ) {
// the identifier table is handled via `identityInsertStatementDetails`
continue;
}
// SelfExecutingUpdateOperation are not legal for inserts...
assert ! (operation instanceof SelfExecutingUpdateOperation );
final PreparableMutationOperation preparableMutationOperation = (PreparableMutationOperation) operation;
if ( secondaryTableMutations == null ) {
secondaryTableMutations = new ArrayList<>();
}
secondaryTableMutations.add( preparableMutationOperation );
}
this.secondaryTablesStatementGroup = ModelMutationHelper.toPreparedStatementGroup(
MutationType.INSERT,
mutationTarget,
secondaryTableMutations,
session
);
}
@Override
public JdbcValueBindings getJdbcValueBindings() {
return valueBindings;
}
@Override
public JdbcValueDescriptor resolveValueDescriptor(String tableName, String columnName, ParameterUsage usage) {
final MutationOperation operation = mutationOperationGroup.getOperation( tableName );
if ( operation == null ) {
return null;
}
return operation.getJdbcValueDescriptor( columnName, usage );
}
@Override
public PreparedStatementDetails getPreparedStatementDetails(String tableName) {
if ( mutationTarget.getIdentifierTableName().equals( tableName ) ) {
return identityInsertStatementDetails;
}
return secondaryTablesStatementGroup.getPreparedStatementDetails( tableName );
}
@Override
public Object execute(
Object modelReference,
ValuesAnalysis valuesAnalysis,
TableInclusionChecker inclusionChecker,
OperationResultChecker resultChecker,
SharedSessionContractImplementor session) {
final InsertGeneratedIdentifierDelegate identityHandler = mutationTarget.getIdentityInsertDelegate();
final Object id = identityHandler.performInsert( identityInsertStatementDetails, valueBindings, modelReference, session );
if ( MODEL_MUTATION_LOGGER.isTraceEnabled() ) {
MODEL_MUTATION_LOGGER.tracef(
"Post-insert generated value : `%s` (%s)",
id,
mutationTarget.getNavigableRole().getFullPath()
);
}
if ( secondaryTablesStatementGroup != null ) {
secondaryTablesStatementGroup.forEachStatement( (tableName, statementDetails) -> executeWithId(
id,
tableName,
statementDetails,
inclusionChecker,
resultChecker,
session
) );
}
return id;
}
private void executeWithId(
Object id,
String tableName,
PreparedStatementDetails statementDetails,
TableInclusionChecker inclusionChecker,
OperationResultChecker resultChecker,
SharedSessionContractImplementor session) {
if ( statementDetails == null ) {
return;
}
final EntityTableMapping tableDetails = (EntityTableMapping) statementDetails.getMutatingTableDetails();
assert !tableDetails.isIdentifierTable();
if ( inclusionChecker != null && !inclusionChecker.include( tableDetails ) ) {
if ( MODEL_MUTATION_LOGGER.isTraceEnabled() ) {
MODEL_MUTATION_LOGGER.tracef(
"Skipping execution of secondary insert : %s",
tableDetails.getTableName()
);
}
return;
}
// If we get here the statement is needed - make sure it is resolved
//noinspection resource
statementDetails.resolveStatement();
tableDetails.getKeyMapping().breakDownKeyJdbcValues(
id,
(jdbcValue, columnMapping) -> {
valueBindings.bindValue(
jdbcValue,
tableName,
columnMapping.getColumnName(),
ParameterUsage.SET
);
},
session
);
session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() );
valueBindings.beforeStatement( statementDetails );
try {
final int affectedRowCount = session.getJdbcCoordinator()
.getResultSetReturn()
.executeUpdate( statementDetails.getStatement(), statementDetails.getSqlString() );
ModelMutationHelper.checkResults( resultChecker, statementDetails, affectedRowCount, -1 );
}
catch (SQLException e) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
e,
"Unable to execute mutation PreparedStatement against table `" + tableName + "`",
statementDetails.getSqlString()
);
}
}
@Override
public void release() {
secondaryTablesStatementGroup.release();
}
@Override
public String toString() {
return String.format(
Locale.ROOT,
"MutationExecutorPostInsert(`%s`)",
mutationTarget.getNavigableRole().getFullPath()
);
}
}

View File

@ -1,126 +0,0 @@
/*
* 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.engine.jdbc.mutation.internal;
import java.util.Locale;
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.MutationExecutor;
import org.hibernate.engine.jdbc.mutation.OperationResultChecker;
import org.hibernate.engine.jdbc.mutation.ParameterUsage;
import org.hibernate.engine.jdbc.mutation.TableInclusionChecker;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.sql.model.EntityMutationOperationGroup;
import org.hibernate.sql.model.MutationType;
import org.hibernate.sql.model.PreparableMutationOperation;
import org.hibernate.sql.model.ValuesAnalysis;
import org.hibernate.sql.model.jdbc.JdbcValueDescriptor;
import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.identityPreparation;
import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER;
/**
* Specialized form of {@link MutationExecutorPostInsert} for cases where there
* is only the single identity table. Allows us to skip references to things
* we won't need (Batch, etc)
*
* @todo (mutation) : look to consolidate this into/with MutationExecutorStandard
* - aside from the special handling for the IDENTITY table insert,
* the code below is the same as MutationExecutorStandard.
* - consolidating this into MutationExecutorStandard would simplify
* creating "single table" variations - i.e. MutationExecutorStandard and
* StandardSingleTableExecutor. Otherwise we'd have MutationExecutorStandard,
* StandardSingleTableExecutor, MutationExecutorPostInsert and
* MutationExecutorPostInsertSingleTable variants
*
* @author Steve Ebersole
*/
public class MutationExecutorPostInsertSingleTable implements MutationExecutor, JdbcValueBindingsImpl.JdbcValueDescriptorAccess {
private final EntityMutationTarget mutationTarget;
private final SharedSessionContractImplementor session;
private final PreparableMutationOperation operation;
private final PreparedStatementDetails identityInsertStatementDetails;
private final JdbcValueBindingsImpl valueBindings;
public MutationExecutorPostInsertSingleTable(
EntityMutationOperationGroup mutationOperationGroup,
SharedSessionContractImplementor session) {
this.mutationTarget = mutationOperationGroup.getMutationTarget();
this.session = session;
assert mutationOperationGroup.getNumberOfOperations() == 1;
this.operation = (PreparableMutationOperation) mutationOperationGroup.getOperation( mutationTarget.getIdentifierTableName() );
this.identityInsertStatementDetails = identityPreparation( operation, session );
this.valueBindings = new JdbcValueBindingsImpl(
MutationType.INSERT,
mutationTarget,
this,
session
);
}
@Override
public JdbcValueDescriptor resolveValueDescriptor(String tableName, String columnName, ParameterUsage usage) {
assert identityInsertStatementDetails.getMutatingTableDetails().getTableName().equals( tableName );
return operation.findValueDescriptor( columnName, usage );
}
@Override
public JdbcValueBindings getJdbcValueBindings() {
return valueBindings;
}
@Override
public PreparedStatementDetails getPreparedStatementDetails(String tableName) {
if ( mutationTarget.getIdentifierTableName().equals( tableName ) ) {
return identityInsertStatementDetails;
}
return null;
}
@Override
public Object execute(
Object modelReference,
ValuesAnalysis valuesAnalysis,
TableInclusionChecker inclusionChecker,
OperationResultChecker resultChecker,
SharedSessionContractImplementor session) {
final InsertGeneratedIdentifierDelegate identityHandler = mutationTarget.getIdentityInsertDelegate();
final Object id = identityHandler.performInsert( identityInsertStatementDetails, valueBindings, modelReference, session );
if ( MODEL_MUTATION_LOGGER.isTraceEnabled() ) {
MODEL_MUTATION_LOGGER.tracef(
"Post-insert generated value : `%s` (%s)",
id,
mutationTarget.getNavigableRole().getFullPath()
);
}
return id;
}
@Override
public void release() {
identityInsertStatementDetails.releaseStatement( session );
}
@Override
public String toString() {
return String.format(
Locale.ROOT,
"MutationExecutorPostInsertSingleTable(`%s`)",
mutationTarget.getNavigableRole().getFullPath()
);
}
}

View File

@ -9,6 +9,8 @@ package org.hibernate.engine.jdbc.mutation.internal;
import org.hibernate.engine.jdbc.mutation.OperationResultChecker;
import org.hibernate.engine.jdbc.mutation.TableInclusionChecker;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.sql.model.PreparableMutationOperation;
import org.hibernate.sql.model.ValuesAnalysis;
@ -17,12 +19,15 @@ import org.hibernate.sql.model.ValuesAnalysis;
*/
public class MutationExecutorSingleNonBatched extends AbstractSingleMutationExecutor {
private final PreparedStatementGroupSingleTable statementGroup;
private final GeneratedValuesMutationDelegate generatedValuesDelegate;
public MutationExecutorSingleNonBatched(
PreparableMutationOperation mutationOperation,
GeneratedValuesMutationDelegate generatedValuesDelegate,
SharedSessionContractImplementor session) {
super( mutationOperation, session );
this.statementGroup = new PreparedStatementGroupSingleTable( mutationOperation, session );
this.generatedValuesDelegate = generatedValuesDelegate;
this.statementGroup = new PreparedStatementGroupSingleTable( mutationOperation, generatedValuesDelegate, session );
prepareForNonBatchedWork( null, session );
}
@ -32,18 +37,31 @@ public class MutationExecutorSingleNonBatched extends AbstractSingleMutationExec
}
@Override
protected void performNonBatchedOperations(
protected GeneratedValues performNonBatchedOperations(
Object modelReference,
ValuesAnalysis valuesAnalysis,
TableInclusionChecker inclusionChecker,
OperationResultChecker resultChecker,
SharedSessionContractImplementor session) {
performNonBatchedMutation(
statementGroup.getSingleStatementDetails(),
getJdbcValueBindings(),
inclusionChecker,
resultChecker,
session
);
if ( generatedValuesDelegate != null ) {
return generatedValuesDelegate.performMutation(
statementGroup.getSingleStatementDetails(),
getJdbcValueBindings(),
modelReference,
session
);
}
else {
performNonBatchedMutation(
statementGroup.getSingleStatementDetails(),
null,
getJdbcValueBindings(),
inclusionChecker,
resultChecker,
session
);
return null;
}
}
@Override

View File

@ -22,8 +22,13 @@ import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementGroup;
import org.hibernate.engine.jdbc.mutation.spi.BatchKeyAccess;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.sql.model.EntityMutationOperationGroup;
import org.hibernate.sql.model.MutationOperation;
import org.hibernate.sql.model.MutationOperationGroup;
import org.hibernate.sql.model.MutationType;
import org.hibernate.sql.model.PreparableMutationOperation;
import org.hibernate.sql.model.SelfExecutingUpdateOperation;
import org.hibernate.sql.model.TableMapping;
@ -49,6 +54,7 @@ public class MutationExecutorStandard extends AbstractMutationExecutor implement
* Any non-batched JDBC statements
*/
private final PreparedStatementGroup nonBatchedStatementGroup;
private final GeneratedValuesMutationDelegate generatedValuesDelegate;
/**
* Operations which handle their own execution
@ -66,6 +72,9 @@ public class MutationExecutorStandard extends AbstractMutationExecutor implement
int batchSize,
SharedSessionContractImplementor session) {
this.mutationOperationGroup = mutationOperationGroup;
this.generatedValuesDelegate = mutationOperationGroup.asEntityMutationOperationGroup() != null ?
mutationOperationGroup.asEntityMutationOperationGroup().getMutationDelegate() :
null;
final BatchKey batchKey = batchKeySupplier.getBatchKey();
@ -82,11 +91,10 @@ public class MutationExecutorStandard extends AbstractMutationExecutor implement
for ( int i = mutationOperationGroup.getNumberOfOperations() - 1; i >= 0; i-- ) {
final MutationOperation operation = mutationOperationGroup.getOperation( i );
if ( operation instanceof SelfExecutingUpdateOperation ) {
final SelfExecutingUpdateOperation selfExecutingMutation = (SelfExecutingUpdateOperation) operation;
if ( selfExecutingMutations == null ) {
selfExecutingMutations = new ArrayList<>();
}
selfExecutingMutations.add( 0, selfExecutingMutation );
selfExecutingMutations.add( 0, ( (SelfExecutingUpdateOperation) operation ) );
}
else {
final PreparableMutationOperation preparableMutationOperation = (PreparableMutationOperation) operation;
@ -126,6 +134,7 @@ public class MutationExecutorStandard extends AbstractMutationExecutor implement
this.batch = null;
}
else {
assert generatedValuesDelegate == null : "Unsupported batched mutation for entity target with generated values delegate";
final List<PreparableMutationOperation> batchedMutationsRef = batchedJdbcMutations;
this.batch = session.getJdbcCoordinator().getBatch(
batchKey,
@ -133,6 +142,7 @@ public class MutationExecutorStandard extends AbstractMutationExecutor implement
() -> ModelMutationHelper.toPreparedStatementGroup(
mutationOperationGroup.getMutationType(),
mutationOperationGroup.getMutationTarget(),
null,
batchedMutationsRef,
session
)
@ -143,6 +153,7 @@ public class MutationExecutorStandard extends AbstractMutationExecutor implement
this.nonBatchedStatementGroup = ModelMutationHelper.toPreparedStatementGroup(
mutationOperationGroup.getMutationType(),
mutationOperationGroup.getMutationTarget(),
generatedValuesDelegate,
nonBatchedJdbcMutations,
session
);
@ -202,22 +213,59 @@ public class MutationExecutorStandard extends AbstractMutationExecutor implement
}
@Override
protected void performNonBatchedOperations(
protected GeneratedValues performNonBatchedOperations(
Object modelReference,
ValuesAnalysis valuesAnalysis,
TableInclusionChecker inclusionChecker,
OperationResultChecker resultChecker,
SharedSessionContractImplementor session) {
if ( nonBatchedStatementGroup == null || nonBatchedStatementGroup.getNumberOfStatements() <= 0 ) {
return;
return null;
}
nonBatchedStatementGroup.forEachStatement( (tableName, statementDetails) -> performNonBatchedMutation(
statementDetails,
valueBindings,
inclusionChecker,
resultChecker,
session
) );
final GeneratedValues generatedValues;
if ( generatedValuesDelegate != null ) {
final EntityMutationOperationGroup entityGroup = mutationOperationGroup.asEntityMutationOperationGroup();
final EntityMutationTarget entityTarget = entityGroup.getMutationTarget();
final PreparedStatementDetails details = nonBatchedStatementGroup.getPreparedStatementDetails(
entityTarget.getIdentifierTableName()
);
generatedValues = generatedValuesDelegate.performMutation(
details,
valueBindings,
modelReference,
session
);
final Object id = entityGroup.getMutationType() == MutationType.INSERT && details.getMutatingTableDetails().isIdentifierTable() ?
generatedValues.getGeneratedValue( entityTarget.getTargetPart().getIdentifierMapping() ) :
null;
nonBatchedStatementGroup.forEachStatement( (tableName, statementDetails) -> {
if ( !statementDetails.getMutatingTableDetails().isIdentifierTable() ) {
performNonBatchedMutation(
statementDetails,
id,
valueBindings,
inclusionChecker,
resultChecker,
session
);
}
} );
}
else {
generatedValues = null;
nonBatchedStatementGroup.forEachStatement( (tableName, statementDetails) -> performNonBatchedMutation(
statementDetails,
null,
valueBindings,
inclusionChecker,
resultChecker,
session
) );
}
return generatedValues;
}
@Override

View File

@ -12,6 +12,7 @@ import java.util.function.Predicate;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementGroup;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.sql.model.PreparableMutationOperation;
import org.hibernate.sql.model.TableMapping;
@ -30,8 +31,15 @@ public class PreparedStatementGroupSingleTable implements PreparedStatementGroup
public PreparedStatementGroupSingleTable(
PreparableMutationOperation jdbcMutation,
SharedSessionContractImplementor session) {
this( jdbcMutation, null, session );
}
public PreparedStatementGroupSingleTable(
PreparableMutationOperation jdbcMutation,
GeneratedValuesMutationDelegate delegate,
SharedSessionContractImplementor session) {
this.jdbcMutation = jdbcMutation;
this.statementDetails = ModelMutationHelper.standardPreparation( jdbcMutation, session );
this.statementDetails = ModelMutationHelper.standardPreparation( jdbcMutation, delegate, session );
this.session = session;
}

View File

@ -22,7 +22,7 @@ import org.hibernate.engine.jdbc.mutation.group.PreparedStatementGroup;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.MutationStatementPreparer;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.sql.model.MutationTarget;
import org.hibernate.sql.model.MutationType;
import org.hibernate.sql.model.PreparableMutationOperation;
@ -46,6 +46,7 @@ public class PreparedStatementGroupStandard implements PreparedStatementGroup {
public PreparedStatementGroupStandard(
MutationType mutationType,
MutationTarget<?> mutationTarget,
GeneratedValuesMutationDelegate generatedValuesDelegate,
List<PreparableMutationOperation> jdbcMutations,
SharedSessionContractImplementor session) {
this.mutationType = mutationType;
@ -54,7 +55,7 @@ public class PreparedStatementGroupStandard implements PreparedStatementGroup {
this.session = session;
this.statementMap = createStatementDetailsMap( jdbcMutations, mutationType, mutationTarget, session );
this.statementMap = createStatementDetailsMap( jdbcMutations, mutationType, generatedValuesDelegate, session );
}
@Override
@ -112,8 +113,7 @@ public class PreparedStatementGroupStandard implements PreparedStatementGroup {
private static PreparedStatementDetails createPreparedStatementDetails(
PreparableMutationOperation jdbcMutation,
MutationType mutationType,
MutationTarget<?> mutationTarget,
GeneratedValuesMutationDelegate generatedValuesDelegate,
SharedSessionContractImplementor session) {
final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
final MutationStatementPreparer statementPreparer = jdbcCoordinator.getMutationStatementPreparer();
@ -121,11 +121,8 @@ public class PreparedStatementGroupStandard implements PreparedStatementGroup {
final TableMapping tableDetails = jdbcMutation.getTableDetails();
final Supplier<PreparedStatement> jdbcStatementCreator;
if ( mutationType == MutationType.INSERT
&& mutationTarget instanceof EntityMutationTarget
&& ( (EntityMutationTarget) mutationTarget ).getIdentityInsertDelegate() != null
&& tableDetails.getTableName().equals( mutationTarget.getIdentifierTableName() ) ) {
jdbcStatementCreator = () -> ( (EntityMutationTarget) mutationTarget ).getIdentityInsertDelegate().prepareStatement(
if ( tableDetails.isIdentifierTable() && generatedValuesDelegate != null ) {
jdbcStatementCreator = () -> generatedValuesDelegate.prepareStatement(
jdbcMutation.getSqlString(),
session
);
@ -152,26 +149,26 @@ public class PreparedStatementGroupStandard implements PreparedStatementGroup {
}
private SortedMap<String, PreparedStatementDetails> createStatementDetailsMap(
private static SortedMap<String, PreparedStatementDetails> createStatementDetailsMap(
List<PreparableMutationOperation> jdbcMutations,
MutationType mutationType,
MutationTarget<?> mutationTarget,
GeneratedValuesMutationDelegate generatedValuesDelegate,
SharedSessionContractImplementor session) {
final Comparator<String> comparator;
if ( mutationType == MutationType.DELETE ) {
// reverse order
comparator = Comparator.comparingInt( (tableName) -> {
final TableMapping tableMapping = locateTableMapping( tableName );
final TableMapping tableMapping = locateTableMapping( jdbcMutations, tableName );
if ( tableMapping == null ) {
return -1;
}
return this.jdbcMutations.size() - tableMapping.getRelativePosition();
return jdbcMutations.size() - tableMapping.getRelativePosition();
} );
}
else {
comparator = Comparator.comparingInt( (tableName) -> {
final TableMapping tableMapping = locateTableMapping( tableName );
final TableMapping tableMapping = locateTableMapping( jdbcMutations, tableName );
if ( tableMapping == null ) {
return -1;
}
@ -181,20 +178,19 @@ public class PreparedStatementGroupStandard implements PreparedStatementGroup {
final TreeMap<String, PreparedStatementDetails> map = new TreeMap<>( comparator );
for ( int i = 0; i < jdbcMutations.size(); i++ ) {
final PreparableMutationOperation jdbcMutation = jdbcMutations.get( i );
for ( final PreparableMutationOperation jdbcMutation : jdbcMutations ) {
map.put(
jdbcMutation.getTableDetails().getTableName(),
createPreparedStatementDetails( jdbcMutation, mutationType, mutationTarget, session )
createPreparedStatementDetails( jdbcMutation, generatedValuesDelegate, session )
);
}
return map;
}
private TableMapping locateTableMapping(String name) {
for ( int i = 0; i < jdbcMutations.size(); i++ ) {
final TableMapping tableMapping = jdbcMutations.get( i ).getTableDetails();
private static TableMapping locateTableMapping(List<PreparableMutationOperation> jdbcMutations, String name) {
for ( final PreparableMutationOperation jdbcMutation : jdbcMutations ) {
final TableMapping tableMapping = jdbcMutation.getTableDetails();
if ( tableMapping.getTableName().equals( name ) ) {
return tableMapping;
}

View File

@ -15,10 +15,8 @@ import org.hibernate.engine.jdbc.mutation.spi.BatchKeyAccess;
import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.sql.model.EntityMutationOperationGroup;
import org.hibernate.sql.model.MutationOperation;
import org.hibernate.sql.model.MutationOperationGroup;
import org.hibernate.sql.model.MutationType;
import org.hibernate.sql.model.PreparableMutationOperation;
import org.hibernate.sql.model.SelfExecutingUpdateOperation;
@ -53,20 +51,7 @@ public class StandardMutationExecutorService implements MutationExecutorService
? globalBatchSize
: sessionBatchSize;
final int numberOfOperations = operationGroup.getNumberOfOperations();
final MutationType mutationType = operationGroup.getMutationType();
final EntityMutationOperationGroup entityMutationOperationGroup = operationGroup.asEntityMutationOperationGroup();
if ( mutationType == MutationType.INSERT
&& entityMutationOperationGroup != null
&& entityMutationOperationGroup.getMutationTarget().getIdentityInsertDelegate() != null ) {
if ( numberOfOperations > 1 ) {
return new MutationExecutorPostInsert( entityMutationOperationGroup, session );
}
return new MutationExecutorPostInsertSingleTable( entityMutationOperationGroup, session );
}
if ( numberOfOperations == 1 ) {
if ( operationGroup.getNumberOfOperations() == 1 ) {
final MutationOperation singleOperation = operationGroup.getSingleOperation();
if ( singleOperation instanceof SelfExecutingUpdateOperation ) {
return new MutationExecutorSingleSelfExecuting( (SelfExecutingUpdateOperation) singleOperation, session );
@ -78,7 +63,13 @@ public class StandardMutationExecutorService implements MutationExecutorService
return new MutationExecutorSingleBatched( jdbcOperation, batchKey, batchSizeToUse, session );
}
return new MutationExecutorSingleNonBatched( jdbcOperation, session );
return new MutationExecutorSingleNonBatched(
jdbcOperation,
operationGroup.asEntityMutationOperationGroup() != null ?
operationGroup.asEntityMutationOperationGroup().getMutationDelegate() :
null,
session
);
}
return new MutationExecutorStandard( operationGroup, batchKeySupplier, batchSizeToUse, session );

View File

@ -744,6 +744,9 @@ public interface PersistenceContext {
void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Object generatedId);
@Internal
void replaceEntityEntryRowId(Object entity, Object rowId);
/**
* Add a child/parent relation to cache for cascading op
*

View File

@ -16,6 +16,7 @@ import org.hibernate.id.insert.UniqueKeySelectingDelegate;
import org.hibernate.persister.entity.EntityPersister;
import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.noCustomSql;
/**
* A generator which produces a new value by actually going ahead and writing a row to the
@ -116,16 +117,21 @@ public interface OnExecutionGenerator extends Generator {
*/
@Incubating
default InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(PostInsertIdentityPersister persister) {
Dialect dialect = persister.getFactory().getJdbcServices().getDialect();
if ( dialect.supportsInsertReturningGeneratedKeys() ) {
return new GetGeneratedKeysDelegate( persister, dialect, false );
final Dialect dialect = persister.getFactory().getJdbcServices().getDialect();
if ( dialect.supportsInsertReturningGeneratedKeys()
&& persister.getFactory().getSessionFactoryOptions().isGetGeneratedKeysEnabled() ) {
return new GetGeneratedKeysDelegate( persister, false, EventType.INSERT );
}
else if ( dialect.supportsInsertReturning() ) {
return new InsertReturningDelegate( persister, dialect );
else if ( dialect.supportsInsertReturning() && noCustomSql( persister, EventType.INSERT ) ) {
return new InsertReturningDelegate( persister, EventType.INSERT );
}
else {
// let's just hope the entity has a @NaturalId!
return new UniqueKeySelectingDelegate( persister, dialect, getUniqueKeyPropertyNames( persister ) );
return new UniqueKeySelectingDelegate(
persister,
getUniqueKeyPropertyNames( persister ),
EventType.INSERT
);
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.generator.values;
import org.hibernate.dialect.Dialect;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.internal.GeneratedValuesHelper;
import org.hibernate.generator.values.internal.GeneratedValuesMappingProducer;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer;
/**
* @author Marco Belladelli
*/
public abstract class AbstractGeneratedValuesMutationDelegate implements GeneratedValuesMutationDelegate {
protected final EntityPersister persister;
private final EventType timing;
private final boolean supportsArbitraryValues;
private final boolean supportsRowId;
protected final GeneratedValuesMappingProducer jdbcValuesMappingProducer;
public AbstractGeneratedValuesMutationDelegate(EntityPersister persister, EventType timing) {
this( persister, timing, true, true );
}
public AbstractGeneratedValuesMutationDelegate(
EntityPersister persister,
EventType timing,
boolean supportsArbitraryValues,
boolean supportsRowId) {
this.persister = persister;
this.timing = timing;
this.supportsArbitraryValues = supportsArbitraryValues;
this.supportsRowId = supportsRowId;
this.jdbcValuesMappingProducer = GeneratedValuesHelper.createMappingProducer(
persister,
timing,
supportsArbitraryValues,
supportsRowId
);
}
@Override
public EventType getTiming() {
return timing;
}
@Override
public final boolean supportsArbitraryValues() {
return supportsArbitraryValues;
}
@Override
public final boolean supportsRowId() {
return supportsRowId;
}
@Override
public JdbcValuesMappingProducer getGeneratedValuesMappingProducer() {
return jdbcValuesMappingProducer;
}
protected Dialect dialect() {
return persister.getFactory().getJdbcServices().getDialect();
}
}

View File

@ -0,0 +1,121 @@
/*
* 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.generator.values;
import java.util.function.BiFunction;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.query.results.DomainResultCreationStateImpl;
import org.hibernate.query.results.ResultBuilder;
import org.hibernate.query.results.ResultsHelper;
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getActualGeneratedModelPart;
import static org.hibernate.query.results.ResultsHelper.impl;
import static org.hibernate.query.results.ResultsHelper.jdbcPositionToValuesArrayPosition;
/**
* Simple implementation of {@link ResultBuilder} for retrieving generated basic values.
*
* @author Marco Belladelli
* @see GeneratedValuesMutationDelegate
*/
public class GeneratedValueBasicResultBuilder implements ResultBuilder {
private final NavigablePath navigablePath;
private final BasicValuedModelPart modelPart;
private final Integer valuesArrayPosition;
private final TableGroup tableGroup;
public GeneratedValueBasicResultBuilder(
NavigablePath navigablePath,
BasicValuedModelPart modelPart,
TableGroup tableGroup,
Integer valuesArrayPosition) {
this.navigablePath = navigablePath;
this.modelPart = modelPart;
this.valuesArrayPosition = valuesArrayPosition;
this.tableGroup = tableGroup;
}
@Override
public Class<?> getJavaType() {
return modelPart.getExpressibleJavaType().getJavaTypeClass();
}
@Override
public ResultBuilder cacheKeyInstance() {
return this;
}
@Override
public BasicResult<?> buildResult(
JdbcValuesMetadata jdbcResultsMetadata,
int resultPosition,
BiFunction<String, String, DynamicFetchBuilderLegacy> legacyFetchResolver,
DomainResultCreationState domainResultCreationState) {
final DomainResultCreationStateImpl creationStateImpl = impl( domainResultCreationState );
final TableGroup tableGroup = creationStateImpl.getFromClauseAccess().resolveTableGroup(
navigablePath.getParent(),
path -> this.tableGroup
);
final TableReference tableReference = tableGroup.resolveTableReference(
navigablePath,
modelPart,
"t"
);
final int position = valuesArrayPosition == null ?
columnIndex( jdbcResultsMetadata, modelPart ) :
valuesArrayPosition;
final SqlSelection sqlSelection = creationStateImpl.resolveSqlSelection(
ResultsHelper.resolveSqlExpression(
creationStateImpl,
tableReference,
modelPart,
position
),
modelPart.getJdbcMapping().getJdbcJavaType(),
null,
creationStateImpl.getSessionFactory().getTypeConfiguration()
);
return new BasicResult<>(
sqlSelection.getValuesArrayPosition(),
null,
modelPart.getJdbcMapping()
);
}
public BasicValuedModelPart getModelPart() {
return modelPart;
}
private static int columnIndex(JdbcValuesMetadata jdbcResultsMetadata, BasicValuedModelPart modelPart) {
try {
return jdbcPositionToValuesArrayPosition( jdbcResultsMetadata.resolveColumnPosition(
getActualGeneratedModelPart( modelPart ).getSelectionExpression()
) );
}
catch (Exception e) {
if ( modelPart.isEntityIdentifierMapping() ) {
// Default to the first position for entity identifiers
return 0;
}
else {
throw e;
}
}
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.generator.values;
import java.util.List;
import org.hibernate.metamodel.mapping.ModelPart;
/**
* A container for {@linkplain org.hibernate.generator.OnExecutionGenerator database generated values}
* retrieved by the mutation operation. The values are stored relative to the {@linkplain ModelPart property}
* they represent.
*
* @author Marco Belladelli
* @see org.hibernate.generator.OnExecutionGenerator
*/
public interface GeneratedValues {
/**
* Register a generated value for the corresponding {@link ModelPart}
*/
void addGeneratedValue(ModelPart modelPart, Object value);
/**
* Retrieve a generated value for the requested {@link ModelPart}.
*/
Object getGeneratedValue(ModelPart modelPart);
/**
* Retrieves a list of generated values corresponding to the list of requested {@link ModelPart}s.
* Ensures the order of the values in the returned list corresponds to the input properties.
*/
List<Object> getGeneratedValues(List<? extends ModelPart> modelParts);
}

View File

@ -0,0 +1,77 @@
/*
* 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.generator.values;
import java.sql.PreparedStatement;
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.jdbc.Expectation;
import org.hibernate.sql.model.ast.builder.TableMutationBuilder;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer;
/**
* Each implementation defines a strategy for retrieving values
* {@linkplain org.hibernate.generator.OnExecutionGenerator generated by
* the database} after execution of a mutation statement.
* <p>
* An implementation controls:
* <ul>
* <li>building the SQL mutation statement, and
* <li>retrieving the generated values using JDBC.
* </ul>
*
* @author Marco Belladelli
* @see org.hibernate.generator.OnExecutionGenerator
* @since 6.5
*/
public interface GeneratedValuesMutationDelegate {
/**
* Create a {@link TableMutationBuilder} instance used to build table mutations for this delegate.
*/
TableMutationBuilder<?> createTableMutationBuilder(Expectation expectation, SessionFactoryImplementor sessionFactory);
/**
* Create a {@link PreparedStatement} from the provided {@code sql} based on the delegate needs.
*/
PreparedStatement prepareStatement(String sql, SharedSessionContractImplementor session);
/**
* Perform the {@code mutation} and extract the database-generated values.
*
* @see #createTableMutationBuilder
*/
GeneratedValues performMutation(
PreparedStatementDetails statementDetails,
JdbcValueBindings valueBindings,
Object entity,
SharedSessionContractImplementor session);
/**
* Returns the timing this generated values delegate handles.
*/
EventType getTiming();
/**
* Returns {@code true} when this delegate supports retrieving arbitrary generated values,
* or {@code false} when it only supports identifiers.
*/
boolean supportsArbitraryValues();
/**
* Returns {@code true} when this delegate supports retrieving the {@link org.hibernate.annotations.RowId} value.
*/
boolean supportsRowId();
/**
* Retrieve the {@linkplain JdbcValuesMappingProducer mapping producer} used to read the generated values.
*/
JdbcValuesMappingProducer getGeneratedValuesMappingProducer();
}

View File

@ -0,0 +1,376 @@
/*
* 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.generator.values.internal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Internal;
import org.hibernate.LockOptions;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.GeneratedValueBasicResultBuilder;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.id.IdentifierGeneratorHelper;
import org.hibernate.id.insert.GetGeneratedKeysDelegate;
import org.hibernate.id.insert.InsertReturningDelegate;
import org.hibernate.id.insert.UniqueKeySelectingDelegate;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.mutation.EntityTableMapping;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.query.results.TableGroupImpl;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.exec.internal.BaseExecutionContext;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.model.MutationType;
import org.hibernate.sql.model.TableMapping;
import org.hibernate.sql.results.internal.ResultsHelper;
import org.hibernate.sql.results.internal.RowProcessingStateStandardImpl;
import org.hibernate.sql.results.internal.RowTransformerArrayImpl;
import org.hibernate.sql.results.jdbc.internal.DirectResultSetAccess;
import org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl;
import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl;
import org.hibernate.sql.results.jdbc.spi.JdbcValues;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.sql.results.spi.RowReader;
import org.hibernate.type.descriptor.WrapperOptions;
import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames;
/**
* Factory and helper methods for {@link GeneratedValuesMutationDelegate} framework.
*
* @author Marco Belladelli
*/
@Internal
public class GeneratedValuesHelper {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( IdentifierGeneratorHelper.class );
/**
* Reads the {@link EntityPersister#getGeneratedProperties(EventType) generated values}
* for the specified {@link ResultSet}.
*
* @param resultSet The result set from which to extract the generated values
* @param persister The entity type which we're reading the generated values for
* @param wrapperOptions The session
*
* @return The generated values
*
* @throws SQLException Can be thrown while accessing the result set
* @throws HibernateException Indicates a problem reading back a generated value
*/
public static GeneratedValues getGeneratedValues(
ResultSet resultSet,
EntityPersister persister,
EventType timing,
WrapperOptions wrapperOptions) throws SQLException {
if ( resultSet == null ) {
return null;
}
final GeneratedValuesMutationDelegate delegate = persister.getMutationDelegate(
timing == EventType.INSERT ? MutationType.INSERT : MutationType.UPDATE
);
final GeneratedValuesMappingProducer mappingProducer =
(GeneratedValuesMappingProducer) delegate.getGeneratedValuesMappingProducer();
final List<GeneratedValueBasicResultBuilder> resultBuilders = mappingProducer.getResultBuilders();
final List<ModelPart> generatedProperties = new ArrayList<>( resultBuilders.size() );
for ( GeneratedValueBasicResultBuilder resultBuilder : resultBuilders ) {
generatedProperties.add( resultBuilder.getModelPart() );
}
final GeneratedValuesImpl generatedValues = new GeneratedValuesImpl( generatedProperties );
final Object[] results = readGeneratedValues(
resultSet,
persister,
mappingProducer,
wrapperOptions.getSession()
);
if ( LOG.isDebugEnabled() ) {
LOG.debugf(
"Extracted generated values %s: %s",
MessageHelper.infoString( persister ),
results
);
}
for ( int i = 0; i < results.length; i++ ) {
generatedValues.addGeneratedValue( generatedProperties.get( i ), results[i] );
}
return generatedValues;
}
/**
* Utility method that reads the generated values from the specified {@link ResultSet}
* using the {@link JdbcValuesMappingProducer} provided in input.
*
* @param resultSet the result set containing the generated values
* @param persister the current entity persister
* @param mappingProducer the mapping producer to use when reading generated values
* @param session the current session
*
* @return an object array containing the generated values, order is consistent with the generated model parts list
*/
private static Object[] readGeneratedValues(
ResultSet resultSet,
EntityPersister persister,
JdbcValuesMappingProducer mappingProducer,
SharedSessionContractImplementor session) {
final ExecutionContext executionContext = new BaseExecutionContext( session );
final DirectResultSetAccess directResultSetAccess;
try {
directResultSetAccess = new DirectResultSetAccess(
session,
(PreparedStatement) resultSet.getStatement(),
resultSet
);
}
catch (SQLException e) {
throw new HibernateException( "Could not retrieve statement from generated values result set", e );
}
final JdbcValues jdbcValues = new JdbcValuesResultSetImpl(
directResultSetAccess,
null,
null,
QueryOptions.NONE,
mappingProducer.resolve(
directResultSetAccess,
session.getLoadQueryInfluencers(),
session.getSessionFactory()
),
null,
executionContext
);
final JdbcValuesSourceProcessingOptions processingOptions = new JdbcValuesSourceProcessingOptions() {
@Override
public Object getEffectiveOptionalObject() {
return null;
}
@Override
public String getEffectiveOptionalEntityName() {
return null;
}
@Override
public Object getEffectiveOptionalId() {
return null;
}
@Override
public boolean shouldReturnProxies() {
return true;
}
};
final JdbcValuesSourceProcessingStateStandardImpl valuesProcessingState = new JdbcValuesSourceProcessingStateStandardImpl(
executionContext,
processingOptions
);
final RowReader<Object[]> rowReader = ResultsHelper.createRowReader(
executionContext,
LockOptions.NONE,
RowTransformerArrayImpl.instance(),
Object[].class,
jdbcValues
);
final RowProcessingStateStandardImpl rowProcessingState = new RowProcessingStateStandardImpl(
valuesProcessingState,
executionContext,
rowReader,
jdbcValues
);
final List<Object[]> results = ListResultsConsumer.<Object[]>instance( ListResultsConsumer.UniqueSemantic.NONE )
.consume(
jdbcValues,
session,
processingOptions,
valuesProcessingState,
rowProcessingState,
rowReader
);
if ( results.isEmpty() ) {
throw new HibernateException(
"The database returned no natively generated values : " + persister.getNavigableRole().getFullPath()
);
}
return results.get( 0 );
}
/**
* Utility method that instantiates a {@link JdbcValuesMappingProducer} so it can be cached by the
* {@link GeneratedValuesMutationDelegate delegates} when they are instantiated.
*
* @param persister the current entity persister
* @param timing the timing of the mutation operation
* @param supportsArbitraryValues if we should process arbitrary (non-identifier) generated values
* @param supportsRowId if we should process {@link org.hibernate.metamodel.mapping.EntityRowIdMapping rowid}s
* {@code false} if we should retrieve the index through the column expression
*
* @return the instantiated jdbc values mapping producer
*/
public static GeneratedValuesMappingProducer createMappingProducer(
EntityPersister persister,
EventType timing,
boolean supportsArbitraryValues,
boolean supportsRowId) {
// This is just a mock table group needed to correctly resolve expressions
final NavigablePath parentNavigablePath = new NavigablePath( persister.getEntityName() );
final TableGroup tableGroup = new TableGroupImpl(
parentNavigablePath,
null,
new NamedTableReference( "t", "t" ),
persister
);
// Create the mapping producer and add all result builders to it
final List<? extends ModelPart> generatedProperties = getActualGeneratedModelParts(
persister,
timing,
supportsArbitraryValues,
supportsRowId
);
final GeneratedValuesMappingProducer mappingProducer = new GeneratedValuesMappingProducer();
for ( int i = 0; i < generatedProperties.size(); i++ ) {
final ModelPart modelPart = generatedProperties.get( i );
final BasicValuedModelPart basicModelPart = modelPart.asBasicValuedModelPart();
if ( basicModelPart != null ) {
final GeneratedValueBasicResultBuilder resultBuilder = new GeneratedValueBasicResultBuilder(
parentNavigablePath.append( basicModelPart.getSelectableName() ),
basicModelPart,
tableGroup,
supportsArbitraryValues ? i : null
);
mappingProducer.addResultBuilder( resultBuilder );
}
else {
throw new UnsupportedOperationException( "Unsupported generated ModelPart: " + modelPart.getPartName() );
}
}
return mappingProducer;
}
public static BasicValuedModelPart getActualGeneratedModelPart(BasicValuedModelPart modelPart) {
// Use the root entity descriptor's identifier mapping to get the correct selection
// expression since we always retrieve generated values for the root table only
return modelPart.isEntityIdentifierMapping() ?
modelPart.findContainingEntityMapping()
.getRootEntityDescriptor()
.getIdentifierMapping()
.asBasicValuedModelPart() :
modelPart;
}
/**
* Returns a list of {@link ModelPart}s that represent the actual generated values
* based on timing and the support flags passed in input.
*/
private static List<? extends ModelPart> getActualGeneratedModelParts(
EntityPersister persister,
EventType timing,
boolean supportsArbitraryValues,
boolean supportsRowId) {
if ( timing == EventType.INSERT ) {
final List<? extends ModelPart> generatedProperties = supportsArbitraryValues ?
persister.getInsertGeneratedProperties() :
List.of( persister.getIdentifierMapping() );
if ( persister.getRowIdMapping() != null && supportsRowId ) {
final List<ModelPart> newList = new ArrayList<>( generatedProperties.size() + 1 );
newList.addAll( generatedProperties );
newList.add( persister.getRowIdMapping() );
return Collections.unmodifiableList( newList );
}
else {
return generatedProperties;
}
}
else {
return persister.getUpdateGeneratedProperties();
}
}
/**
* Creates the {@link GeneratedValuesMutationDelegate delegate} used to retrieve
* {@linkplain org.hibernate.generator.OnExecutionGenerator database generated values} on
* mutation execution through e.g. {@link Dialect#supportsInsertReturning() insert ... returning}
* syntax or the JDBC {@link Dialect#supportsInsertReturningGeneratedKeys() getGeneratedKeys()} API.
* <p>
* If the current {@link Dialect} doesn't support any of the available delegates this method returns {@code null}.
*/
public static GeneratedValuesMutationDelegate getGeneratedValuesDelegate(
EntityPersister persister,
EventType timing) {
final boolean hasGeneratedProperties = !persister.getGeneratedProperties( timing ).isEmpty();
final boolean hasRowId = timing == EventType.INSERT && persister.getRowIdMapping() != null;
final Dialect dialect = persister.getFactory().getJdbcServices().getDialect();
if ( hasRowId && dialect.supportsInsertReturning() && dialect.supportsInsertReturningRowId()
&& noCustomSql( persister, timing ) ) {
// Special case for RowId on INSERT, since GetGeneratedKeysDelegate doesn't support it
// make InsertReturningDelegate the preferred method if the dialect supports it
return new InsertReturningDelegate( persister, timing );
}
if ( !hasGeneratedProperties ) {
return null;
}
if ( dialect.supportsInsertReturningGeneratedKeys()
&& persister.getFactory().getSessionFactoryOptions().isGetGeneratedKeysEnabled() ) {
return new GetGeneratedKeysDelegate( persister, false, timing );
}
else if ( supportsReturning( dialect, timing ) && noCustomSql( persister, timing ) ) {
return new InsertReturningDelegate( persister, timing );
}
else if ( timing == EventType.INSERT && persister.getNaturalIdentifierProperties() != null
&& !persister.getEntityMetamodel().isNaturalIdentifierInsertGenerated() ) {
return new UniqueKeySelectingDelegate(
persister,
getNaturalIdPropertyNames( persister ),
timing
);
}
return null;
}
private static boolean supportsReturning(Dialect dialect, EventType timing) {
return timing == EventType.INSERT ? dialect.supportsInsertReturning() : dialect.supportsUpdateReturning();
}
public static boolean noCustomSql(EntityPersister persister, EventType timing) {
final EntityTableMapping identifierTable = persister.getIdentifierTableMapping();
final TableMapping.MutationDetails mutationDetails = timing == EventType.INSERT ?
identifierTable.getInsertDetails() :
identifierTable.getUpdateDetails();
return mutationDetails.getCustomSql() == null;
}
}

View File

@ -0,0 +1,55 @@
/*
* 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.generator.values.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.ModelPart;
/**
* Standard implementation for {@link GeneratedValues} using {@link IdentityHashMap}.
*
* @author Marco Belladelli
*/
public class GeneratedValuesImpl implements GeneratedValues {
private final Map<ModelPart, Object> generatedValuesMap;
public GeneratedValuesImpl(List<? extends ModelPart> generatedProperties) {
this.generatedValuesMap = new IdentityHashMap<>( generatedProperties.size() );
}
@Override
public void addGeneratedValue(ModelPart modelPart, Object value) {
generatedValuesMap.put( modelPart, value );
}
@Override
public Object getGeneratedValue(ModelPart modelPart) {
return generatedValuesMap.get( modelPart );
}
@Override
public List<Object> getGeneratedValues(List<? extends ModelPart> modelParts) {
if ( CollectionHelper.isEmpty( modelParts ) ) {
return Collections.emptyList();
}
final List<Object> generatedValues = new ArrayList<>( modelParts.size() );
for ( ModelPart modelPart : modelParts ) {
assert generatedValuesMap.containsKey( modelPart );
generatedValues.add( generatedValuesMap.get( modelPart ) );
}
return generatedValues;
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.generator.values.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.generator.values.GeneratedValueBasicResultBuilder;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.query.results.DomainResultCreationStateImpl;
import org.hibernate.query.results.JdbcValuesMappingImpl;
import org.hibernate.query.results.ResultBuilder;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
/**
* Simple implementation of {@link JdbcValuesMappingProducer} used when reading
* generated values from a mutation statement.
*
* @author Marco Belladelli
* @see GeneratedValuesMutationDelegate
* @see GeneratedValuesHelper#getGeneratedValues
*/
public class GeneratedValuesMappingProducer implements JdbcValuesMappingProducer {
private final List<GeneratedValueBasicResultBuilder> resultBuilders = new ArrayList<>();
@Override
public JdbcValuesMapping resolve(
JdbcValuesMetadata jdbcResultsMetadata,
LoadQueryInfluencers loadQueryInfluencers,
SessionFactoryImplementor sessionFactory) {
final int numberOfResults = resultBuilders.size();
final int rowSize = jdbcResultsMetadata.getColumnCount();
final List<SqlSelection> sqlSelections = new ArrayList<>( rowSize );
final List<DomainResult<?>> domainResults = new ArrayList<>( numberOfResults );
final DomainResultCreationStateImpl creationState = new DomainResultCreationStateImpl(
null,
jdbcResultsMetadata,
null,
sqlSelections::add,
loadQueryInfluencers,
sessionFactory
);
for ( int i = 0; i < numberOfResults; i++ ) {
final ResultBuilder resultBuilder = resultBuilders.get( i );
final DomainResult<?> domainResult = resultBuilder.buildResult(
jdbcResultsMetadata,
domainResults.size(),
creationState.getLegacyFetchResolver()::resolve,
creationState
);
if ( domainResult.containsAnyNonScalarResults() ) {
creationState.disallowPositionalSelections();
}
domainResults.add( domainResult );
}
return new JdbcValuesMappingImpl(
sqlSelections,
domainResults,
rowSize,
creationState.getRegisteredLockModes()
);
}
public void addResultBuilder(GeneratedValueBasicResultBuilder resultBuilder) {
resultBuilders.add( resultBuilder );
}
public List<GeneratedValueBasicResultBuilder> getResultBuilders() {
return resultBuilders;
}
@Override
public void addAffectedTableNames(Set<String> affectedTableNames, SessionFactoryImplementor sessionFactory) {
// nothing to do
}
}

View File

@ -0,0 +1,55 @@
/*
* 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.generator.values.internal;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.model.MutationOperation;
import org.hibernate.sql.model.ast.MutatingTableReference;
import org.hibernate.sql.model.ast.RestrictedTableMutation;
import org.hibernate.sql.model.ast.builder.AbstractTableUpdateBuilder;
import org.hibernate.sql.model.internal.TableUpdateStandard;
/**
* @author Marco Belladelli
*/
public class TableUpdateReturningBuilder<O extends MutationOperation> extends AbstractTableUpdateBuilder<O> {
final List<ColumnReference> generatedColumns;
public TableUpdateReturningBuilder(
EntityPersister mutationTarget,
MutatingTableReference tableReference,
List<ColumnReference> generatedColumns,
SessionFactoryImplementor sessionFactory) {
super( mutationTarget, tableReference, sessionFactory );
this.generatedColumns = generatedColumns;
}
@Override
protected EntityPersister getMutationTarget() {
return (EntityPersister) super.getMutationTarget();
}
@Override
@SuppressWarnings( "unchecked" )
public RestrictedTableMutation<O> buildMutation() {
return (RestrictedTableMutation<O>) new TableUpdateStandard(
getMutatingTable(),
getMutationTarget(),
getSqlComment(),
combine( getValueBindings(), getKeyBindings(), getLobValueBindings() ),
getKeyRestrictionBindings(),
getOptimisticLockBindings(),
null,
null,
generatedColumns
);
}
}

View File

@ -0,0 +1,14 @@
/*
* 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
*/
/**
* Contains a framework of strategies for efficient retrieval of
* {@linkplain org.hibernate.generator.OnExecutionGenerator database-generated values}.
*
* @see org.hibernate.generator.values.GeneratedValuesMutationDelegate
*/
package org.hibernate.generator.values;

View File

@ -6,17 +6,6 @@
*/
package org.hibernate.id;
import org.hibernate.HibernateException;
import org.hibernate.Internal;
import org.hibernate.dialect.Dialect;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.SqlTypedMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.type.descriptor.WrapperOptions;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -27,6 +16,18 @@ import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Objects;
import org.hibernate.HibernateException;
import org.hibernate.Internal;
import org.hibernate.dialect.Dialect;
import org.hibernate.generator.values.internal.GeneratedValuesHelper;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.SqlTypedMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.type.descriptor.WrapperOptions;
/**
* Factory and helper methods for {@link IdentifierGenerator} framework.
*
@ -76,7 +77,10 @@ public final class IdentifierGeneratorHelper {
* @return The generated identity value
* @throws SQLException Can be thrown while accessing the result set
* @throws HibernateException Indicates a problem reading back a generated identity value.
*
* @deprecated Use {@link GeneratedValuesHelper#getGeneratedValues} instead
*/
@Deprecated( since = "6.5", forRemoval = true )
public static Object getGeneratedIdentity(
String path,
ResultSet resultSet,
@ -112,7 +116,7 @@ public final class IdentifierGeneratorHelper {
private static boolean equal(String keyColumnName, String alias, Dialect dialect) {
return alias.equalsIgnoreCase( keyColumnName )
|| alias.equalsIgnoreCase( StringHelper.unquote( keyColumnName, dialect ) );
|| alias.equalsIgnoreCase( StringHelper.unquote( keyColumnName, dialect ) );
}
public static IntegralDataTypeHolder getIntegralDataTypeHolder(Class<?> integralType) {

View File

@ -7,11 +7,17 @@
package org.hibernate.id;
import org.hibernate.dialect.Dialect;
import org.hibernate.id.factory.spi.StandardGenerator;
import org.hibernate.generator.EventType;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.id.factory.spi.StandardGenerator;
import org.hibernate.id.insert.BasicSelectingDelegate;
import org.hibernate.id.insert.GetGeneratedKeysDelegate;
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
import org.hibernate.id.insert.InsertReturningDelegate;
import org.hibernate.id.insert.UniqueKeySelectingDelegate;
import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.noCustomSql;
/**
* An {@link OnExecutionGenerator} that handles {@code IDENTITY}/"autoincrement"
@ -21,16 +27,11 @@ import org.hibernate.id.insert.InsertReturningDelegate;
* provided by the {@linkplain Dialect#getIdentityColumnSupport() dialect}.
* <p>
* The actual work involved in retrieving the primary key value is the job of a
* {@link org.hibernate.id.insert.InsertGeneratedIdentifierDelegate}, either:
* <ul>
* <li>a {@link org.hibernate.id.insert.GetGeneratedKeysDelegate},
* <li>an {@link org.hibernate.id.insert.InsertReturningDelegate}, or a
* <li>a {@link org.hibernate.id.insert.BasicSelectingDelegate}.
* </ul>
* {@link org.hibernate.generator.values.GeneratedValuesMutationDelegate}.
*
* @see jakarta.persistence.GenerationType#IDENTITY
* @see org.hibernate.dialect.identity.IdentityColumnSupport
* @see org.hibernate.id.insert.InsertGeneratedIdentifierDelegate
* @see org.hibernate.generator.values.GeneratedValuesMutationDelegate
*
* @author Christoph Sturm
*
@ -52,14 +53,28 @@ public class IdentityGenerator
@Override
public InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(PostInsertIdentityPersister persister) {
final Dialect dialect = persister.getFactory().getJdbcServices().getDialect();
if ( persister.getFactory().getSessionFactoryOptions().isGetGeneratedKeysEnabled() ) {
// Try to use generic delegates if the dialects supports them
if ( dialect.supportsInsertReturningGeneratedKeys()
&& persister.getFactory().getSessionFactoryOptions().isGetGeneratedKeysEnabled() ) {
return new GetGeneratedKeysDelegate( persister, false, EventType.INSERT );
}
else if ( dialect.supportsInsertReturning() && noCustomSql( persister, EventType.INSERT ) ) {
return new InsertReturningDelegate( persister, EventType.INSERT );
}
// Fall back to delegates which only handle identifiers
else if ( persister.getFactory().getSessionFactoryOptions().isGetGeneratedKeysEnabled() ) {
return dialect.getIdentityColumnSupport().buildGetGeneratedKeysDelegate( persister, dialect );
}
else if ( dialect.getIdentityColumnSupport().supportsInsertSelectIdentity() ) {
return new InsertReturningDelegate( persister, dialect );
else if ( persister.getNaturalIdentifierProperties() != null
&& !persister.getEntityMetamodel().isNaturalIdentifierInsertGenerated() ) {
return new UniqueKeySelectingDelegate(
persister,
getNaturalIdPropertyNames( persister ),
EventType.INSERT
);
}
else {
return new BasicSelectingDelegate( persister, dialect );
return new BasicSelectingDelegate( persister );
}
}
}

View File

@ -7,55 +7,30 @@
package org.hibernate.id;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
/**
* A persister that may have an identity assigned by execution of
* A persister that may have an identity assigned by execution of
* a SQL {@code INSERT}.
*
* @author Gavin King
* @deprecated Use {@link EntityPersister} instead.
*/
public interface PostInsertIdentityPersister extends EntityPersister, EntityMutationTarget {
/**
* Get a SQL select string that performs a select based on a unique
* key determined by the given property name.
*
* @param propertyName The name of the property which maps to the
* column(s) to use in the select statement restriction.
* @return The SQL select string
*/
String getSelectByUniqueKeyString(String propertyName);
/**
* Get a SQL select string that performs a select based on a unique
* key determined by the given property names.
*
* @param propertyNames The names of the properties which maps to the
* column(s) to use in the select statement restriction.
* @return The SQL select string
*/
default String getSelectByUniqueKeyString(String[] propertyNames) {
// default impl only for backward compatibility
if ( propertyNames.length > 1 ) {
throw new IllegalArgumentException( "support for multiple properties not implemented" );
}
return getSelectByUniqueKeyString( propertyNames[0] );
}
/**
* Get the database-specific SQL command to retrieve the last
* generated IDENTITY value.
*
* @return The SQL command string
*/
@Deprecated( forRemoval = true, since = "6.5" )
public interface PostInsertIdentityPersister extends EntityPersister {
@Override
String getIdentitySelectString();
@Override
String[] getIdentifierColumnNames();
/**
* The names of the primary key columns in the root table.
*
* @return The primary key column names.
*/
@Override
String getSelectByUniqueKeyString(String propertyName);
@Override
default String getSelectByUniqueKeyString(String[] propertyNames) {
return EntityPersister.super.getSelectByUniqueKeyString( propertyNames );
}
@Override
String[] getRootTableKeyColumnNames();
}

View File

@ -13,43 +13,58 @@ import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.AbstractGeneratedValuesMutationDelegate;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
/**
* Abstract {@link InsertGeneratedIdentifierDelegate} implementation where
* Abstract {@link org.hibernate.generator.values.GeneratedValuesMutationDelegate} implementation where
* the underlying strategy causes the generated identifier to be returned as
* an effect of performing the insert statement. Thus, there is no need for
* an additional sql statement to determine the generated identifier.
*
* @author Steve Ebersole
*/
public abstract class AbstractReturningDelegate implements InsertGeneratedIdentifierDelegate {
private final PostInsertIdentityPersister persister;
public abstract class AbstractReturningDelegate extends AbstractGeneratedValuesMutationDelegate
implements InsertGeneratedIdentifierDelegate {
/**
* @deprecated Use {@link #AbstractReturningDelegate(EntityPersister, EventType, boolean, boolean)} instead.
*/
@Deprecated( forRemoval = true, since = "6.5" )
public AbstractReturningDelegate(PostInsertIdentityPersister persister) {
this.persister = persister;
super( persister, EventType.INSERT );
}
public AbstractReturningDelegate(
EntityPersister persister,
EventType timing,
boolean supportsArbitraryValues,
boolean supportsRowId) {
super( persister, timing, supportsArbitraryValues, supportsRowId );
}
@Override
public Object performInsert(
PreparedStatementDetails insertStatementDetails,
public GeneratedValues performMutation(
PreparedStatementDetails statementDetails,
JdbcValueBindings valueBindings,
Object entity,
SharedSessionContractImplementor session) {
session.getJdbcServices().getSqlStatementLogger().logStatement( insertStatementDetails.getSqlString() );
valueBindings.beforeStatement( insertStatementDetails );
return executeAndExtract( insertStatementDetails.getSqlString(), insertStatementDetails.getStatement(), session );
session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() );
valueBindings.beforeStatement( statementDetails );
return executeAndExtractReturning( statementDetails.getSqlString(), statementDetails.getStatement(), session );
}
@Override
public final Object performInsert(String insertSql, SharedSessionContractImplementor session, Binder binder) {
public final GeneratedValues performInsertReturning(String sql, SharedSessionContractImplementor session, Binder binder) {
try {
// prepare and execute the insert
PreparedStatement insert = prepareStatement( insertSql, session );
PreparedStatement insert = prepareStatement( sql, session );
try {
binder.bindValues( insert );
return executeAndExtract( insertSql, insert, session );
return executeAndExtractReturning( sql, insert, session );
}
finally {
releaseStatement( insert, session );
@ -59,23 +74,31 @@ public abstract class AbstractReturningDelegate implements InsertGeneratedIdenti
throw session.getJdbcServices().getSqlExceptionHelper().convert(
sqle,
"could not insert: " + MessageHelper.infoString( persister ),
insertSql
sql
);
}
}
protected PostInsertIdentityPersister getPersister() {
return persister;
/**
* @deprecated
*/
@Deprecated( forRemoval = true, since = "6.5" )
protected Object executeAndExtract(
String sql,
PreparedStatement preparedStatement,
SharedSessionContractImplementor session) {
final GeneratedValues generatedValues = executeAndExtractReturning( sql, preparedStatement, session );
return generatedValues.getGeneratedValue( persister.getIdentifierMapping() );
}
protected abstract Object executeAndExtract(
String insertSql,
PreparedStatement insertStatement,
protected abstract GeneratedValues executeAndExtractReturning(
String sql,
PreparedStatement preparedStatement,
SharedSessionContractImplementor session);
protected void releaseStatement(PreparedStatement insert, SharedSessionContractImplementor session) {
protected void releaseStatement(PreparedStatement preparedStatement, SharedSessionContractImplementor session) {
final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( insert );
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( preparedStatement );
jdbcCoordinator.afterStatementExecution();
}
}

View File

@ -16,24 +16,39 @@ import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.StatementPreparer;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.AbstractGeneratedValuesMutationDelegate;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import static java.sql.Statement.NO_GENERATED_KEYS;
import static org.hibernate.id.IdentifierGeneratorHelper.getGeneratedIdentity;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getGeneratedValues;
/**
* Abstract {@link InsertGeneratedIdentifierDelegate} implementation where
* Abstract {@link org.hibernate.generator.values.GeneratedValuesMutationDelegate} implementation where
* the underlying strategy requires a subsequent {@code select} after the
* {@code insert} to determine the generated identifier.
*
* @author Steve Ebersole
*/
public abstract class AbstractSelectingDelegate implements InsertGeneratedIdentifierDelegate {
private final PostInsertIdentityPersister persister;
public abstract class AbstractSelectingDelegate extends AbstractGeneratedValuesMutationDelegate
implements InsertGeneratedIdentifierDelegate {
/**
* @deprecated Use {@link #AbstractSelectingDelegate(EntityPersister, EventType, boolean, boolean)} instead.
*/
@Deprecated( forRemoval = true, since = "6.5" )
protected AbstractSelectingDelegate(PostInsertIdentityPersister persister) {
this.persister = persister;
super( persister, EventType.INSERT );
}
protected AbstractSelectingDelegate(
EntityPersister persister,
EventType timing,
boolean supportsArbitraryValues,
boolean supportsRowId) {
super( persister, timing, supportsArbitraryValues, supportsRowId );
}
/**
@ -47,12 +62,22 @@ public abstract class AbstractSelectingDelegate implements InsertGeneratedIdenti
throws SQLException {
}
/**
* @deprecated No substitute.
*/
@Deprecated( forRemoval = true, since = "6.5" )
protected Object extractGeneratedValues(ResultSet resultSet, SharedSessionContractImplementor session)
throws SQLException {
final GeneratedValues generatedValues = extractReturningValues( resultSet, session );
return generatedValues.getGeneratedValue( persister.getIdentifierMapping() );
}
/**
* Extract the generated key value from the given result set after execution of {@link #getSelectSQL()}.
*/
protected Object extractGeneratedValue(ResultSet resultSet, SharedSessionContractImplementor session)
private GeneratedValues extractReturningValues(ResultSet resultSet, SharedSessionContractImplementor session)
throws SQLException {
return getGeneratedIdentity( persister.getNavigableRole().getFullPath(), resultSet, persister, session );
return getGeneratedValues( resultSet, persister, getTiming(), session );
}
@Override
@ -61,19 +86,19 @@ public abstract class AbstractSelectingDelegate implements InsertGeneratedIdenti
}
@Override
public Object performInsert(
PreparedStatementDetails insertStatementDetails,
public GeneratedValues performMutation(
PreparedStatementDetails statementDetails,
JdbcValueBindings jdbcValueBindings,
Object entity,
SharedSessionContractImplementor session) {
final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
final JdbcServices jdbcServices = session.getJdbcServices();
jdbcServices.getSqlStatementLogger().logStatement( insertStatementDetails.getSqlString() );
jdbcValueBindings.beforeStatement( insertStatementDetails );
jdbcServices.getSqlStatementLogger().logStatement( statementDetails.getSqlString() );
jdbcValueBindings.beforeStatement( statementDetails );
jdbcCoordinator.getResultSetReturn()
.executeUpdate( insertStatementDetails.resolveStatement(), insertStatementDetails.getSqlString() );
.executeUpdate( statementDetails.resolveStatement(), statementDetails.getSqlString() );
// the insert is complete, select the generated id...
@ -84,7 +109,7 @@ public abstract class AbstractSelectingDelegate implements InsertGeneratedIdenti
final ResultSet resultSet = session.getJdbcCoordinator().getResultSetReturn().extract( idSelect, idSelectSql );
try {
return extractGeneratedValue( resultSet, session );
return extractReturningValues( resultSet, session );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert(
@ -108,15 +133,15 @@ public abstract class AbstractSelectingDelegate implements InsertGeneratedIdenti
}
@Override
public final Object performInsert(String insertSQL, SharedSessionContractImplementor session, Binder binder) {
public final GeneratedValues performInsertReturning(String sql, SharedSessionContractImplementor session, Binder binder) {
JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
StatementPreparer statementPreparer = jdbcCoordinator.getStatementPreparer();
try {
// prepare and execute the insert
PreparedStatement insert = statementPreparer.prepareStatement( insertSQL, NO_GENERATED_KEYS );
PreparedStatement insert = statementPreparer.prepareStatement( sql, NO_GENERATED_KEYS );
try {
binder.bindValues( insert );
jdbcCoordinator.getResultSetReturn().executeUpdate( insert, insertSQL );
jdbcCoordinator.getResultSetReturn().executeUpdate( insert, sql );
}
finally {
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( insert );
@ -127,7 +152,7 @@ public abstract class AbstractSelectingDelegate implements InsertGeneratedIdenti
throw session.getJdbcServices().getSqlExceptionHelper().convert(
sqle,
"could not insert: " + MessageHelper.infoString( persister ),
insertSQL
sql
);
}
@ -140,7 +165,7 @@ public abstract class AbstractSelectingDelegate implements InsertGeneratedIdenti
bindParameters( binder.getEntity(), idSelect, session );
ResultSet resultSet = jdbcCoordinator.getResultSetReturn().extract( idSelect, selectSQL );
try {
return extractGeneratedValue( resultSet, session );
return extractReturningValues( resultSet, session );
}
finally {
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( resultSet, idSelect );

View File

@ -6,66 +6,46 @@
*/
package org.hibernate.id.insert;
import org.hibernate.MappingException;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.internal.CoreLogging;
import org.hibernate.jdbc.Expectation;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.model.ast.builder.TableInsertBuilderStandard;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.sql.model.ast.builder.TableMutationBuilder;
/**
* Delegate for dealing with {@code IDENTITY} columns where the dialect requires an
* additional command execution to retrieve the generated {@code IDENTITY} value
*/
public class BasicSelectingDelegate extends AbstractSelectingDelegate {
private final PostInsertIdentityPersister persister;
private final Dialect dialect;
final private EntityPersister persister;
/**
* @deprecated Use {@link #BasicSelectingDelegate(EntityPersister)} instead.
*/
@Deprecated( forRemoval = true, since = "6.5" )
public BasicSelectingDelegate(PostInsertIdentityPersister persister, Dialect dialect) {
super( persister );
this.persister = persister;
this.dialect = dialect;
this( persister );
}
@Override @Deprecated
public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) {
IdentifierGeneratingInsert insert = new IdentifierGeneratingInsert( persister.getFactory() );
insert.addGeneratedColumns( persister.getRootTableKeyColumnNames(), (OnExecutionGenerator) persister.getGenerator() );
return insert;
public BasicSelectingDelegate(EntityPersister persister) {
super( persister, EventType.INSERT, false, false );
this.persister = persister;
}
@Override
public TableInsertBuilder createTableInsertBuilder(
BasicEntityIdentifierMapping identifierMapping,
public TableMutationBuilder<?> createTableMutationBuilder(
Expectation expectation,
SessionFactoryImplementor factory) {
final TableInsertBuilder builder =
new TableInsertBuilderStandard( persister, persister.getIdentifierTableMapping(), factory );
final OnExecutionGenerator generator = (OnExecutionGenerator) persister.getGenerator();
if ( generator.referenceColumnsInSql( dialect ) ) {
final String[] columnNames = persister.getRootTableKeyColumnNames();
final String[] columnValues = generator.getReferencedColumnValues( dialect );
if ( columnValues.length != columnNames.length ) {
throw new MappingException("wrong number of generated columns");
}
for ( int i = 0; i < columnValues.length; i++ ) {
builder.addKeyColumn( columnNames[i], columnValues[i], identifierMapping.getJdbcMapping() );
}
}
return builder;
return new TableInsertBuilderStandard( persister, persister.getIdentifierTableMapping(), factory );
}
@Override
protected String getSelectSQL() {
if ( persister.getIdentitySelectString() == null && !dialect.getIdentityColumnSupport().supportsInsertSelectIdentity() ) {
if ( persister.getIdentitySelectString() == null && !dialect().getIdentityColumnSupport().supportsInsertSelectIdentity() ) {
throw CoreLogging.messageLogger( BasicSelectingDelegate.class ).nullIdentitySelectString();
}
return persister.getIdentitySelectString();

View File

@ -9,10 +9,10 @@ package org.hibernate.id.insert;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.hibernate.MappingException;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
@ -21,95 +21,104 @@ import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.MutationStatementPreparer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.GeneratedValueBasicResultBuilder;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.jdbc.Expectation;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.model.ast.builder.TableInsertBuilderStandard;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.sql.model.ast.builder.TableMutationBuilder;
import org.hibernate.sql.model.ast.builder.TableUpdateBuilderStandard;
import static java.sql.Statement.RETURN_GENERATED_KEYS;
import static org.hibernate.id.IdentifierGeneratorHelper.getGeneratedIdentity;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getActualGeneratedModelPart;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getGeneratedValues;
import static org.hibernate.internal.util.StringHelper.unquote;
/**
* Delegate for dealing with {@code IDENTITY} columns using the JDBC3 method
* Delegate for dealing with generated values using the JDBC3 method
* {@link PreparedStatement#getGeneratedKeys()}.
* <p>
* Supports both {@link EventType#INSERT insert} and {@link EventType#UPDATE update} statements.
*
* @author Andrea Boriero
*/
public class GetGeneratedKeysDelegate extends AbstractReturningDelegate {
private final PostInsertIdentityPersister persister;
private final Dialect dialect;
private final boolean inferredKeys;
private final String[] columnNames;
/**
* @deprecated Use {@link #GetGeneratedKeysDelegate(EntityPersister, boolean, EventType)} instead.
*/
@Deprecated( forRemoval = true, since = "6.5" )
public GetGeneratedKeysDelegate(PostInsertIdentityPersister persister, Dialect dialect, boolean inferredKeys) {
super( persister );
this.persister = persister;
this.dialect = dialect;
this.inferredKeys = inferredKeys;
this( persister, inferredKeys, EventType.INSERT );
}
@Override @Deprecated
public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) {
IdentifierGeneratingInsert insert = new IdentifierGeneratingInsert( persister.getFactory() );
insert.addGeneratedColumns( persister.getRootTableKeyColumnNames(), (OnExecutionGenerator) persister.getGenerator() );
return insert;
public GetGeneratedKeysDelegate(
EntityPersister persister,
boolean inferredKeys,
EventType timing) {
super( persister, timing, !inferredKeys, false );
if ( inferredKeys ) {
columnNames = null;
}
else {
final List<GeneratedValueBasicResultBuilder> resultBuilders = jdbcValuesMappingProducer.getResultBuilders();
final List<String> columnNamesList = new ArrayList<>( resultBuilders.size() );
final boolean unquote = dialect().unquoteGetGeneratedKeys();
for ( GeneratedValueBasicResultBuilder resultBuilder : resultBuilders ) {
final String col = getActualGeneratedModelPart( resultBuilder.getModelPart() ).getSelectionExpression();
columnNamesList.add( unquote ? unquote( col, dialect() ) : col );
}
columnNames = columnNamesList.toArray( new String[0] );
}
}
@Override
public TableInsertBuilder createTableInsertBuilder(
BasicEntityIdentifierMapping identifierMapping,
public TableMutationBuilder<?> createTableMutationBuilder(
Expectation expectation,
SessionFactoryImplementor factory) {
final TableInsertBuilder builder =
new TableInsertBuilderStandard( persister, persister.getIdentifierTableMapping(), factory );
final OnExecutionGenerator generator = (OnExecutionGenerator) persister.getGenerator();
if ( generator.referenceColumnsInSql( dialect ) ) {
final String[] columnNames = persister.getRootTableKeyColumnNames();
final String[] columnValues = generator.getReferencedColumnValues( dialect );
if ( columnValues.length != columnNames.length ) {
throw new MappingException("wrong number of generated columns");
}
for ( int i = 0; i < columnValues.length; i++ ) {
builder.addKeyColumn( columnNames[i], columnValues[i], identifierMapping.getJdbcMapping() );
}
if ( getTiming() == EventType.INSERT ) {
return new TableInsertBuilderStandard( persister, persister.getIdentifierTableMapping(), factory );
}
else {
return new TableUpdateBuilderStandard<>( persister, persister.getIdentifierTableMapping(), factory );
}
return builder;
}
@Override
public PreparedStatement prepareStatement(String insertSql, SharedSessionContractImplementor session) {
public PreparedStatement prepareStatement(String sql, SharedSessionContractImplementor session) {
MutationStatementPreparer preparer = session.getJdbcCoordinator().getMutationStatementPreparer();
return inferredKeys
? preparer.prepareStatement( insertSql, RETURN_GENERATED_KEYS )
: preparer.prepareStatement( insertSql, persister.getRootTableKeyColumnNames() );
return columnNames == null
? preparer.prepareStatement( sql, RETURN_GENERATED_KEYS )
: preparer.prepareStatement( sql, columnNames );
}
@Override
public Object performInsert(
PreparedStatementDetails insertStatementDetails,
public GeneratedValues performMutation(
PreparedStatementDetails statementDetails,
JdbcValueBindings jdbcValueBindings,
Object entity,
SharedSessionContractImplementor session) {
final JdbcServices jdbcServices = session.getJdbcServices();
final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
final String insertSql = insertStatementDetails.getSqlString();
final String sql = statementDetails.getSqlString();
jdbcServices.getSqlStatementLogger().logStatement( insertSql );
jdbcServices.getSqlStatementLogger().logStatement( sql );
final PreparedStatement insertStatement = insertStatementDetails.resolveStatement();
jdbcValueBindings.beforeStatement( insertStatementDetails );
final PreparedStatement preparedStatement = statementDetails.resolveStatement();
jdbcValueBindings.beforeStatement( statementDetails );
try {
jdbcCoordinator.getResultSetReturn().executeUpdate( insertStatement, insertSql );
jdbcCoordinator.getResultSetReturn().executeUpdate( preparedStatement, sql );
try {
final ResultSet resultSet = insertStatement.getGeneratedKeys();
final ResultSet resultSet = preparedStatement.getGeneratedKeys();
try {
return getGeneratedIdentity( persister.getNavigableRole().getFullPath(), resultSet, persister, session );
return getGeneratedValues( resultSet, persister, getTiming(), session );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert(
@ -119,7 +128,7 @@ public class GetGeneratedKeysDelegate extends AbstractReturningDelegate {
"Unable to extract generated key from generated-key for `%s`",
persister.getNavigableRole().getFullPath()
),
insertSql
sql
);
}
finally {
@ -127,48 +136,51 @@ public class GetGeneratedKeysDelegate extends AbstractReturningDelegate {
jdbcCoordinator
.getLogicalConnection()
.getResourceRegistry()
.release( resultSet, insertStatement );
.release( resultSet, preparedStatement );
}
}
}
finally {
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( insertStatement );
if ( statementDetails.getStatement() != null ) {
statementDetails.releaseStatement( session );
}
jdbcValueBindings.afterStatement( statementDetails.getMutatingTableDetails() );
}
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert(
e,
"Unable to extract generated-keys ResultSet",
insertSql
sql
);
}
}
@Override
public Object executeAndExtract(
String insertSql,
PreparedStatement insertStatement,
public GeneratedValues executeAndExtractReturning(
String sql,
PreparedStatement preparedStatement,
SharedSessionContractImplementor session) {
final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
final JdbcServices jdbcServices = session.getJdbcServices();
jdbcCoordinator.getResultSetReturn().executeUpdate( insertStatement, insertSql );
jdbcCoordinator.getResultSetReturn().executeUpdate( preparedStatement, sql );
try {
final ResultSet resultSet = insertStatement.getGeneratedKeys();
final ResultSet resultSet = preparedStatement.getGeneratedKeys();
try {
return getGeneratedIdentity( persister.getNavigableRole().getFullPath(), resultSet, persister, session );
return getGeneratedValues( resultSet, persister, getTiming(), session );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert(
e,
"Unable to extract generated key(s) from generated-keys ResultSet",
insertSql
sql
);
}
finally {
if ( resultSet != null ) {
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( resultSet, insertStatement );
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( resultSet, preparedStatement );
}
}
}
@ -176,7 +188,7 @@ public class GetGeneratedKeysDelegate extends AbstractReturningDelegate {
throw jdbcServices.getSqlExceptionHelper().convert(
e,
"Unable to extract generated-keys ResultSet",
insertSql
sql
);
}
}

View File

@ -8,13 +8,15 @@ package org.hibernate.id.insert;
import java.sql.PreparedStatement;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.jdbc.Expectation;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
/**
@ -37,17 +39,23 @@ import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
* @see org.hibernate.generator.OnExecutionGenerator
*
* @author Steve Ebersole
*
* @deprecated Use {@link GeneratedValuesMutationDelegate} instead.
*/
public interface InsertGeneratedIdentifierDelegate {
@Deprecated( forRemoval = true, since = "6.5" )
public interface InsertGeneratedIdentifierDelegate extends GeneratedValuesMutationDelegate {
/**
* Create a {@link TableInsertBuilder} with any specific identity
* handling already built in.
*/
TableInsertBuilder createTableInsertBuilder(
default TableInsertBuilder createTableInsertBuilder(
BasicEntityIdentifierMapping identifierMapping,
Expectation expectation,
SessionFactoryImplementor sessionFactory);
SessionFactoryImplementor sessionFactory) {
return (TableInsertBuilder) createTableMutationBuilder( expectation, sessionFactory );
}
@Override
PreparedStatement prepareStatement(String insertSql, SharedSessionContractImplementor session);
/**
@ -56,23 +64,20 @@ public interface InsertGeneratedIdentifierDelegate {
*
* @see #createTableInsertBuilder
*/
Object performInsert(
default Object performInsert(
PreparedStatementDetails insertStatementDetails,
JdbcValueBindings valueBindings,
Object entity,
SharedSessionContractImplementor session);
/**
* Build an {@linkplain org.hibernate.sql.Insert insert statement}
* specific to the delegate's mode of handling generated key values.
*
* @param context A context to help generate SQL strings
* @return An {@link IdentifierGeneratingInsert}
*
* @deprecated this is no longer called
*/
@Deprecated(since = "6.2")
IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context);
SharedSessionContractImplementor session) {
final EntityPersister entityPersister = session.getEntityPersister( null, entity );
final GeneratedValues generatedValues = performMutation(
insertStatementDetails,
valueBindings,
entity,
session
);
return generatedValues.getGeneratedValue( entityPersister.getIdentifierMapping() );
}
/**
* Append SQL specific to this delegate's mode of handling generated
@ -84,6 +89,22 @@ public interface InsertGeneratedIdentifierDelegate {
return insertSQL;
}
/**
* Execute the given {@code insert} statement and return the generated
* key value.
*
* @param insertSQL The {@code insert} statement string
* @param session The session in which we are operating
* @param binder The parameter binder
*
* @return The generated identifier value
*/
default Object performInsert(String insertSQL, SharedSessionContractImplementor session, Binder binder) {
final EntityPersister entityPersister = session.getEntityPersister( null, binder.getEntity() );
final GeneratedValues generatedValues = performInsertReturning( insertSQL, session, binder );
return generatedValues.getGeneratedValue( entityPersister.getIdentifierMapping() );
}
/**
* Execute the given {@code insert} statement and return the generated
* key value.
@ -94,6 +115,5 @@ public interface InsertGeneratedIdentifierDelegate {
*
* @return The generated identifier value
*/
Object performInsert(String insertSQL, SharedSessionContractImplementor session, Binder binder);
GeneratedValues performInsertReturning(String insertSQL, SharedSessionContractImplementor session, Binder binder);
}

View File

@ -9,86 +9,116 @@ package org.hibernate.id.insert;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.GeneratedValueBasicResultBuilder;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.generator.values.internal.TableUpdateReturningBuilder;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.jdbc.Expectation;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.model.ast.MutatingTableReference;
import org.hibernate.sql.model.ast.builder.TableMutationBuilder;
import static java.sql.Statement.NO_GENERATED_KEYS;
import static org.hibernate.id.IdentifierGeneratorHelper.getGeneratedIdentity;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getActualGeneratedModelPart;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getGeneratedValues;
/**
* Delegate for dealing with {@code IDENTITY} columns where the dialect supports
* returning the generated {@code IDENTITY} value directly from the insert statement.
* Delegate for dealing with generated values where the dialect supports
* returning the generated column directly from the mutation statement.
* <p>
* Supports both {@link EventType#INSERT insert} and {@link EventType#UPDATE update} statements.
*
* @see org.hibernate.id.IdentityGenerator
* @see IdentityColumnSupport#supportsInsertSelectIdentity()
* @see org.hibernate.generator.OnExecutionGenerator
* @see GeneratedValuesMutationDelegate
*/
public class InsertReturningDelegate extends AbstractReturningDelegate {
private final PostInsertIdentityPersister persister;
private final Dialect dialect;
private final MutatingTableReference tableReference;
private final List<ColumnReference> generatedColumns;
/**
* @deprecated Use {@link #InsertReturningDelegate(EntityPersister, EventType)} instead.
*/
@Deprecated( forRemoval = true, since = "6.5" )
public InsertReturningDelegate(PostInsertIdentityPersister persister, Dialect dialect) {
super( persister );
this.persister = persister;
this.dialect = dialect;
this( persister, EventType.INSERT );
}
@Override @Deprecated
public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) {
InsertSelectIdentityInsert insert = new InsertSelectIdentityInsert( persister.getFactory() );
insert.addGeneratedColumns( persister.getRootTableKeyColumnNames(), (OnExecutionGenerator) persister.getGenerator() );
return insert;
public InsertReturningDelegate(EntityPersister persister, EventType timing) {
super(
persister,
timing,
true,
persister.getFactory().getJdbcServices().getDialect().supportsInsertReturningRowId()
);
this.tableReference = new MutatingTableReference( persister.getIdentifierTableMapping() );
final List<GeneratedValueBasicResultBuilder> resultBuilders = jdbcValuesMappingProducer.getResultBuilders();
this.generatedColumns = new ArrayList<>( resultBuilders.size() );
for ( GeneratedValueBasicResultBuilder resultBuilder : resultBuilders ) {
generatedColumns.add( new ColumnReference(
tableReference,
getActualGeneratedModelPart( resultBuilder.getModelPart() )
) );
}
}
@Override
public TableInsertBuilder createTableInsertBuilder(
BasicEntityIdentifierMapping identifierMapping,
public TableMutationBuilder<?> createTableMutationBuilder(
Expectation expectation,
SessionFactoryImplementor sessionFactory) {
return new TableInsertReturningBuilder( persister, sessionFactory );
if ( getTiming() == EventType.INSERT ) {
return new TableInsertReturningBuilder( persister, tableReference, generatedColumns, sessionFactory );
}
else {
return new TableUpdateReturningBuilder<>( persister, tableReference, generatedColumns, sessionFactory );
}
}
@Override
protected Object executeAndExtract(
String insertSql,
PreparedStatement insertStatement,
protected GeneratedValues executeAndExtractReturning(
String sql,
PreparedStatement preparedStatement,
SharedSessionContractImplementor session) {
final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
final JdbcServices jdbcServices = session.getJdbcServices();
final ResultSet resultSet = jdbcCoordinator.getResultSetReturn().execute( insertStatement, insertSql );
final ResultSet resultSet = jdbcCoordinator.getResultSetReturn().execute( preparedStatement, sql );
try {
return getGeneratedIdentity( persister.getNavigableRole().getFullPath(), resultSet, persister, session );
return getGeneratedValues( resultSet, persister, getTiming(), session );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert(
e,
"Unable to extract generated key(s) from generated-keys ResultSet",
insertSql
sql
);
}
finally {
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( resultSet, insertStatement );
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( resultSet, preparedStatement );
}
}
@Override
public String prepareIdentifierGeneratingInsert(String insertSQL) {
return dialect.getIdentityColumnSupport().appendIdentitySelectToInsert( insertSQL );
return dialect().getIdentityColumnSupport().appendIdentitySelectToInsert(
( (BasicEntityIdentifierMapping) persister.getRootEntityDescriptor().getIdentifierMapping() ).getSelectionExpression(),
insertSQL
);
}
@Override
public PreparedStatement prepareStatement(String insertSql, SharedSessionContractImplementor session) {
return session.getJdbcCoordinator().getMutationStatementPreparer().prepareStatement( insertSql, NO_GENERATED_KEYS );
public PreparedStatement prepareStatement(String sql, SharedSessionContractImplementor session) {
return session.getJdbcCoordinator().getMutationStatementPreparer().prepareStatement( sql, NO_GENERATED_KEYS );
}
}

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.id.insert;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.Insert;
import org.hibernate.generator.OnExecutionGenerator;
@ -17,7 +16,11 @@ import org.hibernate.generator.OnExecutionGenerator;
* to the end of the insert statement.
*
* @author Steve Ebersole
*
* @deprecated This is not used anymore in any of the
* {@link org.hibernate.generator.values.GeneratedValuesMutationDelegate} implementations.
*/
@Deprecated( since = "6.5" )
public class InsertSelectIdentityInsert extends IdentifierGeneratingInsert {
protected String identityColumnName;

View File

@ -10,27 +10,17 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.hibernate.MappingException;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.mutation.JdbcValueBindings;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.StatementPreparer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.internal.CoreLogging;
import org.hibernate.jdbc.Expectation;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
import org.hibernate.sql.model.ast.builder.TableInsertBuilderStandard;
import org.hibernate.persister.entity.EntityPersister;
import static java.sql.Statement.NO_GENERATED_KEYS;
import static org.hibernate.id.IdentifierGeneratorHelper.getGeneratedIdentity;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getGeneratedValues;
/**
* Specialized {@link IdentifierGeneratingInsert} which appends the database
@ -40,41 +30,47 @@ import static org.hibernate.id.IdentifierGeneratorHelper.getGeneratedIdentity;
* @author Christian Beikov
*/
public class SybaseJConnGetGeneratedKeysDelegate extends GetGeneratedKeysDelegate {
private final PostInsertIdentityPersister persister;
private final Dialect dialect;
/**
* @deprecated Use {@link #SybaseJConnGetGeneratedKeysDelegate(EntityPersister)} instead.
*/
@Deprecated( forRemoval = true, since = "6.5" )
public SybaseJConnGetGeneratedKeysDelegate(PostInsertIdentityPersister persister, Dialect dialect) {
super( persister, dialect, true );
this.persister = persister;
this.dialect = dialect;
this( persister );
}
public SybaseJConnGetGeneratedKeysDelegate(EntityPersister persister) {
super( persister, true, EventType.INSERT );
}
@Override
public String prepareIdentifierGeneratingInsert(String insertSQL) {
return dialect.getIdentityColumnSupport().appendIdentitySelectToInsert( insertSQL );
return dialect().getIdentityColumnSupport().appendIdentitySelectToInsert(
( (BasicEntityIdentifierMapping) persister.getRootEntityDescriptor().getIdentifierMapping() ).getSelectionExpression(),
insertSQL
);
}
@Override
public Object executeAndExtract(
String insertSql,
PreparedStatement insertStatement,
public GeneratedValues executeAndExtractReturning(
String sql,
PreparedStatement preparedStatement,
SharedSessionContractImplementor session) {
JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
final JdbcServices jdbcServices = session.getJdbcServices();
ResultSet resultSet = jdbcCoordinator.getResultSetReturn().execute( insertStatement, insertSql );
ResultSet resultSet = jdbcCoordinator.getResultSetReturn().execute( preparedStatement, sql );
try {
return getGeneratedIdentity( persister.getNavigableRole().getFullPath(), resultSet, persister, session );
return getGeneratedValues( resultSet, persister, getTiming(), session );
}
catch (SQLException e) {
throw jdbcServices.getSqlExceptionHelper().convert(
e,
"Unable to extract generated-keys ResultSet",
insertSql
sql
);
}
finally {
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( resultSet, insertStatement );
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( resultSet, preparedStatement );
jdbcCoordinator.afterStatementExecution();
}
}

View File

@ -6,40 +6,77 @@
*/
package org.hibernate.id.insert;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.model.ast.MutatingTableReference;
import org.hibernate.sql.model.ast.TableInsert;
import org.hibernate.sql.model.ast.builder.AbstractTableInsertBuilder;
import org.hibernate.sql.model.internal.TableInsertStandard;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getActualGeneratedModelPart;
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
/**
* @author Steve Ebersole
*/
public class TableInsertReturningBuilder extends AbstractTableInsertBuilder {
private final List<ColumnReference> generatedColumns;
/**
* @deprecated Use {@link #TableInsertReturningBuilder(EntityPersister, MutatingTableReference, List, SessionFactoryImplementor)} instead.
*/
@Deprecated( forRemoval = true, since = "6.5" )
public TableInsertReturningBuilder(
PostInsertIdentityPersister mutationTarget,
SessionFactoryImplementor sessionFactory) {
super( mutationTarget, mutationTarget.getIdentifierTableMapping(), sessionFactory );
this(
mutationTarget,
new MutatingTableReference( mutationTarget.getIdentifierTableMapping() ),
new ArrayList<>(),
sessionFactory
);
final List<? extends ModelPart> insertGeneratedProperties = getMutationTarget().getInsertGeneratedProperties();
for ( final ModelPart prop : insertGeneratedProperties ) {
generatedColumns.add( new ColumnReference(
getMutatingTable(),
getActualGeneratedModelPart( castNonNull( prop.asBasicValuedModelPart() ) )
) );
}
// special case for rowid when the dialect supports it
final EntityRowIdMapping rowIdMapping = getMutationTarget().getRowIdMapping();
if ( rowIdMapping != null && getJdbcServices().getDialect().supportsInsertReturningRowId() ) {
generatedColumns.add( new ColumnReference( getMutatingTable(), rowIdMapping ) );
}
}
public TableInsertReturningBuilder(
EntityPersister mutationTarget,
MutatingTableReference tableReference,
List<ColumnReference> generatedColumns,
SessionFactoryImplementor sessionFactory) {
super( mutationTarget, tableReference, sessionFactory );
this.generatedColumns = generatedColumns;
}
@Override
protected PostInsertIdentityPersister getMutationTarget() {
return (PostInsertIdentityPersister) super.getMutationTarget();
protected EntityPersister getMutationTarget() {
return (EntityPersister) super.getMutationTarget();
}
@Override
public TableInsert buildMutation() {
final BasicEntityIdentifierMapping identifierMapping =
(BasicEntityIdentifierMapping) getMutationTarget().getIdentifierMapping();
return new TableInsertStandard(
getMutatingTable(),
getMutationTarget(),
combine( getValueBindingList(), getKeyBindingList(), getLobValueBindingList() ),
Collections.singletonList( new ColumnReference( getMutatingTable(), identifierMapping ) ),
generatedColumns,
getParameters()
);
}

View File

@ -6,19 +6,25 @@
*/
package org.hibernate.id.insert;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.GeneratedValueBasicResultBuilder;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.jdbc.Expectation;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.model.ast.builder.TableInsertBuilderStandard;
import org.hibernate.sql.model.ast.builder.TableMutationBuilder;
import org.hibernate.type.Type;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getActualGeneratedModelPart;
/**
* Uses a unique key of the inserted entity to locate the newly inserted row.
@ -26,40 +32,57 @@ import java.sql.SQLException;
* @author Gavin King
*/
public class UniqueKeySelectingDelegate extends AbstractSelectingDelegate {
private final PostInsertIdentityPersister persister;
private final Dialect dialect;
private final String[] uniqueKeyPropertyNames;
private final Type[] uniqueKeyTypes;
private final String idSelectString;
private final String selectString;
/**
* @deprecated Use {@link #UniqueKeySelectingDelegate(EntityPersister, String[], EventType)} instead.
*/
@Deprecated( forRemoval = true, since = "6.5" )
public UniqueKeySelectingDelegate(PostInsertIdentityPersister persister, Dialect dialect, String[] uniqueKeyPropertyNames) {
super( persister );
this( persister, uniqueKeyPropertyNames, EventType.INSERT );
}
public UniqueKeySelectingDelegate(
EntityPersister persister,
String[] uniqueKeyPropertyNames,
EventType timing) {
super( persister, timing, true, true );
this.persister = persister;
this.dialect = dialect;
this.uniqueKeyPropertyNames = uniqueKeyPropertyNames;
idSelectString = persister.getSelectByUniqueKeyString( uniqueKeyPropertyNames );
uniqueKeyTypes = new Type[ uniqueKeyPropertyNames.length ];
for (int i = 0; i < uniqueKeyPropertyNames.length; i++ ) {
for ( int i = 0; i < uniqueKeyPropertyNames.length; i++ ) {
uniqueKeyTypes[i] = persister.getPropertyType( uniqueKeyPropertyNames[i] );
}
final EntityRowIdMapping rowIdMapping = persister.getRowIdMapping();
if ( !persister.isIdentifierAssignedByInsert()
|| persister.getInsertGeneratedProperties().size() > 1
|| rowIdMapping != null ) {
final List<GeneratedValueBasicResultBuilder> resultBuilders = jdbcValuesMappingProducer.getResultBuilders();
final List<String> columnNames = new ArrayList<>( resultBuilders.size() );
for ( GeneratedValueBasicResultBuilder resultBuilder : resultBuilders ) {
columnNames.add( getActualGeneratedModelPart( resultBuilder.getModelPart() ).getSelectionExpression() );
}
selectString = persister.getSelectByUniqueKeyString(
uniqueKeyPropertyNames,
columnNames.toArray( new String[0] )
);
}
else {
selectString = persister.getSelectByUniqueKeyString( uniqueKeyPropertyNames );
}
}
protected String getSelectSQL() {
return idSelectString;
}
@Override @Deprecated
public IdentifierGeneratingInsert prepareIdentifierGeneratingInsert(SqlStringGenerationContext context) {
return new IdentifierGeneratingInsert( persister.getFactory() );
return selectString;
}
@Override
public TableInsertBuilder createTableInsertBuilder(
BasicEntityIdentifierMapping identifierMapping,
public TableMutationBuilder<?> createTableMutationBuilder(
Expectation expectation,
SessionFactoryImplementor factory) {
return new TableInsertBuilderStandard( persister, persister.getIdentifierTableMapping(), factory );

View File

@ -8,7 +8,6 @@ package org.hibernate.internal;
import java.util.Set;
import jakarta.persistence.EntityGraph;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
@ -31,16 +30,18 @@ import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper;
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.Generator;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.loader.ast.spi.CascadingFetchProfile;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.generator.Generator;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.tuple.entity.EntityMetamodel;
import jakarta.persistence.EntityGraph;
import jakarta.transaction.SystemException;
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
@ -107,10 +108,11 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
persister.setValues( entity, state );
}
}
persister.insert( id, state, entity, this );
persister.insertReturning( id, state, entity, this );
}
else {
id = persister.insert( state, entity, this );
final GeneratedValues generatedValues = persister.insertReturning( state, entity, this );
id = generatedValues.getGeneratedValue( persister.getIdentifierMapping() );
}
persister.setIdentifier( entity, id, this );
return id;
@ -165,7 +167,7 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implemen
else {
oldVersion = null;
}
persister.update( id, state, null, false, null, oldVersion, entity, null, this );
persister.updateReturning( id, state, null, false, null, oldVersion, entity, null, this );
}
@Override

View File

@ -59,4 +59,9 @@ public interface BasicValuedModelPart extends BasicValuedMapping, ValuedModelPar
default boolean hasPartitionedSelectionMapping() {
return isPartitioned();
}
@Override
default BasicValuedModelPart asBasicValuedModelPart() {
return this;
}
}

View File

@ -11,6 +11,6 @@ package org.hibernate.metamodel.mapping;
*
* @see org.hibernate.annotations.RowId
*/
public interface EntityRowIdMapping extends VirtualModelPart, SelectableMapping {
public interface EntityRowIdMapping extends BasicValuedModelPart, VirtualModelPart, SelectableMapping {
String getRowIdName();
}

View File

@ -148,6 +148,10 @@ public interface ModelPart extends MappingModelExpressible {
return null;
}
@Nullable default BasicValuedModelPart asBasicValuedModelPart() {
return null;
}
/**
* A short hand form of {@link #breakDownJdbcValues(Object, int, Object, Object, JdbcValueBiConsumer, SharedSessionContractImplementor)},
* that passes 0 as offset and null for the two values {@code X} and {@code Y}.

View File

@ -9,6 +9,7 @@ package org.hibernate.metamodel.mapping.internal;
import java.util.function.BiConsumer;
import org.hibernate.cache.MutableCacheKeyBuilder;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.IndexedConsumer;
@ -25,6 +26,9 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchOptions;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JavaType;
@ -252,4 +256,35 @@ public class EntityRowIdMappingImpl implements EntityRowIdMapping {
public JdbcMapping getJdbcMapping() {
return rowIdType.getJdbcMapping();
}
@Override
public MappingType getMappedType() {
return rowIdType;
}
@Override
public String getFetchableName() {
return rowIdName;
}
@Override
public int getFetchableKey() {
throw new UnsupportedOperationException();
}
@Override
public FetchOptions getMappedFetchOptions() {
throw new UnsupportedOperationException();
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
String resultVariable,
DomainResultCreationState creationState) {
throw new UnsupportedOperationException();
}
}

View File

@ -17,10 +17,13 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.EventType;
import org.hibernate.generator.Generator;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.loader.ast.internal.LoaderSelectBuilder;
import org.hibernate.loader.ast.internal.NoCallbackExecutionContext;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
@ -28,19 +31,24 @@ import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcParametersList;
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
import static org.hibernate.sql.results.spi.ListResultsConsumer.UniqueSemantic.FILTER;
/**
* Responsible for retrieving {@linkplain OnExecutionGenerator database-generated}
* attribute values after an {@code insert} or {@code update} statement is executed.
* <p>
* Note that this class has responsibility for regular attributes of the entity. The
* primary key / id attribute is handled separately, being the responsibility of an
* instance of {@link org.hibernate.id.insert.InsertGeneratedIdentifierDelegate}.
* The values might have been retrieved early by an instance of {@link GeneratedValuesMutationDelegate},
* which case the {@link GeneratedValues generatedValues} parameter of {@link #processGeneratedValues}
* will already contain the values we need and this processor handles only the
* {@link #setEntityAttributes setting of entity attributes}.
* <p>
* Note that the primary key / id attribute is always handled by the delegate.
*
* @see OnExecutionGenerator
*
* @author Steve Ebersole
* @author Marco Belladelli
*/
@Incubating
public class GeneratedValuesProcessor {
@ -49,24 +57,23 @@ public class GeneratedValuesProcessor {
private final List<AttributeMapping> generatedValuesToSelect;
private final JdbcParametersList jdbcParameters;
private final EntityMappingType entityDescriptor;
private final SessionFactoryImplementor sessionFactory;
private final EntityPersister entityDescriptor;
public GeneratedValuesProcessor(
EntityMappingType entityDescriptor,
EntityPersister entityDescriptor,
List<AttributeMapping> generatedAttributes,
EventType timing,
SessionFactoryImplementor sessionFactory) {
this.entityDescriptor = entityDescriptor;
this.sessionFactory = sessionFactory;
generatedValuesToSelect = getGeneratedAttributes( entityDescriptor, timing );
if ( generatedValuesToSelect.isEmpty() ) {
generatedValuesToSelect = generatedAttributes;
if ( generatedValuesToSelect.isEmpty() || !needsSubsequentSelect( timing, generatedAttributes ) ) {
selectStatement = null;
jdbcSelect = null;
this.jdbcParameters = JdbcParametersList.empty();
jdbcParameters = null;
}
else {
JdbcParametersList.Builder builder = JdbcParametersList.newBuilder();
final JdbcParametersList.Builder builder = JdbcParametersList.newBuilder();
selectStatement = LoaderSelectBuilder.createSelect(
entityDescriptor,
@ -82,7 +89,30 @@ public class GeneratedValuesProcessor {
jdbcSelect = sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory()
.buildSelectTranslator( sessionFactory, selectStatement )
.translate( JdbcParameterBindings.NO_BINDINGS, QueryOptions.NONE );
this.jdbcParameters = builder.build();
jdbcParameters = builder.build();
}
}
private boolean needsSubsequentSelect(EventType timing, List<AttributeMapping> generatedAttributes) {
if ( timing == EventType.INSERT ) {
return entityDescriptor.getInsertDelegate() == null
|| !entityDescriptor.getInsertDelegate().supportsArbitraryValues()
// Check if we need to select more properties than what is processed by the identity delegate.
// This can happen for on-execution generated values on non-identifier tables
|| generatedAttributes.size() > numberOfGeneratedNonIdentifierProperties( timing );
}
else {
return entityDescriptor.getUpdateDelegate() == null;
}
}
private int numberOfGeneratedNonIdentifierProperties(EventType timing) {
if ( timing == EventType.INSERT) {
return entityDescriptor.getInsertGeneratedProperties().size()
- ( entityDescriptor.isIdentifierAssignedByInsert() ? 1 : 0 );
}
else {
return 0;
}
}
@ -91,7 +121,7 @@ public class GeneratedValuesProcessor {
*
* @return a list of {@link AttributeMapping}s.
*/
private static List<AttributeMapping> getGeneratedAttributes(EntityMappingType entityDescriptor, EventType timing) {
public static List<AttributeMapping> getGeneratedAttributes(EntityMappingType entityDescriptor, EventType timing) {
// todo (6.0): For now, we rely on the entity metamodel as composite attributes report
// GenerationTiming.NEVER even if they have attributes that would need generation
final Generator[] generators = entityDescriptor.getEntityPersister().getEntityMetamodel().getGenerators();
@ -110,11 +140,23 @@ public class GeneratedValuesProcessor {
/**
* Obtain the generated values, and populate the snapshot and the fields of the entity instance.
*/
public void processGeneratedValues(Object entity, Object id, Object[] state, SharedSessionContractImplementor session) {
if ( selectStatement != null && hasActualGeneratedValuesToSelect( session, entity ) ) {
final List<Object[]> results = executeSelect( id, session );
assert results.size() == 1;
setEntityAttributes( entity, state, results.get(0) );
public void processGeneratedValues(
Object entity,
Object id,
Object[] state,
GeneratedValues generatedValues,
SharedSessionContractImplementor session) {
if ( hasActualGeneratedValuesToSelect( session, entity ) ) {
if ( selectStatement != null ) {
final List<Object[]> results = executeSelect( id, session );
assert results.size() == 1;
setEntityAttributes( entity, state, results.get( 0 ) );
}
else {
castNonNull( generatedValues );
final List<Object> results = generatedValues.getGeneratedValues( generatedValuesToSelect );
setEntityAttributes( entity, state, results.toArray( new Object[0] ) );
}
}
}
@ -169,8 +211,4 @@ public class GeneratedValuesProcessor {
public EntityMappingType getEntityDescriptor() {
return entityDescriptor;
}
public SessionFactoryImplementor getSessionFactory() {
return sessionFactory;
}
}

View File

@ -104,6 +104,9 @@ import org.hibernate.generator.EventType;
import org.hibernate.generator.Generator;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.generator.internal.VersionGeneration;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.internal.GeneratedValuesHelper;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.id.Assigned;
import org.hibernate.id.BulkInsertionCapableIdentifierGenerator;
@ -112,7 +115,6 @@ import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.OptimizableGenerator;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.id.enhanced.Optimizer;
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.FilterAliasGenerator;
@ -162,7 +164,6 @@ import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.AttributeMappingsList;
import org.hibernate.metamodel.mapping.AttributeMappingsMap;
import org.hibernate.metamodel.mapping.AttributeMetadata;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart;
import org.hibernate.metamodel.mapping.DiscriminatorConverter;
import org.hibernate.metamodel.mapping.DiscriminatorType;
@ -316,6 +317,7 @@ import static org.hibernate.internal.util.collections.ArrayHelper.toIntArray;
import static org.hibernate.internal.util.collections.ArrayHelper.toObjectArray;
import static org.hibernate.internal.util.collections.ArrayHelper.toStringArray;
import static org.hibernate.internal.util.collections.ArrayHelper.toTypeArray;
import static org.hibernate.internal.util.collections.CollectionHelper.combine;
import static org.hibernate.internal.util.collections.CollectionHelper.isNotEmpty;
import static org.hibernate.internal.util.collections.CollectionHelper.setOfSize;
import static org.hibernate.internal.util.collections.CollectionHelper.toSmallList;
@ -426,10 +428,13 @@ public abstract class AbstractEntityPersister
private final FilterHelper filterHelper;
private volatile Set<String> affectingFetchProfileNames;
protected List<? extends ModelPart> insertGeneratedProperties;
protected List<? extends ModelPart> updateGeneratedProperties;
private GeneratedValuesProcessor insertGeneratedValuesProcessor;
private GeneratedValuesProcessor updateGeneratedValuesProcessor;
private InsertGeneratedIdentifierDelegate identityDelegate;
private GeneratedValuesMutationDelegate insertDelegate;
private GeneratedValuesMutationDelegate updateDelegate;
private String identitySelectString;
private boolean[] tableHasColumns;
@ -2008,8 +2013,10 @@ public abstract class AbstractEntityPersister
return select.addRestriction( rootTableKeyColumnNames ).toStatementString();
}
private GeneratedValuesProcessor createGeneratedValuesProcessor(EventType timing) {
return new GeneratedValuesProcessor( this, timing, getFactory() );
private GeneratedValuesProcessor createGeneratedValuesProcessor(
EventType timing,
List<AttributeMapping> generatedAttributes) {
return new GeneratedValuesProcessor( this, generatedAttributes, timing, getFactory() );
}
@Override
@ -2795,11 +2802,22 @@ public abstract class AbstractEntityPersister
return select.toStatementString();
}
@Override
public String getSelectByUniqueKeyString(String[] propertyNames, String[] columnNames) {
final SimpleSelect select = new SimpleSelect( getFactory() )
.setTableName( getTableName( 0 ) )
.addColumns( columnNames );
for ( final String propertyName : propertyNames ) {
select.addRestriction( getPropertyColumnNames( propertyName ) );
}
return select.toStatementString();
}
/**
* Update an object
*/
@Override
public void update(
public GeneratedValues updateReturning(
final Object id,
final Object[] values,
int[] dirtyAttributeIndexes,
@ -2809,7 +2827,7 @@ public abstract class AbstractEntityPersister
final Object object,
final Object rowId,
final SharedSessionContractImplementor session) throws HibernateException {
updateCoordinator.coordinateUpdate(
return updateCoordinator.coordinateUpdate(
object,
id,
rowId,
@ -2861,18 +2879,23 @@ public abstract class AbstractEntityPersister
}
@Override
public InsertGeneratedIdentifierDelegate getIdentityInsertDelegate() {
return identityDelegate;
public GeneratedValuesMutationDelegate getInsertDelegate() {
return insertDelegate;
}
@Override
public Object insert(Object[] fields, Object object, SharedSessionContractImplementor session) {
public GeneratedValuesMutationDelegate getUpdateDelegate() {
return updateDelegate;
}
@Override
public GeneratedValues insertReturning(Object[] fields, Object object, SharedSessionContractImplementor session) {
return insertCoordinator.coordinateInsert( null, fields, object, session );
}
@Override
public void insert(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) {
insertCoordinator.coordinateInsert( id, fields, object, session );
public GeneratedValues insertReturning(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) {
return insertCoordinator.coordinateInsert( id, fields, object, session );
}
protected EntityTableMapping[] getTableMappings() {
@ -3404,13 +3427,36 @@ public abstract class AbstractEntityPersister
}
private void doLateInit() {
tableMappings = buildTableMappings();
final List<AttributeMapping> insertGeneratedAttributes = hasInsertGeneratedProperties() ?
GeneratedValuesProcessor.getGeneratedAttributes( this, INSERT )
: Collections.emptyList();
final List<AttributeMapping> updateGeneratedAttributes = hasUpdateGeneratedProperties() ?
GeneratedValuesProcessor.getGeneratedAttributes( this, UPDATE )
: Collections.emptyList();
insertGeneratedProperties = initInsertGeneratedProperties( insertGeneratedAttributes );
updateGeneratedProperties = initUpdateGeneratedProperties( updateGeneratedAttributes );
if ( isIdentifierAssignedByInsert() ) {
final OnExecutionGenerator generator = (OnExecutionGenerator) getGenerator();
identityDelegate = generator.getGeneratedIdentifierDelegate( this );
insertDelegate = generator.getGeneratedIdentifierDelegate( this );
identitySelectString = getIdentitySelectString( factory.getJdbcServices().getDialect() );
}
else {
insertDelegate = GeneratedValuesHelper.getGeneratedValuesDelegate( this, INSERT );
}
updateDelegate = GeneratedValuesHelper.getGeneratedValuesDelegate( this, UPDATE );
if ( hasInsertGeneratedProperties() ) {
insertGeneratedValuesProcessor = createGeneratedValuesProcessor( INSERT, insertGeneratedAttributes );
}
if ( hasUpdateGeneratedProperties() ) {
updateGeneratedValuesProcessor = createGeneratedValuesProcessor( UPDATE, updateGeneratedAttributes );
}
tableMappings = buildTableMappings();
insertCoordinator = buildInsertCoordinator();
updateCoordinator = buildUpdateCoordinator();
deleteCoordinator = buildDeleteCoordinator();
@ -4666,11 +4712,39 @@ public abstract class AbstractEntityPersister
Object id,
Object entity,
Object[] state,
GeneratedValues generatedValues,
SharedSessionContractImplementor session) {
if ( insertGeneratedValuesProcessor == null ) {
throw new UnsupportedOperationException( "Entity has no insert-generated properties - `" + getEntityName() + "`" );
}
insertGeneratedValuesProcessor.processGeneratedValues( entity, id, state, session );
insertGeneratedValuesProcessor.processGeneratedValues( entity, id, state, generatedValues, session );
}
protected List<? extends ModelPart> initInsertGeneratedProperties(List<AttributeMapping> generatedAttributes) {
final int originalSize = generatedAttributes.size();
final List<ModelPart> generatedBasicAttributes = new ArrayList<>( originalSize );
for ( AttributeMapping generatedAttribute : generatedAttributes ) {
// todo (7.0) : support non selectable mappings? Component, ToOneAttributeMapping, ...
if ( generatedAttribute instanceof BasicValuedModelPart
&& generatedAttribute.getContainingTableExpression().equals( getSubclassTableName( 0 ) ) ) {
generatedBasicAttributes.add( generatedAttribute );
}
}
final List<ModelPart> identifierList = isIdentifierAssignedByInsert() ?
List.of( getIdentifierMapping() ) :
Collections.emptyList();
if ( originalSize > 0 && generatedBasicAttributes.size() == originalSize ) {
return Collections.unmodifiableList( combine( identifierList, generatedBasicAttributes ) );
}
else {
return identifierList;
}
}
@Override
public List<? extends ModelPart> getInsertGeneratedProperties() {
return insertGeneratedProperties;
}
@Override
@ -4678,11 +4752,35 @@ public abstract class AbstractEntityPersister
Object id,
Object entity,
Object[] state,
GeneratedValues generatedValues,
SharedSessionContractImplementor session) {
if ( updateGeneratedValuesProcessor == null ) {
throw new AssertionFailure( "Entity has no update-generated properties - `" + getEntityName() + "`" );
}
updateGeneratedValuesProcessor.processGeneratedValues( entity, id, state, session );
updateGeneratedValuesProcessor.processGeneratedValues( entity, id, state, generatedValues, session );
}
protected List<? extends ModelPart> initUpdateGeneratedProperties(List<AttributeMapping> generatedAttributes) {
final int originalSize = generatedAttributes.size();
final List<ModelPart> generatedBasicAttributes = new ArrayList<>( originalSize );
for ( AttributeMapping generatedAttribute : generatedAttributes ) {
if ( generatedAttribute instanceof SelectableMapping
&& ( (SelectableMapping) generatedAttribute ).getContainingTableExpression().equals( getSubclassTableName( 0 ) ) ) {
generatedBasicAttributes.add( generatedAttribute );
}
}
if ( generatedBasicAttributes.size() == originalSize ) {
return Collections.unmodifiableList( generatedBasicAttributes );
}
else {
return Collections.emptyList();
}
}
@Override
public List<? extends ModelPart> getUpdateGeneratedProperties() {
return updateGeneratedProperties;
}
@Override
@ -4998,12 +5096,6 @@ public abstract class AbstractEntityPersister
visitSubTypeAttributeMappings( builder::add );
assert superMappingType != null || builder.assertFetchableIndexes();
staticFetchableList = builder.build();
if ( hasInsertGeneratedProperties() ) {
insertGeneratedValuesProcessor = createGeneratedValuesProcessor( INSERT );
}
if ( hasUpdateGeneratedProperties() ) {
updateGeneratedValuesProcessor = createGeneratedValuesProcessor( UPDATE );
}
return true;
}
);

View File

@ -7,6 +7,7 @@
package org.hibernate.persister.entity;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@ -29,9 +30,11 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.generator.Generator;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.EventType;
import org.hibernate.generator.Generator;
import org.hibernate.generator.internal.VersionGeneration;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.internal.FilterAliasGenerator;
import org.hibernate.internal.TableGroupFilterAliasGenerator;
@ -41,9 +44,10 @@ import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.persister.walking.spi.AttributeSource;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
@ -104,7 +108,7 @@ import org.hibernate.type.descriptor.java.VersionJavaType;
* @see org.hibernate.persister.spi.PersisterFactory
* @see org.hibernate.persister.spi.PersisterClassResolver
*/
public interface EntityPersister extends EntityMappingType, RootTableGroupProducer, AttributeSource {
public interface EntityPersister extends EntityMappingType, EntityMutationTarget, RootTableGroupProducer, AttributeSource {
/**
* Finish the initialization of this object.
@ -624,22 +628,50 @@ public interface EntityPersister extends EntityMappingType, RootTableGroupProduc
/**
* Persist an instance
*/
void insert(Object id, Object[] fields, Object object, SharedSessionContractImplementor session);
default void insert(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) {
insertReturning( id, fields, object, session );
}
/**
* Persist an instance
*/
GeneratedValues insertReturning(Object id, Object[] fields, Object object, SharedSessionContractImplementor session);
/**
* Persist an instance
*/
default Object insert(Object[] fields, Object object, SharedSessionContractImplementor session) {
final GeneratedValues generatedValues = insertReturning( fields, object, session );
return generatedValues.getGeneratedValue( getIdentifierMapping() );
}
/**
* Persist an instance, using a natively generated identifier (optional operation)
*/
Object insert(Object[] fields, Object object, SharedSessionContractImplementor session);
GeneratedValues insertReturning(Object[] fields, Object object, SharedSessionContractImplementor session);
/**
* Delete a persistent instance
*/
void delete(Object id, Object version, Object object, SharedSessionContractImplementor session);
default void update(
Object id,
Object[] fields,
int[] dirtyFields,
boolean hasDirtyCollection,
Object[] oldFields,
Object oldVersion,
Object object,
Object rowId,
SharedSessionContractImplementor session) {
updateReturning( id, fields, dirtyFields, hasDirtyCollection, oldFields, oldVersion, object, rowId, session );
}
/**
* Update a persistent instance
*/
void update(
GeneratedValues updateReturning(
Object id,
Object[] fields,
int[] dirtyFields,
@ -896,8 +928,39 @@ public interface EntityPersister extends EntityMappingType, RootTableGroupProduc
* Note, that because we update the PersistenceContext here, callers
* need to take care that they have already written the initial snapshot
* to the PersistenceContext before calling this method.
* @deprecated Use {@link #processInsertGeneratedProperties(Object, Object, Object[], GeneratedValues, SharedSessionContractImplementor)} instead.
*/
void processInsertGeneratedProperties(Object id, Object entity, Object[] state, SharedSessionContractImplementor session);
@Deprecated( forRemoval = true, since = "6.5" )
default void processInsertGeneratedProperties(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) {
processInsertGeneratedProperties( id, entity, state, null, session );
}
/**
* Retrieve the values of any insert generated properties through the provided
* {@link GeneratedValues} or, when that's not available, by selecting them
* back from the database, injecting these generated values into the
* given entity as well as writing this state to the
* {@link org.hibernate.engine.spi.PersistenceContext}.
* <p>
* Note, that because we update the PersistenceContext here, callers
* need to take care that they have already written the initial snapshot
* to the PersistenceContext before calling this method.
*/
default void processInsertGeneratedProperties(
Object id,
Object entity,
Object[] state,
GeneratedValues generatedValues,
SharedSessionContractImplementor session) {
}
default List<? extends ModelPart> getGeneratedProperties(EventType timing) {
return timing == EventType.INSERT ? getInsertGeneratedProperties() : getUpdateGeneratedProperties();
}
default List<? extends ModelPart> getInsertGeneratedProperties() {
return Collections.emptyList();
}
/**
* Perform a select to retrieve the values of any generated properties
@ -908,8 +971,34 @@ public interface EntityPersister extends EntityMappingType, RootTableGroupProduc
* Note, that because we update the PersistenceContext here, callers
* need to take care that they have already written the initial snapshot
* to the PersistenceContext before calling this method.
* @deprecated Use {@link #processUpdateGeneratedProperties(Object, Object, Object[], GeneratedValues, SharedSessionContractImplementor)} instead.
*/
void processUpdateGeneratedProperties(Object id, Object entity, Object[] state, SharedSessionContractImplementor session);
@Deprecated( forRemoval = true, since = "6.5" )
default void processUpdateGeneratedProperties(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) {
processUpdateGeneratedProperties( id, entity, state, null, session );
}
/**
* Retrieve the values of any update generated properties through the provided
* {@link GeneratedValues} or, when that's not available, by selecting them
* back from the database, injecting these generated values into the
* given entity as well as writing this state to the
* {@link org.hibernate.engine.spi.PersistenceContext}.
* <p>
* Note, that because we update the PersistenceContext here, callers
* need to take care that they have already written the initial snapshot
* to the PersistenceContext before calling this method.
*/
void processUpdateGeneratedProperties(
Object id,
Object entity,
Object[] state,
GeneratedValues generatedValues,
SharedSessionContractImplementor session);
default List<? extends ModelPart> getUpdateGeneratedProperties() {
return Collections.emptyList();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -1122,4 +1211,50 @@ public interface EntityPersister extends EntityMappingType, RootTableGroupProduc
*/
@Incubating
Iterable<UniqueKeyEntry> uniqueKeyEntries();
/**
* Get a SQL select string that performs a select based on a unique
* key determined by the given property name.
*
* @param propertyName The name of the property which maps to the
* column(s) to use in the select statement restriction.
* @return The SQL select string
*/
String getSelectByUniqueKeyString(String propertyName);
/**
* Get a SQL select string that performs a select based on a unique
* key determined by the given property names.
*
* @param propertyNames The names of the properties which maps to the
* column(s) to use in the select statement restriction.
* @return The SQL select string
*/
default String getSelectByUniqueKeyString(String[] propertyNames) {
// default impl only for backward compatibility
if ( propertyNames.length > 1 ) {
throw new IllegalArgumentException( "support for multiple properties not implemented" );
}
return getSelectByUniqueKeyString( propertyNames[0] );
}
String getSelectByUniqueKeyString(String[] propertyNames, String[] columnNames);
/**
* The names of the primary key columns in the root table.
*
* @return The primary key column names.
*/
String[] getRootTableKeyColumnNames();
/**
* Get the database-specific SQL command to retrieve the last
* generated IDENTITY value.
*
* @return The SQL command string
*/
String getIdentitySelectString();
String[] getIdentifierColumnNames();
}

View File

@ -43,7 +43,6 @@ import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.TableDetails;
import org.hibernate.metamodel.mapping.internal.BasicEntityIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.CaseStatementDiscriminatorMappingImpl;
@ -75,7 +74,6 @@ import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.logging.Logger;
import static java.util.Collections.emptyMap;
import static org.hibernate.internal.util.collections.ArrayHelper.indexOf;
import static org.hibernate.internal.util.collections.ArrayHelper.to2DStringArray;
import static org.hibernate.internal.util.collections.ArrayHelper.toIntArray;
import static org.hibernate.internal.util.collections.ArrayHelper.toStringArray;

View File

@ -9,10 +9,12 @@ package org.hibernate.persister.entity.mutation;
import org.hibernate.Incubating;
import org.hibernate.annotations.Table;
import org.hibernate.engine.jdbc.mutation.MutationExecutor;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.sql.model.MutationTarget;
import org.hibernate.sql.model.MutationType;
/**
* Anything that can be the target of {@linkplain MutationExecutor mutations}
@ -47,6 +49,30 @@ public interface EntityMutationTarget extends MutationTarget<EntityTableMapping>
/**
* The delegate for executing inserts against the root table for
* targets defined using post-insert id generation
*
* @deprecated use {@link #getInsertDelegate()} instead
*/
InsertGeneratedIdentifierDelegate getIdentityInsertDelegate();
@Deprecated( forRemoval = true, since = "6.5" )
default InsertGeneratedIdentifierDelegate getIdentityInsertDelegate() {
final GeneratedValuesMutationDelegate insertDelegate = getInsertDelegate();
if ( insertDelegate instanceof InsertGeneratedIdentifierDelegate ) {
return (InsertGeneratedIdentifierDelegate) insertDelegate;
}
return null;
}
GeneratedValuesMutationDelegate getInsertDelegate();
GeneratedValuesMutationDelegate getUpdateDelegate();
default GeneratedValuesMutationDelegate getMutationDelegate(MutationType mutationType) {
switch ( mutationType ) {
case INSERT:
return getInsertDelegate();
case UPDATE:
return getUpdateDelegate();
default:
return null;
}
}
}

View File

@ -22,7 +22,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.Generator;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.AttributeMappingsList;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
@ -36,6 +37,7 @@ import org.hibernate.sql.model.ValuesAnalysis;
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
import org.hibernate.sql.model.ast.builder.TableInsertBuilder;
import org.hibernate.sql.model.ast.builder.TableInsertBuilderStandard;
import org.hibernate.sql.model.ast.builder.TableMutationBuilder;
import org.hibernate.tuple.entity.EntityMetamodel;
import static org.hibernate.generator.EventType.INSERT;
@ -55,8 +57,8 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
public InsertCoordinator(AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) {
super( entityPersister, factory );
if ( entityPersister.hasInsertGeneratedProperties() ) {
// disable batching in case of insert generated properties
if ( entityPersister.isIdentifierAssignedByInsert() || entityPersister.hasInsertGeneratedProperties() ) {
// disable batching in case of insert generated identifier or properties
batchKey = null;
}
else {
@ -96,7 +98,7 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
*
* @return The id
*/
public Object coordinateInsert(
public GeneratedValues coordinateInsert(
Object id,
Object[] values,
Object entity,
@ -154,7 +156,7 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
}
}
protected Object doStaticInserts(Object id, Object[] values, Object object, SharedSessionContractImplementor session) {
protected GeneratedValues doStaticInserts(Object id, Object[] values, Object object, SharedSessionContractImplementor session) {
final InsertValuesAnalysis insertValuesAnalysis = new InsertValuesAnalysis( entityPersister(), values );
final TableInclusionChecker tableInclusionChecker = getTableInclusionChecker( insertValuesAnalysis );
@ -220,7 +222,7 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
}
if ( id == null ) {
assert entityPersister().getIdentityInsertDelegate() != null;
assert entityPersister().getInsertDelegate() != null;
}
else {
for ( int position = 0; position < mutationGroup.getNumberOfOperations(); position++ ) {
@ -277,7 +279,7 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
}
}
protected Object doDynamicInserts(
protected GeneratedValues doDynamicInserts(
Object id,
Object[] values,
Object object,
@ -361,12 +363,10 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
return createOperationGroup( null, insertGroupBuilder.buildMutationGroup() );
}
private TableInsertBuilder createTableInsertBuilder(EntityTableMapping tableMapping, boolean forceIdentifierBinding) {
final InsertGeneratedIdentifierDelegate identityDelegate = entityPersister().getIdentityInsertDelegate();
if ( tableMapping.isIdentifierTable() && identityDelegate != null && !forceIdentifierBinding ) {
final BasicEntityIdentifierMapping mapping =
(BasicEntityIdentifierMapping) entityPersister().getIdentifierMapping();
return identityDelegate.createTableInsertBuilder( mapping, tableMapping.getInsertExpectation(), factory() );
private TableMutationBuilder<?> createTableInsertBuilder(EntityTableMapping tableMapping, boolean forceIdentifierBinding) {
final GeneratedValuesMutationDelegate delegate = entityPersister().getInsertDelegate();
if ( tableMapping.isIdentifierTable() && delegate != null && !forceIdentifierBinding ) {
return delegate.createTableMutationBuilder( tableMapping.getInsertExpectation(), factory() );
}
else {
return new TableInsertBuilderStandard( entityPersister(), tableMapping, factory() );
@ -414,13 +414,21 @@ public class InsertCoordinator extends AbstractMutationCoordinator {
entityPersister().addSoftDeleteToInsertGroup( insertGroupBuilder );
// add the keys
final InsertGeneratedIdentifierDelegate identityDelegate = entityPersister().getIdentityInsertDelegate();
insertGroupBuilder.forEachTableMutationBuilder( (tableMutationBuilder) -> {
final TableInsertBuilder tableInsertBuilder = (TableInsertBuilder) tableMutationBuilder;
final EntityTableMapping tableMapping = (EntityTableMapping) tableInsertBuilder.getMutatingTable().getTableMapping();
//noinspection StatementWithEmptyBody
if ( tableMapping.isIdentifierTable() && identityDelegate != null && !forceIdentifierBinding ) {
// nothing to do - the builder already includes the identity handling
if ( tableMapping.isIdentifierTable() && entityPersister().isIdentifierAssignedByInsert() && !forceIdentifierBinding ) {
assert entityPersister().getInsertDelegate() != null;
final OnExecutionGenerator generator = (OnExecutionGenerator) entityPersister().getGenerator();
if ( generator.referenceColumnsInSql( dialect() ) ) {
final BasicEntityIdentifierMapping identifierMapping = (BasicEntityIdentifierMapping) entityPersister().getIdentifierMapping();
final String[] columnValues = generator.getReferencedColumnValues( dialect );
tableMapping.getKeyMapping().forEachKeyColumn( (i, column) -> tableInsertBuilder.addKeyColumn(
column.getColumnName(),
columnValues[i],
identifierMapping.getJdbcMapping()
) );
}
}
else {
tableMapping.getKeyMapping().forEachKeyColumn( tableInsertBuilder::addKeyColumn );

View File

@ -11,6 +11,7 @@ import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.sql.model.MutationOperation;
import org.hibernate.sql.model.ast.builder.AbstractTableUpdateBuilder;
import org.hibernate.sql.model.ast.builder.TableMergeBuilder;
import org.hibernate.sql.model.ast.builder.TableUpdateBuilder;
/**
* Specialized {@link UpdateCoordinator} for {@code merge into}.

View File

@ -8,6 +8,7 @@ package org.hibernate.persister.entity.mutation;
import org.hibernate.Internal;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.sql.model.MutationOperationGroup;
/**
@ -21,7 +22,7 @@ import org.hibernate.sql.model.MutationOperationGroup;
public interface UpdateCoordinator {
MutationOperationGroup getStaticUpdateGroup();
void coordinateUpdate(
GeneratedValues coordinateUpdate(
Object entity,
Object id,
Object rowId,

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.persister.entity.mutation;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.sql.model.internal.MutationOperationGroupFactory;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.persister.entity.AbstractEntityPersister;
@ -28,8 +29,9 @@ public class UpdateCoordinatorNoOp implements UpdateCoordinator {
}
@Override
public void coordinateUpdate(Object entity, Object id, Object rowId, Object[] values, Object oldVersion, Object[] incomingOldValues, int[] dirtyAttributeIndexes, boolean hasDirtyCollection, SharedSessionContractImplementor session) {
public GeneratedValues coordinateUpdate(Object entity, Object id, Object rowId, Object[] values, Object oldVersion, Object[] incomingOldValues, int[] dirtyAttributeIndexes, boolean hasDirtyCollection, SharedSessionContractImplementor session) {
// nothing to do
return null;
}
@Override

View File

@ -9,6 +9,7 @@ package org.hibernate.persister.entity.mutation;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Supplier;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
@ -29,6 +30,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.Generator;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.CollectionHelper;
@ -45,7 +48,7 @@ import org.hibernate.sql.model.MutationType;
import org.hibernate.sql.model.ast.MutatingTableReference;
import org.hibernate.sql.model.ast.builder.AbstractTableUpdateBuilder;
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
import org.hibernate.sql.model.ast.builder.RestrictedTableMutationBuilder;
import org.hibernate.sql.model.ast.builder.TableMutationBuilder;
import org.hibernate.sql.model.ast.builder.TableUpdateBuilder;
import org.hibernate.sql.model.ast.builder.TableUpdateBuilderSkipped;
import org.hibernate.sql.model.ast.builder.TableUpdateBuilderStandard;
@ -164,7 +167,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
}
@Override
public void coordinateUpdate(
public GeneratedValues coordinateUpdate(
Object entity,
Object id,
Object rowId,
@ -176,7 +179,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
SharedSessionContractImplementor session) {
final EntityVersionMapping versionMapping = entityPersister().getVersionMapping();
if ( versionMapping != null ) {
final boolean isForcedVersionIncrement = handlePotentialImplicitForcedVersionIncrement(
final Supplier<GeneratedValues> generatedValuesAccess = handlePotentialImplicitForcedVersionIncrement(
entity,
id,
values,
@ -185,8 +188,8 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
session,
versionMapping
);
if ( isForcedVersionIncrement ) {
return;
if ( generatedValuesAccess != null ) {
return generatedValuesAccess.get();
}
}
@ -239,7 +242,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
}
performUpdate(
return performUpdate(
entity,
id,
rowId,
@ -255,7 +258,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
);
}
protected void performUpdate(
protected GeneratedValues performUpdate(
Object entity,
Object id,
Object rowId,
@ -307,9 +310,10 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
//noinspection StatementWithEmptyBody
if ( valuesAnalysis.tablesNeedingUpdate.isEmpty() ) {
// nothing to do
return null;
}
else if ( valuesAnalysis.needsDynamicUpdate() ) {
doDynamicUpdate(
return doDynamicUpdate(
entity,
id,
rowId,
@ -321,7 +325,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
);
}
else {
doStaticUpdate(
return doStaticUpdate(
entity,
id,
rowId,
@ -395,7 +399,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
}
}
protected boolean handlePotentialImplicitForcedVersionIncrement(
protected Supplier<GeneratedValues> handlePotentialImplicitForcedVersionIncrement(
Object entity,
Object id,
Object[] values,
@ -437,11 +441,11 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
if ( isSimpleVersionUpdate ) {
// we have just the version being updated - use the special handling
assert newVersion != null;
doVersionUpdate( entity, id, newVersion, oldVersion, session );
return true;
final GeneratedValues generatedValues = doVersionUpdate( entity, id, newVersion, oldVersion, session );
return () -> generatedValues;
}
else {
return false;
return null;
}
}
@ -466,16 +470,16 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
: entityPersister().getPropertyUpdateability();
}
protected void doVersionUpdate(
protected GeneratedValues doVersionUpdate(
Object entity,
Object id,
Object version,
Object oldVersion,
SharedSessionContractImplementor session) {
doVersionUpdate( entity, id, version, oldVersion, true, session );
return doVersionUpdate( entity, id, version, oldVersion, true, session );
}
protected void doVersionUpdate(
protected GeneratedValues doVersionUpdate(
Object entity,
Object id,
Object version,
@ -521,7 +525,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
);
try {
mutationExecutor.execute(
return mutationExecutor.execute(
entity,
null,
(tableMapping) -> tableMapping.getTableName().equals( entityPersister().getIdentifierTableName() ),
@ -749,7 +753,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
);
}
protected void doStaticUpdate(
protected GeneratedValues doStaticUpdate(
Object entity,
Object id,
Object rowId,
@ -774,8 +778,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
bindPartitionColumnValueBindings( oldValues, session, mutationExecutor.getJdbcValueBindings() );
try {
//noinspection SuspiciousMethodCalls
mutationExecutor.execute(
return mutationExecutor.execute(
entity,
valuesAnalysis,
valuesAnalysis.tablesNeedingUpdate::contains,
@ -926,7 +929,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
}
}
protected void doDynamicUpdate(
protected GeneratedValues doDynamicUpdate(
Object entity,
Object id,
Object rowId,
@ -962,7 +965,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
bindPartitionColumnValueBindings( oldValues, session, mutationExecutor.getJdbcValueBindings() );
try {
mutationExecutor.execute(
return mutationExecutor.execute(
entity,
valuesAnalysis,
(tableMapping) -> {
@ -1040,14 +1043,14 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
entityPersister().forEachMutableTable( (tableMapping) -> {
final MutatingTableReference tableReference = new MutatingTableReference( tableMapping );
final RestrictedTableMutationBuilder<?,?> tableUpdateBuilder;
final TableMutationBuilder<?> tableUpdateBuilder;
//noinspection SuspiciousMethodCalls
if ( ! valuesAnalysis.tablesNeedingUpdate.contains( tableReference.getTableMapping() ) ) {
// this table does not need updating
tableUpdateBuilder = new TableUpdateBuilderSkipped( tableReference );
}
else {
tableUpdateBuilder = newTableUpdateBuilder( tableMapping );
tableUpdateBuilder = createTableUpdateBuilder( tableMapping );
}
updateGroupBuilder.addTableDetailsBuilder( tableUpdateBuilder );
} );
@ -1065,6 +1068,18 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
return createOperationGroup( valuesAnalysis, updateGroupBuilder.buildMutationGroup() );
}
private TableMutationBuilder<?> createTableUpdateBuilder(EntityTableMapping tableMapping) {
final GeneratedValuesMutationDelegate delegate = tableMapping.isIdentifierTable() ?
entityPersister().getUpdateDelegate() :
null;
if ( delegate != null ) {
return delegate.createTableMutationBuilder( tableMapping.getInsertExpectation(), factory() );
}
else {
return newTableUpdateBuilder( tableMapping );
}
}
protected <O extends MutationOperation> AbstractTableUpdateBuilder<O> newTableUpdateBuilder(EntityTableMapping tableMapping) {
return new TableUpdateBuilderStandard<>( entityPersister(), tableMapping, factory() );
}
@ -1624,7 +1639,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
entityPersister().forEachMutableTable( (tableMapping) -> {
// NOTE : TableUpdateBuilderStandard handles custom sql-update mappings
updateGroupBuilder.addTableDetailsBuilder( newTableUpdateBuilder( tableMapping ) );
updateGroupBuilder.addTableDetailsBuilder( createTableUpdateBuilder( tableMapping ) );
} );
// next, iterate each attribute and build the SET and WHERE clauses

View File

@ -67,6 +67,24 @@ public class ResultsHelper {
);
}
public static Expression resolveSqlExpression(
DomainResultCreationStateImpl resolver,
TableReference tableReference,
SelectableMapping selectableMapping,
int valuesArrayPosition) {
return resolver.resolveSqlExpression(
createColumnReferenceKey(
tableReference,
selectableMapping.getSelectablePath(),
selectableMapping.getJdbcMapping()
),
processingState -> new ResultSetMappingSqlSelection(
valuesArrayPosition,
selectableMapping.getJdbcMapping()
)
);
}
private ResultsHelper() {
}

View File

@ -22,9 +22,12 @@ import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.Generator;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.id.BulkInsertionCapableIdentifierGenerator;
import org.hibernate.id.OptimizableGenerator;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.id.enhanced.Optimizer;
import org.hibernate.id.insert.Binder;
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
@ -37,6 +40,7 @@ import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
import org.hibernate.query.SemanticException;
import org.hibernate.query.SortDirection;
import org.hibernate.query.results.TableGroupImpl;
@ -70,17 +74,12 @@ import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
import org.hibernate.sql.exec.internal.JdbcParameterImpl;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation;
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
import org.hibernate.sql.exec.spi.JdbcOperationQueryUpdate;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.graph.basic.BasicFetch;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.generator.Generator;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.type.descriptor.ValueBinder;
import static org.hibernate.generator.EventType.INSERT;
@ -564,15 +563,16 @@ public class InsertExecutionDelegate implements TableBasedInsertHandler.Executio
.translate( null, executionContext.getQueryOptions() );
if ( generator.generatedOnExecution() ) {
final OnExecutionGenerator databaseGenerator = (OnExecutionGenerator) generator;
final InsertGeneratedIdentifierDelegate identifierDelegate =
databaseGenerator.getGeneratedIdentifierDelegate( (PostInsertIdentityPersister) entityPersister );
final GeneratedValuesMutationDelegate insertDelegate = ( (EntityMutationTarget) entityDescriptor.getEntityPersister() ).getInsertDelegate();
// todo 7.0 : InsertGeneratedIdentifierDelegate will be removed once we're going to handle
// generated values within the jdbc insert operaetion itself
final InsertGeneratedIdentifierDelegate identifierDelegate = (InsertGeneratedIdentifierDelegate) insertDelegate;
final String finalSql = identifierDelegate.prepareIdentifierGeneratingInsert( jdbcInsert.getSqlString() );
final BasicEntityIdentifierMapping identifierMapping =
(BasicEntityIdentifierMapping) entityDescriptor.getIdentifierMapping();
final ValueBinder jdbcValueBinder = identifierMapping.getJdbcMapping().getJdbcValueBinder();
for ( Map.Entry<Object, Object> entry : entityTableToRootIdentity.entrySet() ) {
final Object rootIdentity = identifierDelegate.performInsert(
final GeneratedValues generatedValues = identifierDelegate.performInsertReturning(
finalSql,
session,
new Binder() {
@ -586,6 +586,7 @@ public class InsertExecutionDelegate implements TableBasedInsertHandler.Executio
}
}
);
final Object rootIdentity = generatedValues.getGeneratedValue( identifierMapping );
entry.setValue( rootIdentity );
}

View File

@ -8737,6 +8737,10 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
if ( tableUpdate.getWhereFragment() != null ) {
sqlBuffer.append( " and (" ).append( tableUpdate.getWhereFragment() ).append( ")" );
}
if ( tableUpdate.getNumberOfReturningColumns() > 0 ) {
visitReturningColumns( tableUpdate::getReturningColumns );
}
}
finally {
getCurrentClauseStack().pop();

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.sql.model;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
public interface EntityMutationOperationGroup extends MutationOperationGroup {
@ -22,4 +23,7 @@ public interface EntityMutationOperationGroup extends MutationOperationGroup {
return this;
}
default GeneratedValuesMutationDelegate getMutationDelegate() {
return getMutationTarget().getMutationDelegate( getMutationType() );
}
}

View File

@ -57,15 +57,12 @@ public interface PreparableMutationOperation extends MutationOperation {
return false;
}
if ( getMutationType() == MutationType.INSERT ) {
// we cannot batch inserts which generate id values post-insert (IDENTITY, TRIGGER, etc)
if ( getTableDetails().isIdentifierTable()
&& getMutationTarget() instanceof EntityMutationTarget
&& ( (EntityMutationTarget) getMutationTarget() ).getIdentityInsertDelegate() != null ) {
return false;
}
}
else if ( getMutationType() == MutationType.UPDATE ) {
// This should already be guaranteed by the batchKey being null
assert !getTableDetails().isIdentifierTable() ||
!( getMutationTarget() instanceof EntityMutationTarget
&& ( (EntityMutationTarget) getMutationTarget() ).getMutationDelegate( getMutationType() ) != null );
if ( getMutationType() == MutationType.UPDATE ) {
// we cannot batch updates against optional tables
if ( getTableDetails().isOptional() ) {
return false;

View File

@ -43,8 +43,6 @@ public interface TableInsert extends TableMutation<JdbcInsertMutation> {
/**
* The columns to return from the insert.
*
* @see java.sql.Connection#prepareStatement(String, String[])
*/
List<ColumnReference> getReturningColumns();

View File

@ -9,6 +9,8 @@ package org.hibernate.sql.model.ast;
import java.util.List;
import java.util.function.BiConsumer;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.model.MutationOperation;
/**
@ -44,4 +46,26 @@ public interface TableUpdate<O extends MutationOperation>
* @see #getValueBindings()
*/
void forEachValueBinding(BiConsumer<Integer, ColumnValueBinding> consumer);
/**
* The columns to return from the insert.
*/
List<ColumnReference> getReturningColumns();
/**
* The number of columns being returned
*
* @see #getReturningColumns
*/
default int getNumberOfReturningColumns() {
final List<ColumnReference> returningColumns = getReturningColumns();
return CollectionHelper.size( returningColumns );
}
/**
* Visit each return-column
*
* @see #getReturningColumns
*/
void forEachReturningColumn(BiConsumer<Integer,ColumnReference> consumer);
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.sql.model.ast.builder;
import java.util.Collections;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -76,7 +77,8 @@ public class CollectionRowDeleteByUpdateSetNullBuilder<O extends MutationOperati
getKeyRestrictionBindings(),
getOptimisticLockBindings(),
getWhereFragment(),
null
null,
Collections.emptyList()
) {
@Override
public Expectation getExpectation() {

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.sql.model.ast.builder;
import java.util.Collections;
import java.util.List;
import org.hibernate.HibernateException;
@ -96,7 +97,8 @@ public class TableUpdateBuilderStandard<O extends MutationOperation> extends Abs
getKeyRestrictionBindings(),
getOptimisticLockBindings(),
whereFragment,
null
null,
Collections.emptyList()
);
}
}

View File

@ -6,9 +6,12 @@
*/
package org.hibernate.sql.model.internal;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.model.MutationTarget;
import org.hibernate.sql.model.ast.AbstractTableUpdate;
import org.hibernate.sql.model.ast.ColumnValueBinding;
@ -63,6 +66,16 @@ public class TableUpdateCustomSql
return getMutatingTable().getTableMapping().getUpdateDetails().isCallable();
}
@Override
public List<ColumnReference> getReturningColumns() {
return Collections.emptyList();
}
@Override
public void forEachReturningColumn(BiConsumer<Integer, ColumnReference> consumer) {
// nothing to do
}
@Override
public void accept(SqlAstWalker walker) {
walker.visitCustomTableUpdate( this );

View File

@ -13,6 +13,7 @@ import java.util.function.Consumer;
import org.hibernate.jdbc.Expectation;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
import org.hibernate.sql.model.MutationOperation;
import org.hibernate.sql.model.MutationTarget;
@ -86,4 +87,14 @@ public class TableUpdateNoSet
@Override
public void forEachParameter(Consumer<ColumnValueParameter> consumer) {
}
@Override
public List<ColumnReference> getReturningColumns() {
return Collections.emptyList();
}
@Override
public void forEachReturningColumn(BiConsumer<Integer, ColumnReference> consumer) {
// nothing to do
}
}

View File

@ -6,10 +6,13 @@
*/
package org.hibernate.sql.model.internal;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import org.hibernate.jdbc.Expectation;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.model.MutationTarget;
import org.hibernate.sql.model.ast.AbstractTableUpdate;
import org.hibernate.sql.model.ast.ColumnValueBinding;
@ -22,8 +25,8 @@ import org.hibernate.sql.model.jdbc.JdbcMutationOperation;
*/
public class TableUpdateStandard extends AbstractTableUpdate<JdbcMutationOperation> {
private final String whereFragment;
private final Expectation expectation;
private final List<ColumnReference> returningColumns;
public TableUpdateStandard(
MutatingTableReference mutatingTable,
@ -32,7 +35,17 @@ public class TableUpdateStandard extends AbstractTableUpdate<JdbcMutationOperati
List<ColumnValueBinding> valueBindings,
List<ColumnValueBinding> keyRestrictionBindings,
List<ColumnValueBinding> optLockRestrictionBindings) {
this( mutatingTable, mutationTarget, sqlComment, valueBindings, keyRestrictionBindings, optLockRestrictionBindings, null, null );
this(
mutatingTable,
mutationTarget,
sqlComment,
valueBindings,
keyRestrictionBindings,
optLockRestrictionBindings,
null,
null,
Collections.emptyList()
);
}
public TableUpdateStandard(
@ -43,10 +56,12 @@ public class TableUpdateStandard extends AbstractTableUpdate<JdbcMutationOperati
List<ColumnValueBinding> keyRestrictionBindings,
List<ColumnValueBinding> optLockRestrictionBindings,
String whereFragment,
Expectation expectation) {
Expectation expectation,
List<ColumnReference> returningColumns) {
super( mutatingTable, mutationTarget, sqlComment, valueBindings, keyRestrictionBindings, optLockRestrictionBindings );
this.whereFragment = whereFragment;
this.expectation = expectation;
this.returningColumns = returningColumns;
}
public TableUpdateStandard(
@ -57,7 +72,17 @@ public class TableUpdateStandard extends AbstractTableUpdate<JdbcMutationOperati
List<ColumnValueBinding> keyRestrictionBindings,
List<ColumnValueBinding> optLockRestrictionBindings,
List<ColumnValueParameter> parameters) {
this( tableReference, mutationTarget, sqlComment, valueBindings, keyRestrictionBindings, optLockRestrictionBindings, parameters, null, null );
this(
tableReference,
mutationTarget,
sqlComment,
valueBindings,
keyRestrictionBindings,
optLockRestrictionBindings,
parameters,
null,
null
);
}
public TableUpdateStandard(
@ -73,6 +98,7 @@ public class TableUpdateStandard extends AbstractTableUpdate<JdbcMutationOperati
super( tableReference, mutationTarget, sqlComment, valueBindings, keyRestrictionBindings, optLockRestrictionBindings, parameters );
this.whereFragment = whereFragment;
this.expectation = expectation;
this.returningColumns = Collections.emptyList();
}
@Override
@ -101,4 +127,14 @@ public class TableUpdateStandard extends AbstractTableUpdate<JdbcMutationOperati
}
return super.getExpectation();
}
@Override
public List<ColumnReference> getReturningColumns() {
return returningColumns;
}
@Override
public void forEachReturningColumn(BiConsumer<Integer,ColumnReference> consumer) {
forEachThing( returningColumns, consumer );
}
}

View File

@ -36,6 +36,8 @@ import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.internal.FilterAliasGenerator;
import org.hibernate.internal.util.IndexedConsumer;
@ -56,11 +58,13 @@ import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.TableDetails;
import org.hibernate.metamodel.mapping.ValuedModelPart;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.UniqueKeyEntry;
import org.hibernate.persister.entity.mutation.EntityTableMapping;
import org.hibernate.persister.spi.PersisterClassResolver;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
@ -399,11 +403,12 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
}
@Override
public void insert(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) {
public GeneratedValues insertReturning(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) {
return null;
}
@Override
public Serializable insert(Object[] fields, Object object, SharedSessionContractImplementor session) {
public GeneratedValues insertReturning(Object[] fields, Object object, SharedSessionContractImplementor session) {
return null;
}
@ -412,7 +417,7 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
}
@Override
public void update(
public GeneratedValues updateReturning(
Object id,
Object[] fields,
int[] dirtyFields,
@ -422,6 +427,7 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
Object object,
Object rowId,
SharedSessionContractImplementor session) {
return null;
}
@Override
@ -605,11 +611,11 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
}
@Override
public void processInsertGeneratedProperties(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) {
public void processInsertGeneratedProperties(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) {
}
@Override
public void processUpdateGeneratedProperties(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) {
public void processUpdateGeneratedProperties(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) {
}
@Override
@ -759,6 +765,31 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
return Collections.emptyList();
}
@Override
public String getSelectByUniqueKeyString(String propertyName) {
return null;
}
@Override
public String getSelectByUniqueKeyString(String[] propertyNames, String[] columnNames) {
return null;
}
@Override
public String[] getRootTableKeyColumnNames() {
return new String[0];
}
@Override
public String getIdentitySelectString() {
return null;
}
@Override
public String[] getIdentifierColumnNames() {
return new String[0];
}
@Override
public boolean isAffectedByEnabledFilters(LoadQueryInfluencers influencers) {
return false;
@ -788,6 +819,51 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
public JavaType getMappedJavaType() {
return null;
}
@Override
public EntityMappingType getTargetPart() {
return null;
}
@Override
public void forEachMutableTable(Consumer<EntityTableMapping> consumer) {
}
@Override
public void forEachMutableTableReverse(Consumer<EntityTableMapping> consumer) {
}
@Override
public String getIdentifierTableName() {
return null;
}
@Override
public EntityTableMapping getIdentifierTableMapping() {
return null;
}
@Override
public ModelPart getIdentifierDescriptor() {
return null;
}
@Override
public boolean hasSkippableTables() {
return false;
}
@Override
public GeneratedValuesMutationDelegate getInsertDelegate() {
return null;
}
@Override
public GeneratedValuesMutationDelegate getUpdateDelegate() {
return null;
}
}
public static class NoopCollectionPersister implements CollectionPersister {

View File

@ -33,6 +33,8 @@ import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.internal.FilterAliasGenerator;
import org.hibernate.internal.util.IndexedConsumer;
@ -59,6 +61,7 @@ import org.hibernate.orm.test.jpa.SettingsGenerator;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.UniqueKeyEntry;
import org.hibernate.persister.entity.mutation.EntityTableMapping;
import org.hibernate.persister.internal.PersisterClassResolverInitiator;
import org.hibernate.persister.spi.PersisterClassResolver;
import org.hibernate.persister.spi.PersisterCreationContext;
@ -442,11 +445,12 @@ public class PersisterClassProviderTest {
}
@Override
public void insert(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) {
public GeneratedValues insertReturning(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) {
return null;
}
@Override
public Serializable insert(Object[] fields, Object object, SharedSessionContractImplementor session) {
public GeneratedValues insertReturning(Object[] fields, Object object, SharedSessionContractImplementor session) {
return null;
}
@ -455,7 +459,8 @@ public class PersisterClassProviderTest {
}
@Override
public void update(Object id, Object[] fields, int[] dirtyFields, boolean hasDirtyCollection, Object[] oldFields, Object oldVersion, Object object, Object rowId, SharedSessionContractImplementor session) {
public GeneratedValues updateReturning(Object id, Object[] fields, int[] dirtyFields, boolean hasDirtyCollection, Object[] oldFields, Object oldVersion, Object object, Object rowId, SharedSessionContractImplementor session) {
return null;
}
@Override
@ -622,11 +627,11 @@ public class PersisterClassProviderTest {
}
@Override
public void processInsertGeneratedProperties(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) {
public void processInsertGeneratedProperties(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) {
}
@Override
public void processUpdateGeneratedProperties(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) {
public void processUpdateGeneratedProperties(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) {
}
@Override
@ -730,6 +735,31 @@ public class PersisterClassProviderTest {
return Collections.emptyList();
}
@Override
public String getSelectByUniqueKeyString(String propertyName) {
return null;
}
@Override
public String getSelectByUniqueKeyString(String[] propertyNames, String[] columnNames) {
return null;
}
@Override
public String[] getRootTableKeyColumnNames() {
return new String[0];
}
@Override
public String getIdentitySelectString() {
return null;
}
@Override
public String[] getIdentifierColumnNames() {
return new String[0];
}
@Override
public CacheEntry buildCacheEntry(Object entity, Object[] state, Object version, SharedSessionContractImplementor session) {
return null;
@ -809,6 +839,51 @@ public class PersisterClassProviderTest {
public JavaType getMappedJavaType() {
return null;
}
@Override
public EntityMappingType getTargetPart() {
return null;
}
@Override
public void forEachMutableTable(Consumer<EntityTableMapping> consumer) {
}
@Override
public void forEachMutableTableReverse(Consumer<EntityTableMapping> consumer) {
}
@Override
public String getIdentifierTableName() {
return null;
}
@Override
public EntityTableMapping getIdentifierTableMapping() {
return null;
}
@Override
public ModelPart getIdentifierDescriptor() {
return null;
}
@Override
public boolean hasSkippableTables() {
return false;
}
@Override
public GeneratedValuesMutationDelegate getInsertDelegate() {
return null;
}
@Override
public GeneratedValuesMutationDelegate getUpdateDelegate() {
return null;
}
}
public static class GoofyException extends RuntimeException {

View File

@ -34,6 +34,8 @@ import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.generator.values.GeneratedValues;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.UUIDHexGenerator;
import org.hibernate.internal.FilterAliasGenerator;
@ -58,6 +60,7 @@ import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.UniqueKeyEntry;
import org.hibernate.persister.entity.mutation.EntityTableMapping;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
@ -264,10 +267,10 @@ public class CustomPersister implements EntityPersister {
return getPropertyValues( object );
}
public void processInsertGeneratedProperties(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) {
public void processInsertGeneratedProperties(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) {
}
public void processUpdateGeneratedProperties(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) {
public void processUpdateGeneratedProperties(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) {
}
@Override
@ -500,7 +503,7 @@ public class CustomPersister implements EntityPersister {
throw new UnsupportedOperationException();
}
public void insert(
public GeneratedValues insertReturning(
Object id,
Object[] fields,
Object object,
@ -508,9 +511,11 @@ public class CustomPersister implements EntityPersister {
) throws HibernateException {
INSTANCES.put(id, ( (Custom) object ).clone() );
return null;
}
public Serializable insert(Object[] fields, Object object, SharedSessionContractImplementor session)
public GeneratedValues insertReturning(Object[] fields, Object object, SharedSessionContractImplementor session)
throws HibernateException {
throw new UnsupportedOperationException();
@ -529,7 +534,7 @@ public class CustomPersister implements EntityPersister {
/**
* @see EntityPersister
*/
public void update(
public GeneratedValues updateReturning(
Object id,
Object[] fields,
int[] dirtyFields,
@ -543,6 +548,7 @@ public class CustomPersister implements EntityPersister {
INSTANCES.put( id, ( (Custom) object ).clone() );
return null;
}
private static final BasicType<String> STRING_TYPE = new BasicTypeImpl<>(
@ -851,6 +857,31 @@ public class CustomPersister implements EntityPersister {
return Collections.emptyList();
}
@Override
public String getSelectByUniqueKeyString(String propertyName) {
return null;
}
@Override
public String getSelectByUniqueKeyString(String[] propertyNames, String[] columnNames) {
return null;
}
@Override
public String getIdentitySelectString() {
return null;
}
@Override
public String[] getIdentifierColumnNames() {
return new String[0];
}
@Override
public String[] getRootTableKeyColumnNames() {
return new String[0];
}
@Override
public boolean isAffectedByEntityGraph(LoadQueryInfluencers loadQueryInfluencers) {
return loadQueryInfluencers.getEffectiveEntityGraph().getGraph() != null;
@ -880,4 +911,49 @@ public class CustomPersister implements EntityPersister {
public JavaType getMappedJavaType() {
return null;
}
@Override
public EntityMappingType getTargetPart() {
return null;
}
@Override
public void forEachMutableTable(Consumer<EntityTableMapping> consumer) {
}
@Override
public void forEachMutableTableReverse(Consumer<EntityTableMapping> consumer) {
}
@Override
public String getIdentifierTableName() {
return null;
}
@Override
public EntityTableMapping getIdentifierTableMapping() {
return null;
}
@Override
public ModelPart getIdentifierDescriptor() {
return null;
}
@Override
public boolean hasSkippableTables() {
return false;
}
@Override
public GeneratedValuesMutationDelegate getInsertDelegate() {
return null;
}
@Override
public GeneratedValuesMutationDelegate getUpdateDelegate() {
return null;
}
}

View File

@ -0,0 +1,353 @@
/*
* 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.orm.test.mapping.generated.delegate;
import java.util.Date;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.RowId;
import org.hibernate.annotations.SourceType;
import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.id.insert.UniqueKeySelectingDelegate;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.model.MutationType;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests {@link GeneratedValuesMutationDelegate efficient generated values retrieval}
* when {@link GenerationType#IDENTITY identity} generated identifiers are involved.
*
* @author Marco Belladelli
*/
@DomainModel( annotatedClasses = {
MutationDelegateIdentityTest.IdentityOnly.class,
MutationDelegateIdentityTest.IdentityAndValues.class,
MutationDelegateIdentityTest.IdentityAndValuesAndRowId.class,
MutationDelegateIdentityTest.IdentityAndValuesAndRowIdAndNaturalId.class,
} )
@SessionFactory( useCollectingStatementInspector = true )
@RequiresDialectFeature( feature = DialectFeatureChecks.SupportsIdentityColumns.class )
// Batch size is only enabled to make sure it's ignored when using mutation delegates
@ServiceRegistry( settings = @Setting( name = AvailableSettings.STATEMENT_BATCH_SIZE, value = "5" ) )
public class MutationDelegateIdentityTest {
@Test
public void testInsertGeneratedIdentityOnly(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate( scope, IdentityOnly.class, MutationType.INSERT );
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
inspector.clear();
scope.inTransaction( session -> {
final IdentityOnly entity = new IdentityOnly();
session.persist( entity );
session.flush();
assertThat( entity.getId() ).isNotNull();
assertThat( entity.getName() ).isNull();
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" );
inspector.assertExecutedCount( delegate != null ? 1 : 2 );
} );
}
@Test
public void testInsertGeneratedValuesAndIdentity(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate(
scope,
IdentityAndValues.class,
MutationType.INSERT
);
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
inspector.clear();
scope.inTransaction( session -> {
final IdentityAndValues entity = new IdentityAndValues();
session.persist( entity );
session.flush();
assertThat( entity.getId() ).isNotNull();
assertThat( entity.getName() ).isEqualTo( "default_name" );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" );
inspector.assertExecutedCount(
delegate != null && delegate.supportsArbitraryValues() ? 1 : 2
);
} );
}
@Test
public void testUpdateGeneratedValuesAndIdentity(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate(
scope,
IdentityAndValues.class,
MutationType.UPDATE
);
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
final Integer id = scope.fromTransaction( session -> {
final IdentityAndValues entity = new IdentityAndValues();
session.persist( entity );
session.flush();
return entity.getId();
} );
inspector.clear();
scope.inTransaction( session -> {
final IdentityAndValues entity = session.find( IdentityAndValues.class, id );
entity.setData( "changed" );
session.flush();
assertThat( entity.getUpdateDate() ).isNotNull();
inspector.assertIsSelect( 0 );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "update" );
inspector.assertExecutedCount(
delegate != null && delegate.supportsArbitraryValues() ? 2 : 3
);
} );
}
@Test
public void testInsertGeneratedValuesAndIdentityAndRowId(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate(
scope,
IdentityAndValuesAndRowId.class,
MutationType.INSERT
);
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
inspector.clear();
scope.inTransaction( session -> {
final IdentityAndValuesAndRowId entity = new IdentityAndValuesAndRowId();
session.persist( entity );
session.flush();
assertThat( entity.getId() ).isNotNull();
assertThat( entity.getName() ).isEqualTo( "default_name" );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" );
inspector.assertExecutedCount(
delegate != null && delegate.supportsArbitraryValues() ? 1 : 2
);
final boolean shouldHaveRowId = delegate != null && delegate.supportsRowId()
&& scope.getSessionFactory().getJdbcServices().getDialect().rowId( "" ) != null;
if ( shouldHaveRowId ) {
// assert row-id was populated in entity entry
final PersistenceContext pc = session.getPersistenceContextInternal();
final EntityEntry entry = pc.getEntry( entity );
assertThat( entry.getRowId() ).isNotNull();
}
// test update in same transaction
inspector.clear();
entity.setData( "changed" );
session.flush();
assertThat( entity.getUpdateDate() ).isNotNull();
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "update" );
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "id_column", shouldHaveRowId ? 0 : 1 );
} );
scope.inSession( session -> assertThat( session.find(
IdentityAndValuesAndRowId.class,
1
).getUpdateDate() ).isNotNull() );
}
@Test
public void testInsertGeneratedValuesAndIdentityAndRowIdAndNaturalId(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate(
scope,
IdentityAndValuesAndRowIdAndNaturalId.class,
MutationType.INSERT
);
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
inspector.clear();
scope.inTransaction( session -> {
final IdentityAndValuesAndRowIdAndNaturalId entity = new IdentityAndValuesAndRowIdAndNaturalId(
"naturalid_1"
);
session.persist( entity );
session.flush();
assertThat( entity.getId() ).isNotNull();
assertThat( entity.getName() ).isEqualTo( "default_name" );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" );
final boolean isUniqueKeyDelegate = delegate instanceof UniqueKeySelectingDelegate;
inspector.assertExecutedCount(
delegate == null || !delegate.supportsArbitraryValues() || isUniqueKeyDelegate ? 2 : 1
);
if ( isUniqueKeyDelegate ) {
inspector.assertNumberOfOccurrenceInQueryNoSpace( 1, "data", 1 );
inspector.assertNumberOfOccurrenceInQueryNoSpace( 1, "id_column", 0 );
}
final boolean shouldHaveRowId = delegate != null && delegate.supportsRowId()
&& scope.getSessionFactory().getJdbcServices().getDialect().rowId( "" ) != null;
if ( shouldHaveRowId ) {
// assert row-id was populated in entity entry
final PersistenceContext pc = session.getPersistenceContextInternal();
final EntityEntry entry = pc.getEntry( entity );
assertThat( entry.getRowId() ).isNotNull();
}
} );
}
private static GeneratedValuesMutationDelegate getDelegate(
SessionFactoryScope scope,
Class<?> entityClass,
MutationType mutationType) {
final EntityPersister entityDescriptor = scope.getSessionFactory()
.getMappingMetamodel()
.findEntityDescriptor( entityClass );
return entityDescriptor.getMutationDelegate( mutationType );
}
@Entity( name = "IdentityOnly" )
public static class IdentityOnly {
@Id
@GeneratedValue( strategy = GenerationType.IDENTITY )
private Integer id;
private String name;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
}
@Entity( name = "IdentityAndValues" )
@SuppressWarnings( "unused" )
public static class IdentityAndValues {
@Id
@GeneratedValue( strategy = GenerationType.IDENTITY )
private Integer id;
@Generated( event = EventType.INSERT )
@ColumnDefault( "'default_name'" )
private String name;
@UpdateTimestamp( source = SourceType.DB )
private Date updateDate;
private String data;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public Date getUpdateDate() {
return updateDate;
}
public void setData(String data) {
this.data = data;
}
}
@RowId
@Entity( name = "IdentityAndValuesAndRowId" )
@SuppressWarnings( "unused" )
public static class IdentityAndValuesAndRowId {
@Id
@Column( name = "id_column" )
@GeneratedValue( strategy = GenerationType.IDENTITY )
private Integer id;
@Generated( event = EventType.INSERT )
@ColumnDefault( "'default_name'" )
private String name;
@UpdateTimestamp( source = SourceType.DB )
private Date updateDate;
private String data;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public Date getUpdateDate() {
return updateDate;
}
public void setData(String data) {
this.data = data;
}
}
@RowId
@Entity( name = "IdentityAndValuesAndRowIdAndNaturalId" )
@SuppressWarnings( "unused" )
public static class IdentityAndValuesAndRowIdAndNaturalId {
@Id
@Column( name = "id_column" )
@GeneratedValue( strategy = GenerationType.IDENTITY )
private Integer id;
@Generated( event = EventType.INSERT )
@ColumnDefault( "'default_name'" )
private String name;
@NaturalId
private String data;
public IdentityAndValuesAndRowIdAndNaturalId() {
}
private IdentityAndValuesAndRowIdAndNaturalId(String data) {
this.data = data;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
}
}

View File

@ -0,0 +1,225 @@
/*
* 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.orm.test.mapping.generated.delegate;
import java.util.Date;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.SourceType;
import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.model.MutationType;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests {@link GeneratedValuesMutationDelegate efficient generated values retrieval}
* with {@link InheritanceType#JOINED} inheritance structures.
*
* @author Marco Belladelli
*/
@DomainModel( annotatedClasses = {
MutationDelegateJoinedInheritanceTest.BaseEntity.class,
MutationDelegateJoinedInheritanceTest.ChildEntity.class,
} )
@SessionFactory( useCollectingStatementInspector = true )
public class MutationDelegateJoinedInheritanceTest {
@AfterAll
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction( session -> session.createMutationQuery( "delete from BaseEntity" ).executeUpdate() );
}
@Test
public void testInsertBaseEntity(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate(
scope,
BaseEntity.class,
MutationType.INSERT
);
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
inspector.clear();
scope.inTransaction( session -> {
final BaseEntity entity = new BaseEntity();
session.persist( entity );
session.flush();
assertThat( entity.getId() ).isNotNull();
assertThat( entity.getName() ).isEqualTo( "default_name" );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" );
inspector.assertExecutedCount(
delegate != null && delegate.supportsArbitraryValues() ? 1 : 2
);
} );
}
@Test
public void testInsertChildEntity(SessionFactoryScope scope) {
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
inspector.clear();
scope.inTransaction( session -> {
final ChildEntity entity = new ChildEntity();
session.persist( entity );
session.flush();
assertThat( entity.getId() ).isNotNull();
assertThat( entity.getName() ).isEqualTo( "default_name" );
assertThat( entity.getChildName() ).isEqualTo( "default_child_name" );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" );
assertThat( inspector.getSqlQueries().get( 1 ) ).contains( "insert" );
// Note: this is a current restriction, mutation delegates only retrieve generated values
// on the "root" table, and we expect other values to be read through a subsequent select
inspector.assertIsSelect( 2 );
inspector.assertExecutedCount( 3 );
} );
}
@Test
public void testUpdateBaseEntity(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate(
scope,
BaseEntity.class,
MutationType.UPDATE
);
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
final Integer id = scope.fromTransaction( session -> {
final BaseEntity entity = new BaseEntity();
session.persist( entity );
session.flush();
return entity.getId();
} );
inspector.clear();
scope.inTransaction( session -> {
final BaseEntity entity = session.find( BaseEntity.class, id );
entity.setData( "changed" );
session.flush();
assertThat( entity.getUpdateDate() ).isNotNull();
inspector.assertIsSelect( 0 );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "update" );
inspector.assertExecutedCount(
delegate != null && delegate.supportsArbitraryValues() ? 2 : 3
);
} );
}
@Test
public void testUpdateChildEntity(SessionFactoryScope scope) {
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
final Integer id = scope.fromTransaction( session -> {
final ChildEntity entity = new ChildEntity();
session.persist( entity );
session.flush();
return entity.getId();
} );
inspector.clear();
scope.inTransaction( session -> {
final ChildEntity entity = session.find( ChildEntity.class, id );
entity.setData( "changed" );
session.flush();
assertThat( entity.getUpdateDate() ).isNotNull();
assertThat( entity.getChildUpdateDate() ).isNotNull();
inspector.assertIsSelect( 0 );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "update" );
// Note: this is a current restriction, mutation delegates only retrieve generated values
// on the "root" table, and we expect other values to be read through a subsequent select
inspector.assertIsSelect( 2 );
inspector.assertExecutedCount( 3 );
} );
}
private static GeneratedValuesMutationDelegate getDelegate(
SessionFactoryScope scope,
@SuppressWarnings( "SameParameterValue" ) Class<?> entityClass,
MutationType mutationType) {
final EntityPersister entityDescriptor = scope.getSessionFactory()
.getMappingMetamodel()
.findEntityDescriptor( entityClass );
return entityDescriptor.getMutationDelegate( mutationType );
}
@Entity( name = "BaseEntity" )
@Inheritance( strategy = InheritanceType.JOINED )
@SuppressWarnings( "unused" )
public static class BaseEntity {
@Id
@GeneratedValue( strategy = GenerationType.IDENTITY )
private Integer id;
@Generated( event = EventType.INSERT )
@ColumnDefault( "'default_name'" )
private String name;
@UpdateTimestamp( source = SourceType.DB )
private Date updateDate;
@SuppressWarnings( "FieldCanBeLocal" )
private String data;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public Date getUpdateDate() {
return updateDate;
}
public void setData(String data) {
this.data = data;
}
}
@Entity( name = "ChildEntity" )
@SuppressWarnings( "unused" )
public static class ChildEntity extends BaseEntity {
@Generated( event = EventType.INSERT )
@ColumnDefault( "'default_child_name'" )
private String childName;
@UpdateTimestamp( source = SourceType.DB )
private Date childUpdateDate;
public String getChildName() {
return childName;
}
public Date getChildUpdateDate() {
return childUpdateDate;
}
}
}

View File

@ -0,0 +1,283 @@
/*
* 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.orm.test.mapping.generated.delegate;
import java.util.Date;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.RowId;
import org.hibernate.annotations.SourceType;
import org.hibernate.annotations.UpdateTimestamp;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.generator.EventType;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.id.insert.UniqueKeySelectingDelegate;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.model.MutationType;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Generic tests regarding {@link GeneratedValuesMutationDelegate efficient generated values retrieval}.
*
* @author Marco Belladelli
*/
@DomainModel( annotatedClasses = {
MutationDelegateTest.ValuesOnly.class,
MutationDelegateTest.ValuesAndRowId.class,
MutationDelegateTest.ValuesAndNaturalId.class,
} )
@SessionFactory( useCollectingStatementInspector = true )
// Batch size is only enabled to make sure it's ignored when using mutation delegates
@ServiceRegistry( settings = @Setting( name = AvailableSettings.STATEMENT_BATCH_SIZE, value = "5" ) )
public class MutationDelegateTest {
@Test
public void testInsertGeneratedValues(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate( scope, ValuesOnly.class, MutationType.INSERT );
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
inspector.clear();
scope.inTransaction( session -> {
final ValuesOnly entity = new ValuesOnly( 1 );
session.persist( entity );
session.flush();
assertThat( entity.getName() ).isEqualTo( "default_name" );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" );
inspector.assertExecutedCount(
delegate != null && delegate.supportsArbitraryValues() ? 1 : 2
);
} );
}
@Test
public void testUpdateGeneratedValues(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate( scope, ValuesOnly.class, MutationType.UPDATE );
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
scope.inTransaction( session -> {
final ValuesOnly entity = new ValuesOnly( 2 );
session.persist( entity );
} );
inspector.clear();
scope.inTransaction( session -> {
final ValuesOnly entity = session.find( ValuesOnly.class, 2 );
entity.setData( "changed" );
session.flush();
assertThat( entity.getUpdateDate() ).isNotNull();
inspector.assertIsSelect( 0 );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "update" );
inspector.assertExecutedCount(
delegate != null && delegate.supportsArbitraryValues() ? 2 : 3
);
} );
}
@Test
public void testGeneratedValuesAndRowId(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate(
scope,
ValuesAndRowId.class,
MutationType.INSERT
);
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
inspector.clear();
scope.inTransaction( session -> {
final ValuesAndRowId entity = new ValuesAndRowId( 1 );
session.persist( entity );
session.flush();
assertThat( entity.getName() ).isEqualTo( "default_name" );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" );
inspector.assertExecutedCount(
delegate != null && delegate.supportsArbitraryValues() ? 1 : 2
);
final boolean shouldHaveRowId = delegate != null && delegate.supportsRowId()
&& scope.getSessionFactory().getJdbcServices().getDialect().rowId( "" ) != null;
if ( shouldHaveRowId ) {
// assert row-id was populated in entity entry
final PersistenceContext pc = session.getPersistenceContextInternal();
final EntityEntry entry = pc.getEntry( entity );
assertThat( entry.getRowId() ).isNotNull();
}
// test update in same transaction
inspector.clear();
entity.setData( "changed" );
session.flush();
assertThat( entity.getUpdateDate() ).isNotNull();
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "update" );
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "id_column", shouldHaveRowId ? 0 : 1 );
} );
scope.inSession( session -> assertThat( session.find( ValuesAndRowId.class, 1 ).getUpdateDate() ).isNotNull() );
}
@Test
public void testInsertGeneratedValuesAndNaturalId(SessionFactoryScope scope) {
final GeneratedValuesMutationDelegate delegate = getDelegate(
scope,
ValuesAndNaturalId.class,
MutationType.INSERT
);
final SQLStatementInspector inspector = scope.getCollectingStatementInspector();
inspector.clear();
scope.inTransaction( session -> {
final ValuesAndNaturalId entity = new ValuesAndNaturalId( 1, "natural_1" );
session.persist( entity );
session.flush();
assertThat( entity.getName() ).isEqualTo( "default_name" );
assertThat( inspector.getSqlQueries().get( 0 ) ).contains( "insert" );
final boolean isUniqueKeyDelegate = delegate instanceof UniqueKeySelectingDelegate;
inspector.assertExecutedCount(
delegate == null || isUniqueKeyDelegate ? 2 : 1
);
if ( isUniqueKeyDelegate ) {
inspector.assertNumberOfOccurrenceInQueryNoSpace( 1, "data", 1 );
inspector.assertNumberOfOccurrenceInQueryNoSpace( 1, "id_column", 0 );
}
} );
}
private static GeneratedValuesMutationDelegate getDelegate(
SessionFactoryScope scope,
Class<?> entityClass,
MutationType mutationType) {
final EntityPersister entityDescriptor = scope.getSessionFactory()
.getMappingMetamodel()
.findEntityDescriptor( entityClass );
return entityDescriptor.getMutationDelegate( mutationType );
}
@Entity( name = "ValuesOnly" )
@SuppressWarnings( "unused" )
public static class ValuesOnly {
@Id
private Integer id;
@Generated( event = EventType.INSERT )
@ColumnDefault( "'default_name'" )
private String name;
@UpdateTimestamp( source = SourceType.DB )
private Date updateDate;
@SuppressWarnings( "FieldCanBeLocal" )
private String data;
public ValuesOnly() {
}
private ValuesOnly(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public Date getUpdateDate() {
return updateDate;
}
public void setData(String data) {
this.data = data;
}
}
@RowId
@Entity( name = "ValuesAndRowId" )
@SuppressWarnings( "unused" )
public static class ValuesAndRowId {
@Id
@Column( name = "id_column" )
private Integer id;
@Generated( event = EventType.INSERT )
@ColumnDefault( "'default_name'" )
private String name;
@UpdateTimestamp( source = SourceType.DB )
private Date updateDate;
@SuppressWarnings( "FieldCanBeLocal" )
private String data;
public ValuesAndRowId() {
}
private ValuesAndRowId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public Date getUpdateDate() {
return updateDate;
}
public void setData(String data) {
this.data = data;
}
}
@Entity( name = "ValuesAndNaturalId" )
@SuppressWarnings( "unused" )
public static class ValuesAndNaturalId {
@Id
@Column( name = "id_column" )
private Integer id;
@Generated( event = EventType.INSERT )
@ColumnDefault( "'default_name'" )
private String name;
@NaturalId
private String data;
public ValuesAndNaturalId() {
}
private ValuesAndNaturalId(Integer id, String data) {
this.id = id;
this.data = data;
}
public String getName() {
return name;
}
}
}

View File

@ -8,6 +8,9 @@ package org.hibernate.orm.test.rowid;
import org.hibernate.annotations.RowId;
import org.hibernate.dialect.Dialect;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.sql.model.MutationType;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
@ -67,8 +70,8 @@ public class RowIdUpdateAndDeleteTest {
simpleEntity.setStatus( "new_status" );
inspector.clear();
} );
// the update should have used the primary key, as the row-id value is not available
checkUpdateQuery( inspector, true );
// the update should have used the primary key when the row-id value is not available
checkUpdateQuery( inspector, scope, true );
scope.inTransaction( session -> assertThat(
session.find( SimpleEntity.class, 3L ).getStatus()
).isEqualTo( "new_status" ) );
@ -85,8 +88,8 @@ public class RowIdUpdateAndDeleteTest {
session.remove( simpleEntity );
inspector.clear();
} );
// the update should have used the primary key, as the row-id value is not available
checkUpdateQuery( inspector, true );
// the update should have used the primary key when the row-id value is not available
checkUpdateQuery( inspector, scope, true );
scope.inTransaction( session -> assertThat( session.find( SimpleEntity.class, 13L ) ).isNull() );
}
@ -107,8 +110,8 @@ public class RowIdUpdateAndDeleteTest {
parent.getChild().setStatus( "new_status" );
inspector.clear();
} );
// the update should have used the primary key, as the row-id value is not available
checkUpdateQuery( inspector, true );
// the update should have used the primary key when the row-id value is not available
checkUpdateQuery( inspector, scope, true );
scope.inTransaction( session -> assertThat(
session.find( SimpleEntity.class, 4L ).getStatus()
).isEqualTo( "new_status" ) );
@ -122,8 +125,7 @@ public class RowIdUpdateAndDeleteTest {
simpleEntity.setStatus( "new_status" );
inspector.clear();
} );
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
checkUpdateQuery( inspector, dialect.rowId( "" ) == null );
checkUpdateQuery( inspector, scope, false );
scope.inTransaction( session -> assertThat(
session.find( SimpleEntity.class, 1L ).getStatus()
).isEqualTo( "new_status" ) );
@ -137,8 +139,7 @@ public class RowIdUpdateAndDeleteTest {
session.remove( simpleEntity );
inspector.clear();
} );
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
checkUpdateQuery( inspector, dialect.rowId( "" ) == null );
checkUpdateQuery( inspector, scope, false );
scope.inTransaction( session -> assertThat( session.find( SimpleEntity.class, 11L ) ).isNull() );
}
@ -150,14 +151,30 @@ public class RowIdUpdateAndDeleteTest {
parent.getChild().setStatus( "new_status" );
inspector.clear();
} );
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
checkUpdateQuery( inspector, dialect.rowId( "" ) == null );
checkUpdateQuery( inspector, scope, false );
scope.inTransaction( session -> assertThat(
session.find( SimpleEntity.class, 2L ).getStatus()
).isEqualTo( "new_status" ) );
}
private void checkUpdateQuery(SQLStatementInspector inspector, boolean shouldUsePrimaryKey) {
private void checkUpdateQuery(SQLStatementInspector inspector, SessionFactoryScope scope, boolean sameTransaction) {
final Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect();
final boolean shouldUsePrimaryKey;
if ( dialect.rowId( "" ) == null ) {
shouldUsePrimaryKey = true;
}
else {
if ( sameTransaction ) {
final EntityPersister persister = scope.getSessionFactory()
.getMappingMetamodel()
.findEntityDescriptor( SimpleEntity.class );
final GeneratedValuesMutationDelegate delegate = persister.getMutationDelegate( MutationType.INSERT );
shouldUsePrimaryKey = delegate == null || !delegate.supportsRowId();
}
else {
shouldUsePrimaryKey = false;
}
}
inspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "primary_key", shouldUsePrimaryKey ? 1 : 0 );
}