I'm migrating from php/doctrine to java/hibernate. In this case I need that naming strategy in hibernate should be the same as in doctrine.
So, I have implemented custom ImplicitNamingStrategy. I'm using fk_XXX, uniq_XXX, idx_XXX as templates for indexes naming. Foreign keys working fine, but unique keys does not. Seems that hibernate is using old naming strategy for unique keys. I see next messages in the console:
constraint "uk_g8hr207ijpxlwu10pewyo65gv" of relation "language" does not exist, skipping
So, in my strategy such kind of key should be named as uniq_XXX. Is it possible to replace naming of all unique keys to my strategy?
Update:
Constraints declared like this works fine:
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "display_number")})
Constrints declared like this has problems:
@Column(nullable = true, unique = true)
protected Integer displayNumber;
ImplicitNamingStrategyDoctrineImpl.java:
package ru.tvip.support.naming;
import org.hibernate.HibernateException;
import org.hibernate.boot.model.naming.*;
import org.hibernate.boot.model.source.spi.AttributePath;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.util.StringHelper;
import java.io.Serializable;
import java.util.Locale;
/**
* Emulates PHP Doctrine naming strategy.
*/
public class ImplicitNamingStrategyDoctrineImpl implements ImplicitNamingStrategy, Serializable {
@Override
public Identifier determinePrimaryTableName(ImplicitEntityNameSource source) {
if ( source == null ) {
// should never happen, but to be defensive...
throw new HibernateException( "Entity naming information was not provided." );
}
String tableName = transformEntityName( source.getEntityNaming() );
if ( tableName == null ) {
throw new HibernateException( "Could not determine primary table name for entity" );
}
return toIdentifier( tableName, source.getBuildingContext() );
}
@Override
public Identifier determineJoinTableName(ImplicitJoinTableNameSource source) {
final String name = transformEntityName( source.getOwningEntityNaming() )
+ '_'
+ transformEntityName( source.getNonOwningEntityNaming());
return toIdentifier( name, source.getBuildingContext() );
}
@Override
public Identifier determineCollectionTableName(ImplicitCollectionTableNameSource source) {
final String entityName = transformEntityName( source.getOwningEntityNaming() );
final String name = entityName
+ '_'
+ transformAttributePath( source.getOwningAttributePath() );
return toIdentifier( name, source.getBuildingContext() );
}
@Override
public Identifier determineIdentifierColumnName(ImplicitIdentifierColumnNameSource source) {
return toIdentifier(
transformAttributePath( source.getIdentifierAttributePath() ),
source.getBuildingContext()
);
}
@Override
public Identifier determineDiscriminatorColumnName(ImplicitDiscriminatorColumnNameSource source) {
return toIdentifier(
source.getBuildingContext().getMappingDefaults().getImplicitDiscriminatorColumnName(),
source.getBuildingContext()
);
}
@Override
public Identifier determineTenantIdColumnName(ImplicitTenantIdColumnNameSource source) {
return toIdentifier(
source.getBuildingContext().getMappingDefaults().getImplicitTenantIdColumnName(),
source.getBuildingContext()
);
}
@Override
public Identifier determineBasicColumnName(ImplicitBasicColumnNameSource source) {
String name = transformAttributePath( source.getAttributePath() );
name = addUnderscores(name);
return toIdentifier( name, source.getBuildingContext() );
}
@Override
public Identifier determineJoinColumnName(ImplicitJoinColumnNameSource source) {
// JPA states we should use the following as default:
//
// (1) if there is a "referencing relationship property":
// "The concatenation of the following: the name of the referencing relationship
// property or field of the referencing entity or embeddable class; "_"; the
// name of the referenced primary key column."
//
// (2) if there is no such "referencing relationship property", or if the association is
// an element collection:
// "The concatenation of the following: the name of the entity; "_"; the name of the
// referenced primary key column"
// todo : we need to better account for "referencing relationship property"
final String name;
//FIXME: This not handle manytomany relations. Doctrine using attribute name as reference column - Pavel Sokolov
/*
// For debug:
String soruceEntityName=source.getEntityNaming().getEntityName();
String sourceEntityColumn=source.getAttributePath().getProperty();
String referencedColumnName=source.getReferencedColumnName().getText();
ImplicitJoinColumnNameSource.Nature nature = source.getNature();
*/
if ( source.getNature() == ImplicitJoinColumnNameSource.Nature.ELEMENT_COLLECTION
|| source.getAttributePath() == null ) {
name = transformEntityName( source.getEntityNaming() )
+ '_'
+ source.getReferencedColumnName().getText();
}
else {
name = transformAttributePath( source.getAttributePath() )
+ '_'
+ source.getReferencedColumnName().getText();
}
return toIdentifier( name, source.getBuildingContext() );
}
@Override
public Identifier determinePrimaryKeyJoinColumnName(ImplicitPrimaryKeyJoinColumnNameSource source) {
// JPA states we should use the following as default:
// "the same name as the primary key column [of the referenced table]"
return source.getReferencedPrimaryKeyColumnName();
}
@Override
public Identifier determineAnyDiscriminatorColumnName(ImplicitAnyDiscriminatorColumnNameSource source) {
return toIdentifier(
transformAttributePath( source.getAttributePath() ) + "_" + source.getBuildingContext().getMappingDefaults().getImplicitDiscriminatorColumnName(),
source.getBuildingContext()
);
}
@Override
public Identifier determineAnyKeyColumnName(ImplicitAnyKeyColumnNameSource source) {
return toIdentifier(
transformAttributePath( source.getAttributePath() ) + "_" + source.getBuildingContext().getMappingDefaults().getImplicitIdColumnName(),
source.getBuildingContext()
);
}
@Override
public Identifier determineMapKeyColumnName(ImplicitMapKeyColumnNameSource source) {
return toIdentifier(
transformAttributePath( source.getPluralAttributePath() ) + "_KEY",
source.getBuildingContext()
);
}
@Override
public Identifier determineListIndexColumnName(ImplicitIndexColumnNameSource source) {
return toIdentifier(
transformAttributePath( source.getPluralAttributePath() ) + "_ORDER",
source.getBuildingContext()
);
}
@Override
public Identifier determineForeignKeyName(ImplicitForeignKeyNameSource source) {
return toIdentifier(
DoctrineNamingHelper.INSTANCE.generateHashedFkName(
"fk_",
source.getTableName(),
source.getReferencedTableName(),
source.getColumnNames()
),
source.getBuildingContext()
);
}
@Override
public Identifier determineUniqueKeyName(ImplicitUniqueKeyNameSource source) {
return toIdentifier(
DoctrineNamingHelper.INSTANCE.generateHashedConstraintName(
"uniq_",
source.getTableName(),
source.getColumnNames()
),
source.getBuildingContext()
);
}
@Override
public Identifier determineIndexName(ImplicitIndexNameSource source) {
return toIdentifier(
DoctrineNamingHelper.INSTANCE.generateHashedConstraintName(
"idx_",
source.getTableName(),
source.getColumnNames()
),
source.getBuildingContext()
);
}
protected Identifier toIdentifier(String stringForm, MetadataBuildingContext buildingContext) {
return buildingContext.getMetadataCollector()
.getDatabase()
.getJdbcEnvironment()
.getIdentifierHelper()
.toIdentifier( stringForm );
}
private String transformAttributePath(AttributePath attributePath) {
return addUnderscores(attributePath.getProperty());
}
protected String transformEntityName(EntityNaming entityNaming) {
String name=entityNaming.getEntityName();
name = StringHelper.unqualify( name );
name = addUnderscores(name);
return name;
}
protected static String addUnderscores(String name) {
final StringBuilder buf = new StringBuilder( name.replace('.', '_') );
for (int i=1; i<buf.length()-1; i++) {
if (
Character.isLowerCase( buf.charAt(i-1) ) &&
Character.isUpperCase( buf.charAt(i) ) &&
Character.isLowerCase( buf.charAt(i+1) )
) {
buf.insert(i++, '_');
}
}
return buf.toString().toLowerCase(Locale.ROOT);
}
}
DoctrineNamingHelper.java:
import org.hibernate.boot.model.naming.Identifier;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.CRC32;
/**
* Based on:
* Doctrine\DBAL\Schema\Table
* Doctrine\DBAL\Schema\AbstractAsset
*/
public class DoctrineNamingHelper {
public static final DoctrineNamingHelper INSTANCE = new DoctrineNamingHelper();
private static Integer MAX_LENGTH=63;
/*
FK:
$name = $this->_generateIdentifierName(
array_merge((array) $this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength()
);
IDX:
$indexName = $this->_generateIdentifierName(
array_merge(array($this->getName()), $constraint->getColumns()),
"idx",
$this->_getMaxIdentifierLength()
);
$indexName = $this->_generateIdentifierName(
array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength()
);
UNIQ:
$indexName = $this->_generateIdentifierName(
array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength()
);
*/
public String generateHashedFkName( String prefix, Identifier tableName, Identifier referencedTableName, List<Identifier> columnNames){
List<Identifier> parts = new ArrayList<>();
parts.add(tableName);
parts.addAll(columnNames);
return makeHash(prefix, parts, MAX_LENGTH) ;
}
public String generateHashedConstraintName(String prefix, Identifier tableName, List<Identifier> columnNames) {
List<Identifier> parts = new ArrayList<>();
parts.add(tableName);
parts.addAll(columnNames);
return makeHash(prefix,parts, MAX_LENGTH) ;
}
private String makeHash(String prefix, List<Identifier> parts, Integer maxSize){
// Based on Doctrine\DBAL\Schema\AbstractAsset::_generateIdentifierName()
String hash = prefix;
for(Identifier part: parts)
{
CRC32 crc32=new CRC32();
crc32.update(part.getText().getBytes());
String hexed=Integer.toHexString((int)crc32.getValue());
hash += hexed;
}
if (hash.length()>maxSize) {
hash = hash.substring(0, maxSize);
}
return hash;
}
}