Skip to content

Commit 17c6204

Browse files
committed
HHH-8217 Make generated constraint names short and non-random
Conflicts: hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java hibernate-core/src/main/java/org/hibernate/mapping/UniqueKey.java
1 parent 0ea24c6 commit 17c6204

File tree

10 files changed

+329
-55
lines changed

10 files changed

+329
-55
lines changed

hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.Map;
3636
import java.util.Properties;
3737
import java.util.Set;
38+
3839
import javax.persistence.Basic;
3940
import javax.persistence.Cacheable;
4041
import javax.persistence.CollectionTable;
@@ -81,8 +82,6 @@
8182
import javax.persistence.UniqueConstraint;
8283
import javax.persistence.Version;
8384

84-
import org.jboss.logging.Logger;
85-
8685
import org.hibernate.AnnotationException;
8786
import org.hibernate.AssertionFailure;
8887
import org.hibernate.EntityMode;
@@ -155,9 +154,9 @@
155154
import org.hibernate.id.TableHiLoGenerator;
156155
import org.hibernate.id.enhanced.SequenceStyleGenerator;
157156
import org.hibernate.internal.CoreMessageLogger;
158-
import org.hibernate.internal.util.StringHelper;
159157
import org.hibernate.mapping.Any;
160158
import org.hibernate.mapping.Component;
159+
import org.hibernate.mapping.Constraint;
161160
import org.hibernate.mapping.DependantValue;
162161
import org.hibernate.mapping.IdGenerator;
163162
import org.hibernate.mapping.Join;
@@ -171,6 +170,7 @@
171170
import org.hibernate.mapping.Subclass;
172171
import org.hibernate.mapping.ToOne;
173172
import org.hibernate.mapping.UnionSubclass;
173+
import org.jboss.logging.Logger;
174174

