ImprovedNamingStrategy does not work with Hibernate 5

Problem description

When you are trying to use the ImprovedNamingStrategy with Hibernate 5, it seems not to work. It does not resolve to underscore separated names, and because of that you get errors like this:

javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory
...
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [QuestionCategory]
@Entity
public class QuestionCategory extends IdentifiableEntity {

    private String name;

    // ...
}

It is looking for a table named “QuestionCategory”, but according to the ImprovedNamingStrategy it should be looking for “question_category”.

Why does it no longer work?

In Hibernate 4 you could use the following property to set the naming strategy that would map to names that only use lowercase letters and underscores as word separators:

jpaProperties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy");

This naming strategy is pretty commonly used, because many developers like to use these kinds of names for the tables, columns etc. of their databases.

In Hibernate 5 however, the above mentioned hibernate.ejb.naming_strategy property is no longer available, because it has been split into two new properties to allow for deeper customization of the naming strategy.

What changed in Hibernate 5

Now, instead of one, there are two properties:

hibernate.implicit_naming_strategy
hibernate.physical_naming_strategy

Hibernate looks at name resolution as a 2 stage process:

  • When an entity does not explicitly name the database table that it maps to, we need to implicitly determine that table name. Or when a particular attribute does not explicitly name the database column that it maps to, we need to implicitly determine that column name. The first property is for defining this. 
  • Many organizations define rules around the naming of database objects (tables, columns, foreign-keys, etc). The idea of a PhysicalNamingStrategy is to help implement such naming rules without having to hard-code them into the mapping via explicit names. The second property is for this.

Solution

To resolve the issue and mimic the usage of ImprovedNamingStrategy, you can leave the implicit naming strategy as the default.

However, you need to create a custom physical naming strategy, because there is none that works the way we need it.

Here is how it’ll look like:

import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

import java.util.Locale;

public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardImpl {


    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        return new Identifier(addUnderscores(name.getText()), name.isQuoted());
    }

    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
        return new Identifier(addUnderscores(name.getText()), name.isQuoted());
    }


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

You need to set this up by specifying the corresponding property:

jpaProperties.put("hibernate.physical_naming_strategy", "com.test.persistence.config.PhysicalNamingStrategyImpl");

Sources

  • https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/domain/naming.html
  • http://stackoverflow.com/questions/32437202/improvednamingstrategy-no-longer-working-in-hibernate-5