From 0baefce734bed664a20f994ad35b0e0017aca770 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Tue, 11 Oct 2022 14:58:33 +0200 Subject: [PATCH] HHH-15561 - Fixed and added test for issue Signed-off-by: Jan Schatteman --- .../community/dialect/H2LegacyDialect.java | 3 +- .../java/org/hibernate/dialect/H2Dialect.java | 3 +- .../H2FinalTableIdentityColumnSupport.java | 29 +++++ .../identity/H2IdentityColumnSupport.java | 6 + .../identity/IdentityColumnSupport.java | 17 +++ .../id/insert/InsertSelectIdentityInsert.java | 10 +- .../orm/test/id/IdentityIdEntityTest.java | 112 ++++++++++++++++++ 7 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/identity/H2FinalTableIdentityColumnSupport.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/id/IdentityIdEntityTest.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java index 5117c242a0..e27e26b091 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java @@ -28,6 +28,7 @@ import org.hibernate.dialect.SimpleDatabaseVersion; import org.hibernate.dialect.TimeZoneSupport; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.hint.IndexQueryHintHandler; +import org.hibernate.dialect.identity.H2FinalTableIdentityColumnSupport; import org.hibernate.dialect.identity.H2IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; @@ -774,7 +775,7 @@ public class H2LegacyDialect extends Dialect { @Override public IdentityColumnSupport getIdentityColumnSupport() { - return new H2IdentityColumnSupport(); + return getVersion().isSameOrAfter( 2 ) ? H2FinalTableIdentityColumnSupport.INSTANCE : H2IdentityColumnSupport.INSTANCE; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index 6087f899a8..8b8d19269c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -20,6 +20,7 @@ import org.hibernate.PessimisticLockException; import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.hint.IndexQueryHintHandler; +import org.hibernate.dialect.identity.H2FinalTableIdentityColumnSupport; import org.hibernate.dialect.identity.H2IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; @@ -741,7 +742,7 @@ public class H2Dialect extends Dialect { @Override public IdentityColumnSupport getIdentityColumnSupport() { - return new H2IdentityColumnSupport(); + return getVersion().isSameOrAfter( 2 ) ? H2FinalTableIdentityColumnSupport.INSTANCE : H2IdentityColumnSupport.INSTANCE; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2FinalTableIdentityColumnSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2FinalTableIdentityColumnSupport.java new file mode 100644 index 0000000000..3f93ae7386 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2FinalTableIdentityColumnSupport.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * Identity column support for H2 2+ versions + * @author Jan Schatteman + */ +public class H2FinalTableIdentityColumnSupport extends H2IdentityColumnSupport { + + public static final H2FinalTableIdentityColumnSupport INSTANCE = new H2FinalTableIdentityColumnSupport(); + + private H2FinalTableIdentityColumnSupport() { + } + + @Override + public boolean supportsInsertSelectIdentity() { + return true; + } + + @Override + public String appendIdentitySelectToInsert(String identityColumnName, String insertString) { + return "select " + identityColumnName + " from final table ( " + insertString + " )"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java index 8ea8827a6c..35bcf5d32b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/H2IdentityColumnSupport.java @@ -10,6 +10,12 @@ package org.hibernate.dialect.identity; * @author Andrea Boriero */ public class H2IdentityColumnSupport extends IdentityColumnSupportImpl { + + public static final H2IdentityColumnSupport INSTANCE = new H2IdentityColumnSupport(); + + protected H2IdentityColumnSupport() { + } + @Override public boolean supportsIdentityColumns() { return true; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java index 56753c5390..d229b5c475 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/identity/IdentityColumnSupport.java @@ -57,6 +57,23 @@ public interface IdentityColumnSupport { */ String appendIdentitySelectToInsert(String insertString); + /** + * Provided we {@link #supportsInsertSelectIdentity}, then attach the + * "select identity" clause to the insert statement. + *

+ * Note, if {@link #supportsInsertSelectIdentity} == false then + * the insert-string should be returned without modification. + * + * @param identityColumnName The name of the identity column + * @param insertString The insert command + * + * @return The insert command with any necessary identity select + * clause attached. + */ + default String appendIdentitySelectToInsert(String identityColumnName, String insertString) { + return appendIdentitySelectToInsert( insertString ); + } + /** * Get the select command to use to retrieve the last generated IDENTITY * value for a particular table diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/InsertSelectIdentityInsert.java b/hibernate-core/src/main/java/org/hibernate/id/insert/InsertSelectIdentityInsert.java index e130e19704..d97c919914 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/InsertSelectIdentityInsert.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/InsertSelectIdentityInsert.java @@ -6,6 +6,7 @@ */ package org.hibernate.id.insert; import org.hibernate.dialect.Dialect; +import org.hibernate.sql.Insert; /** * Specialized IdentifierGeneratingInsert which appends the database @@ -15,11 +16,18 @@ import org.hibernate.dialect.Dialect; * @author Steve Ebersole */ public class InsertSelectIdentityInsert extends IdentifierGeneratingInsert { + protected String identityColumnName; + + public Insert addIdentityColumn(String columnName) { + identityColumnName = columnName; + return super.addIdentityColumn( columnName ); + } + public InsertSelectIdentityInsert(Dialect dialect) { super( dialect ); } public String toStatementString() { - return getDialect().getIdentityColumnSupport().appendIdentitySelectToInsert( super.toStatementString() ); + return getDialect().getIdentityColumnSupport().appendIdentitySelectToInsert( identityColumnName, super.toStatementString() ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/IdentityIdEntityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/IdentityIdEntityTest.java new file mode 100644 index 0000000000..f189328157 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/IdentityIdEntityTest.java @@ -0,0 +1,112 @@ +/* + * 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.id; + +import java.util.Date; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialect; +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.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Jan Schatteman + */ +@TestForIssue(jiraKey = "HHH-15561") +@RequiresDialect( value = H2Dialect.class ) +public class IdentityIdEntityTest { + + @AfterEach + public void cleanup(SessionFactoryScope scope) { + scope.inTransaction( + session -> session.createMutationQuery( "delete from id_entity" ).executeUpdate() + ); + } + + @Test + @ServiceRegistry( + settings = { @Setting( name = AvailableSettings.USE_GET_GENERATED_KEYS, value = "false") } + ) + @DomainModel( annotatedClasses = { IdentityEntity.class } ) + @SessionFactory + public void testIdentityEntityWithDisabledGetGeneratedKeys(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + try { + IdentityEntity ie = new IdentityEntity(); + ie.setTimestamp( new Date() ); + session.persist( ie ); + } + catch (Exception e) { + fail( "Creation of an IDENTITY-id-based entity failed when \"hibernate.jdbc.use_get_generated_keys\" was set to false (" + e.getMessage() + ")" ); + } + } + ); + } + + @Test + @ServiceRegistry( + settings = { @Setting( name = "use_jdbc_metadata_defaults", value = "false") } + ) + @DomainModel( annotatedClasses = { IdentityEntity.class } ) + @SessionFactory + public void testIdentityEntityWithDisabledJdbcMetadataDefaults(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + try { + IdentityEntity ie = new IdentityEntity(); + ie.setTimestamp( new Date() ); + session.persist( ie ); + } + catch (Exception e) { + fail( "Creation of an IDENTITY-id-based entity failed when \"use_jdbc_metadata_defaults\" was set to false (" + e.getMessage() + ")" ); + } + } + ); + } + + @Entity(name = "id_entity") + public static class IdentityEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + private Date timestamp; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } + +}