3

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;
    }

}
  • how have you declared the use of ImplicitNamingStrategyDoctrineImpl ? Could you show the configuration ? – Thierry Jul 06 '16 at 09:43
  • of cause, I have added to application.properties (spring boot) `spring.jpa.hibernate.naming.implicit-strategy=ru.tvip.support.naming.ImplicitNamingStrategyDoctrineImpl` I'm using spring boot 1.4.0.RC1, so it should know about implicit-strategy config. – Pavel Sokolov Jul 06 '16 at 10:06
  • I'm no expert of spring boot + hibernate config. Have a look at : http://stackoverflow.com/questions/37062675/hibernate-5-1-x-naming-strategy-backward-compatible-with-hibernate-4-x . The key used there is `spring.jpa.properties.hibernate.naming.implicit-strategy` (yours is missing the `.properties.` part). Could it be that ? – Thierry Jul 06 '16 at 12:08
  • You need to add .properties only in spring boot < 1.4 – Pavel Sokolov Jul 07 '16 at 12:15

1 Answers1

1

A bit late to the game on this one. I am seeing the issue with 5.4.6.Final; seems like it is still an open issue:

boot-and-bonnet
  • 731
  • 2
  • 5
  • 16