175175
/**
176176
* JSR 175 annotation binder which reads the annotations from classes, applies the
@@ -2093,16 +2093,22 @@ else if ( !isId || !entityBinder.isIgnoreIdAnnotations() ) {
20932093
}
20942094
}
20952095

2096+
// Natural ID columns must reside in one single UniqueKey within the Table.
2097+
// For now, simply ensure consistent naming.
2098+
// TODO: AFAIK, there really isn't a reason for these UKs to be created
2099+
// on the secondPass. This whole area should go away...
20962100
NaturalId naturalIdAnn = property.getAnnotation( NaturalId.class );
20972101
if ( naturalIdAnn != null ) {
20982102
if ( joinColumns != null ) {
20992103
for ( Ejb3Column column : joinColumns ) {
2100-
column.addUniqueKey( column.getTable().getNaturalIdUniqueKeyName(), inSecondPass );
2104+
String keyName = "UK_" + Constraint.hashedName( column.getTable().getName() + "_NaturalID" );
2105+
column.addUniqueKey( keyName, inSecondPass );
21012106
}
21022107
}
21032108
else {
21042109
for ( Ejb3Column column : columns ) {
2105-
column.addUniqueKey( column.getTable().getNaturalIdUniqueKeyName(), inSecondPass );
2110+
String keyName = "UK_" + Constraint.hashedName( column.getTable().getName() + "_NaturalID" );
2111+
column.addUniqueKey( keyName, inSecondPass );
21062112
}
21072113
}
21082114
}

hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
import org.hibernate.mapping.AuxiliaryDatabaseObject;
114114
import org.hibernate.mapping.Collection;
115115
import org.hibernate.mapping.Column;
116+
import org.hibernate.mapping.Constraint;
116117
import org.hibernate.mapping.DenormalizedTable;
117118
import org.hibernate.mapping.FetchProfile;
118119
import org.hibernate.mapping.ForeignKey;
@@ -1391,10 +1392,7 @@ protected void secondPassCompile() throws MappingException {
13911392
final Table table = tableListEntry.getKey();
13921393
final List<UniqueConstraintHolder> uniqueConstraints = tableListEntry.getValue();
13931394
for ( UniqueConstraintHolder holder : uniqueConstraints ) {
1394-
final String keyName = StringHelper.isEmpty( holder.getName() )
1395-
? StringHelper.randomFixedLengthHex("UK_")
1396-
: holder.getName();
1397-
buildUniqueKeyFromColumnNames( table, keyName, holder.getColumns() );
1395+
buildUniqueKeyFromColumnNames( table, holder.getName(), holder.getColumns() );
13981396
}
13991397
}
14001398

@@ -1553,8 +1551,6 @@ private void processEndOfQueue(List<FkSecondPass> endOfQueueFkSecondPasses) {
15531551
}
15541552

15551553
private void buildUniqueKeyFromColumnNames(Table table, String keyName, String[] columnNames) {
1556-
keyName = normalizer.normalizeIdentifierQuoting( keyName );
1557-
15581554
int size = columnNames.length;
15591555
Column[] columns = new Column[size];
15601556
Set<Column> unbound = new HashSet<Column>();
@@ -1571,6 +1567,12 @@ private void buildUniqueKeyFromColumnNames(Table table, String keyName, String[]
15711567
unboundNoLogical.add( new Column( column ) );
15721568
}
15731569
}
1570+
1571+
if ( StringHelper.isEmpty( keyName ) ) {
1572+
keyName = Constraint.generateName( "UK_", table, columns );
1573+
}
1574+
keyName = normalizer.normalizeIdentifierQuoting( keyName );
1575+
15741576
UniqueKey uk = table.getOrCreateUniqueKey( keyName );
15751577
for ( Column column : columns ) {
15761578
if ( table.containsColumn( column ) ) {

hibernate-core/src/main/java/org/hibernate/cfg/HbmBinder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import org.hibernate.mapping.Collection;
6060
import org.hibernate.mapping.Column;
6161
import org.hibernate.mapping.Component;
62+
import org.hibernate.mapping.Constraint;
6263
import org.hibernate.mapping.DependantValue;
6364
import org.hibernate.mapping.FetchProfile;
6465
import org.hibernate.mapping.Fetchable;
@@ -2246,7 +2247,6 @@ else if ( "filter".equals( name ) ) {
22462247
}
22472248
else if ( "natural-id".equals( name ) ) {
22482249
UniqueKey uk = new UniqueKey();
2249-
uk.setName(StringHelper.randomFixedLengthHex("UK_"));
22502250
uk.setTable(table);
22512251
//by default, natural-ids are "immutable" (constant)
22522252
boolean mutableId = "true".equals( subnode.attributeValue("mutable") );
@@ -2260,6 +2260,8 @@ else if ( "natural-id".equals( name ) ) {
22602260
false,
22612261
true
22622262
);
2263+
uk.setName( Constraint.generateName( uk.generatedConstraintNamePrefix(),
2264+
table, uk.getColumns() ) );
22632265
table.addUniqueKey(uk);
22642266
}
22652267
else if ( "query".equals(name) ) {

hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import java.util.BitSet;
3131
import java.util.Iterator;
3232
import java.util.StringTokenizer;
33-
import java.util.UUID;
3433

3534
import org.hibernate.dialect.Dialect;
3635
import org.hibernate.internal.util.collections.ArrayHelper;
@@ -762,18 +761,4 @@ public static String expandBatchIdPlaceholder(
762761
public static String[] toArrayElement(String s) {
763762
return ( s == null || s.length() == 0 ) ? new String[0] : new String[] { s };
764763
}
765-
766-
// Oracle restricts identifier lengths to 30. Rather than tie this to
767-
// Dialect, simply restrict randomly-generated constrain names across
768-
// the board.
769-
private static final int MAX_NAME_LENGTH = 30;
770-
public static String randomFixedLengthHex(String prefix) {
771-
int length = MAX_NAME_LENGTH - prefix.length();
772-
String s = UUID.randomUUID().toString();
773-
s = s.replace( "-", "" );
774-
if (s.length() > length) {
775-
s = s.substring( 0, length );
776-
}
777-
return prefix + s;
778-
}
779764
}

hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,29 @@
2323
*/
2424
package org.hibernate.mapping;
2525
import java.io.Serializable;
26+
import java.math.BigInteger;
27+
import java.security.MessageDigest;
28+
import java.security.NoSuchAlgorithmException;
2629
import java.util.ArrayList;
30+
import java.util.Arrays;
31+
import java.util.Comparator;
2732
import java.util.Iterator;
2833
import java.util.List;
2934

35+
import org.hibernate.HibernateException;
3036
import org.hibernate.dialect.Dialect;
3137
import org.hibernate.engine.spi.Mapping;
3238

