From b431029bffbda5764386ce204c41f2e52f6341e8 Mon Sep 17 00:00:00 2001 From: The-Huginn Date: Mon, 28 Aug 2023 14:24:29 +0200 Subject: [PATCH] [HHH-17065] Unique Index on PrimaryKey orders primary key columns. --- .../userguide/chapters/schema/Schema.adoc | 2 ++ .../ColumnOrderingStrategyStandard.java | 12 +++++++++ .../hibernate/mapping/PersistentClass.java | 4 +-- .../org/hibernate/mapping/PrimaryKey.java | 16 +++++++++--- .../java/org/hibernate/mapping/Table.java | 25 ++++++++++++++++--- .../CompositePrimaryKeyColumnOrderTest.java | 5 ++++ 6 files changed, 55 insertions(+), 9 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc b/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc index 184972fe031b..5749faddbf3c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/schema/Schema.adoc @@ -211,6 +211,8 @@ The second INSERT statement fails because of the unique constraint violation. The {jpaJavadocUrlPrefix}Index.html[`@Index`] annotation is used by the automated schema generation tool to create a database index. +TIP: Creating unique index containing all primary key columns will result in ordering primary key columns specified by `columnList` + Considering the following entity mapping. Hibernate generates the index when creating the database schema: [[schema-generation-columns-index-mapping-example]] diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/ColumnOrderingStrategyStandard.java b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/ColumnOrderingStrategyStandard.java index e85f5be4dbb7..7a202751542a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/relational/ColumnOrderingStrategyStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/relational/ColumnOrderingStrategyStandard.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -17,7 +18,9 @@ import org.hibernate.engine.jdbc.Size; import org.hibernate.mapping.Column; import org.hibernate.mapping.Constraint; +import org.hibernate.mapping.PrimaryKey; import org.hibernate.mapping.Table; +import org.hibernate.mapping.UniqueKey; import org.hibernate.mapping.UserDefinedType; import static java.lang.Math.log; @@ -45,6 +48,15 @@ public List orderUserDefinedTypeColumns(UserDefinedType userDefinedType, @Override public List orderConstraintColumns(Constraint constraint, Metadata metadata) { + // We try to find uniqueKey constraint containing only primary key. + // This uniqueKey then orders primaryKey columns. Otherwise, order as usual. + if ( constraint instanceof PrimaryKey ) { + UniqueKey uniqueKey = ((PrimaryKey) constraint).getOrderingUniqueKey(); + if ( uniqueKey != null ) { + return uniqueKey.getColumns(); + } + } + return orderColumns( constraint.getColumns(), metadata ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java index 9582697c0d8f..0008092a4bab 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java @@ -424,9 +424,9 @@ public void createPrimaryKey() { final Table table = getTable(); final PrimaryKey pk = new PrimaryKey( table ); pk.setName( PK_ALIAS.toAliasString( table.getName() ) ); - table.setPrimaryKey( pk ); - pk.addColumns( getKey() ); + + table.setPrimaryKey( pk ); } public abstract String getWhere(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java index cfb0b3999b89..56df2a8c4d5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java @@ -25,6 +25,7 @@ public class PrimaryKey extends Constraint { private static final Logger log = Logger.getLogger( PrimaryKey.class ); + private UniqueKey orderingUniqueKey = null; private int[] originalOrder; public PrimaryKey(Table table){ @@ -117,6 +118,14 @@ public List getColumnsInOriginalOrder() { return Arrays.asList( columnsInOriginalOrder ); } + public void setOrderingUniqueKey(UniqueKey uniqueKey) { + this.orderingUniqueKey = uniqueKey; + } + + public UniqueKey getOrderingUniqueKey() { + return this.orderingUniqueKey; + } + @Internal public void reorderColumns(List reorderedColumns) { if ( originalOrder != null ) { @@ -126,12 +135,13 @@ public void reorderColumns(List reorderedColumns) { assert getColumns().size() == reorderedColumns.size() && getColumns().containsAll( reorderedColumns ); final List columns = getColumns(); originalOrder = new int[columns.size()]; - for ( int i = 0; i < reorderedColumns.size(); i++ ) { - final Column reorderedColumn = reorderedColumns.get( i ); + List newColumns = getOrderingUniqueKey() != null ? getOrderingUniqueKey().getColumns() : reorderedColumns; + for ( int i = 0; i < newColumns.size(); i++ ) { + final Column reorderedColumn = newColumns.get( i ); originalOrder[i] = columns.indexOf( reorderedColumn ); } columns.clear(); - columns.addAll( reorderedColumns ); + columns.addAll( newColumns ); } @Internal diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index 7a561db2c3ff..83ccd8ba4e4a 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -357,10 +357,10 @@ private void cleanseUniqueKeyMap() { // any sharing the same columns as other defined unique keys; this is needed for the annotation // processor since it creates unique constraints automagically for the user // 2) Remove any unique keys that share the same columns as the primary key; again, this is - // needed for the annotation processor to handle @Id @OneToOne cases. In such cases the - // unique key is unnecessary because a primary key is already unique by definition. We handle + // needed for the annotation processor to handle @Id @OneToOne cases. In such cases we handle // this case specifically because some databases fail if you try to apply a unique key to - // the primary key columns which causes schema export to fail in these cases. + // the primary key columns which causes schema export to fail in these cases. Furthermore, we + // pass the unique key to a primary key for reordering columns specified by the unique key. if ( !uniqueKeys.isEmpty() ) { if ( uniqueKeys.size() == 1 ) { // we have to worry about condition 2 above, but not condition 1 @@ -395,6 +395,7 @@ private void cleanseUniqueKeyMap() { // condition 2 : check against pk if ( !removeIt && isSameAsPrimaryKeyColumns( uniqueKeyEntry.getValue() ) ) { + primaryKey.setOrderingUniqueKey(uniqueKeyEntry.getValue()); removeIt = true; } @@ -413,7 +414,7 @@ private boolean isSameAsPrimaryKeyColumns(UniqueKey uniqueKey) { return false; } return primaryKey.getColumns().containsAll( uniqueKey.getColumns() ) - && uniqueKey.getColumns().containsAll( primaryKey.getColumns() ); + && primaryKey.getColumns().size() == uniqueKey.getColumns().size(); } @Override @@ -471,6 +472,7 @@ public PrimaryKey getPrimaryKey() { public void setPrimaryKey(PrimaryKey primaryKey) { this.primaryKey = primaryKey; + checkPrimaryKeyUniqueKey(); } public Index getOrCreateIndex(String indexName) { @@ -580,6 +582,21 @@ public ForeignKey createForeignKey( return foreignKey; } + /** + * Checks for uniqueKey containing only whole primary key and sets + * order of the columns accordingly + */ + private void checkPrimaryKeyUniqueKey() { + final Iterator> uniqueKeyEntries = uniqueKeys.entrySet().iterator(); + while ( uniqueKeyEntries.hasNext() ) { + final Map.Entry uniqueKeyEntry = uniqueKeyEntries.next(); + + if ( isSameAsPrimaryKeyColumns( uniqueKeyEntry.getValue() ) ) { + primaryKey.setOrderingUniqueKey(uniqueKeyEntry.getValue()); + uniqueKeyEntries.remove(); + } + } + } // This must be done outside of Table, rather than statically, to ensure // deterministic alias names. See HHH-2448. diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/uniqueconstraint/CompositePrimaryKeyColumnOrderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/uniqueconstraint/CompositePrimaryKeyColumnOrderTest.java index 1b2f287dfdb2..00d2a3b36f88 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/uniqueconstraint/CompositePrimaryKeyColumnOrderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/uniqueconstraint/CompositePrimaryKeyColumnOrderTest.java @@ -14,6 +14,8 @@ import java.util.Map; import java.util.Objects; +import jakarta.persistence.Index; +import jakarta.persistence.Table; import org.hibernate.boot.Metadata; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.H2Dialect; @@ -71,6 +73,9 @@ public List getManagedClassNames() { } @Entity + @Table( + indexes = @Index(unique = true, columnList = "b, a") + ) @IdClass( CompositePrimaryKey.class ) static class TestEntity {