3339
/**
3440
* A relational constraint.
3541
*
3642
* @author Gavin King
43+
* @author Brett Meyer
3744
*/
3845
public abstract class Constraint implements RelationalModel, Serializable {
3946

4047
private String name;
41-
private final List columns = new ArrayList();
48+
private final ArrayList columns = new ArrayList();
4249
private Table table;
4350

4451
public String getName() {
@@ -48,6 +55,85 @@ public String getName() {
4855
public void setName(String name) {
4956
this.name = name;
5057
}
58+
59+
/**
60+
* If a constraint is not explicitly named, this is called to generate
61+
* a unique hash using the table and column names.
62+
* Static so the name can be generated prior to creating the Constraint.
63+
* They're cached, keyed by name, in multiple locations.
64+
*
65+
* @param prefix
66+
* Appended to the beginning of the generated name
67+
* @param table
68+
* @param columns
69+
* @return String The generated name
70+
*/
71+
public static String generateName(String prefix, Table table, Column... columns) {
72+
// Use a concatenation that guarantees uniqueness, even if identical names
73+
// exist between all table and column identifiers.
74+
75+
StringBuilder sb = new StringBuilder( "table`" + table.getName() + "`" );
76+
77+
// Ensure a consistent ordering of columns, regardless of the order
78+
// they were bound.
79+
// Clone the list, as sometimes a set of order-dependent Column
80+
// bindings are given.
81+
Column[] alphabeticalColumns = columns.clone();
82+
Arrays.sort( alphabeticalColumns, ColumnComparator.INSTANCE );
83+
for ( Column column : alphabeticalColumns ) {
84+
String columnName = column == null ? "" : column.getName();
85+
sb.append( "column`" + columnName + "`" );
86+
}
87+
return prefix + hashedName( sb.toString() );
88+
}
89+
90+
/**
91+
* Helper method for {@link #generateName(String, Table, Column...)}.
92+
*
93+
* @param prefix
94+
* Appended to the beginning of the generated name
95+
* @param table
96+
* @param columns
97+
* @return String The generated name
98+
*/
99+
public static String generateName(String prefix, Table table, List<Column> columns) {
100+
return generateName( prefix, table, columns.toArray( new Column[columns.size()] ) );
101+
}
102+
103+
/**
104+
* Hash a constraint name using MD5. Convert the MD5 digest to base 35
105+
* (full alphanumeric), guaranteeing
106+
* that the length of the name will always be smaller than the 30
107+
* character identifier restriction enforced by a few dialects.
108+
*
109+
* @param s
110+
* The name to be hashed.
111+
* @return String The hased name.
112+
*/
113+
public static String hashedName(String s) {
114+
try {
115+
MessageDigest md = MessageDigest.getInstance( "MD5" );
116+
md.reset();
117+
md.update( s.getBytes() );
118+
byte[] digest = md.digest();
119+
BigInteger bigInt = new BigInteger( 1, digest );
120+
// By converting to base 35 (full alphanumeric), we guarantee
121+
// that the length of the name will always be smaller than the 30
122+
// character identifier restriction enforced by a few dialects.
123+
return bigInt.toString( 35 );
124+
}
125+
catch ( NoSuchAlgorithmException e ) {
126+
throw new HibernateException( "Unable to generate a hashed Constraint name!", e );
127+
}
128+
}
129+
130+
private static class ColumnComparator implements Comparator<Column> {
131+
public static ColumnComparator INSTANCE = new ColumnComparator();
132+
133+
public int compare(Column col1, Column col2) {
134+
return col1.getName().compareTo( col2.getName() );
135+
}
136+
}
51137

52138
public Iterator getColumnIterator() {
53139
return columns.iterator();
@@ -133,4 +219,10 @@ public abstract String sqlConstraintString(Dialect d, String constraintName, Str
133219
public String toString() {
134220
return getClass().getName() + '(' + getTable().getName() + getColumns() + ") as " + name;
135221
}
222+
223+
/**
224+
* @return String The prefix to use in generated constraint names. Examples:
225+
* "UK_", "FK_", and "PK_".
226+
*/
227+
public abstract String generatedConstraintNamePrefix();
136228
}

hibernate-core/src/main/java/org/hibernate/mapping/ForeignKey.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,8 @@ public String toString() {
180180
}
181181

182182
}
183+
184+
public String generatedConstraintNamePrefix() {
185+
return "FK_";
186+
}
183187
}

hibernate-core/src/main/java/org/hibernate/mapping/PrimaryKey.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,8 @@ public String sqlConstraintString(Dialect dialect, String constraintName, String
5353
}
5454
return buf.append(')').toString();
5555
}
56+
57+
public String generatedConstraintNamePrefix() {
58+
return "PK_";
59+
}
5660
}

hibernate-core/src/main/java/org/hibernate/mapping/Table.java

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.hibernate.MappingException;
3737
import org.hibernate.dialect.Dialect;
3838
import org.hibernate.engine.spi.Mapping;
39-
import org.hibernate.internal.util.StringHelper;
4039
import org.hibernate.tool.hbm2ddl.ColumnMetadata;
4140
import org.hibernate.tool.hbm2ddl.TableMetadata;
4241

@@ -70,15 +69,6 @@ public class Table implements RelationalModel, Serializable {
7069
private boolean hasDenormalizedTables = false;
7170
private String comment;
7271

73-
/**
74-
* Natural ID columns must reside in one single UniqueKey within the Table.
75-
* To prevent separate UniqueKeys from being created, this keeps track of
76-
* a sole name used for all of them. It's necessary since
77-
* AnnotationBinder#processElementAnnotations (static) creates the
78-
* UniqueKeys on a second pass using randomly-generated names.
79-
*/
80-
private final String naturalIdUniqueKeyName = StringHelper.randomFixedLengthHex( "UK_" );
81-
8272
static class ForeignKeyKey implements Serializable {
8373
String referencedClassName;
8474
List columns;
@@ -431,8 +421,8 @@ public Iterator sqlAlterStrings(Dialect dialect, Mapping p, TableMetadata tableI
431421
}
432422

433423
if ( column.isUnique() ) {
434-
UniqueKey uk = getOrCreateUniqueKey(
435-
StringHelper.randomFixedLengthHex("UK_"));
424+
String keyName = Constraint.generateName( "UK_", this, column );
425+
UniqueKey uk = getOrCreateUniqueKey( keyName );
436426
uk.addColumn( column );
437427
alter.append( dialect.getUniqueDelegate()
438428
.applyUniqueToColumn( column ) );
@@ -534,8 +524,8 @@ public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog,
534524
}
535525

536526
if ( col.isUnique() ) {
537-
UniqueKey uk = getOrCreateUniqueKey(
538-
StringHelper.randomFixedLengthHex("UK_"));
527+
String keyName = Constraint.generateName( "UK_", this, col );
528+
UniqueKey uk = getOrCreateUniqueKey( keyName );
539529
uk.addColumn( col );
540530
buf.append( dialect.getUniqueDelegate()
541531
.applyUniqueToColumn( col ) );
@@ -631,7 +621,7 @@ public UniqueKey addUniqueKey(UniqueKey uniqueKey) {
631621
}
632622

633623
public UniqueKey createUniqueKey(List keyColumns) {
634-
String keyName = StringHelper.randomFixedLengthHex("UK_");
624+
String keyName = Constraint.generateName( "UK_", this, keyColumns );
635625
UniqueKey uk = getOrCreateUniqueKey( keyName );
636626
uk.addColumns( keyColumns.iterator() );
637627
return uk;
@@ -667,19 +657,22 @@ public ForeignKey createForeignKey(String keyName, List keyColumns, String refer
667657
ForeignKey fk = (ForeignKey) foreignKeys.get( key );
668658
if ( fk == null ) {
669659
fk = new ForeignKey();
670-
if ( keyName != null ) {
671-
fk.setName( keyName );
672-
}
673-
else {
674-
fk.setName( StringHelper.randomFixedLengthHex("FK_") );
675-
}
676660
fk.setTable( this );
677-
foreignKeys.put( key, fk );
678661
fk.setReferencedEntityName( referencedEntityName );
679662
fk.addColumns( keyColumns.iterator() );
680663
if ( referencedColumns != null ) {
681664
fk.addReferencedColumns( referencedColumns.iterator() );
682665
}
666+
667+
if ( keyName != null ) {
668+
fk.setName( keyName );
669+
}
670+
else {
671+
fk.setName( Constraint.generateName( fk.generatedConstraintNamePrefix(),
672+
this, keyColumns ) );
673+
}
674+
675+
foreignKeys.put( key, fk );
683676
}
684677

685678
if ( keyName != null ) {
@@ -826,10 +819,6 @@ public void setComment(String comment) {
826819
public Iterator getCheckConstraintsIterator() {
827820
return checkConstraints.iterator();
828821
}
829-
830-
public String getNaturalIdUniqueKeyName() {
831-
return naturalIdUniqueKeyName;
832-
}
833822

834823
public Iterator sqlCommentStrings(Dialect dialect, String defaultCatalog, String defaultSchema) {
835824
List comments = new ArrayList();

0 commit comments

Comments
 (0)