ImprovedNamingStrategy no longer working in Hibernate 5

42,439

Solution 1

Just figured out the issue, the configuration is absolutely fine when using a Hibernate version < 5.0 but not for Hibernate >= 5.0.

I was using Hibernate 5.0.0.Final with Spring 4.2.0.RELEASE. I guess the Hibernate 5 is not fully compatible with Spring 4.2 . I just downgraded Hibernate to 4.2.1.Final and things started working fine.

Hibernate's NamingStrategy class is deprecated in Hibernate 5.

Solution 2

Thanks for posting your own solution. It helps me so much to set Hibernate 5 naming strategy!

The hibernate.ejb.naming_strategy property of pre-Hibernate 5.0 seems split into two parts:

  • hibernate.physical_naming_strategy
  • hibernate.implicit_naming_strategy

The values of these properties do not implement the NamingStrategy interface as did hibernate.ejb.naming_strategy. There are two new interfaces for these purposes:

  • org.hibernate.boot.model.naming.PhysicalNamingStrategy
  • org.hibernate.boot.model.naming.ImplicitNamingStrategy

Hibernate 5 provides only one implementation of PhysicalNamingStrategy (PhysicalNamingStrategyStandardImpl) that assumes physical identifier names are the same as logical ones.

There are several implementations of ImplicitNamingStrategy but I found none equivalent to the old ImprovedNamingStrategy. (See: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl)

So, I implemented my own PhysicalNamingStrategy which is very simple:

public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardImpl implements Serializable {

 public static final PhysicalNamingStrategyImpl INSTANCE = new PhysicalNamingStrategyImpl();

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

Note that the addUnderscores() method is from the original org.hibernate.cfg.ImprovedNamingStrategy.

Then, I set this physical strategy into the persistence.xml file :

  <property name="hibernate.physical_naming_strategy" value="my.package.PhysicalNamingStrategyImpl" />

It is a trap to set Hibernate 5 naming strategy as previous version settings.

Solution 3

Thanks and +1 to Samuel Andrés for the very helpful answer, however it's probably a good idea to avoid the hand-written snake-casing logic. Here is the same solution, using Guava.

It assumes your entity names are written in the StandardJavaClassFormat and column names in the standardJavaFieldFormat

Hopefully this will save some people coming here in future some googling :-)

import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import static com.google.common.base.CaseFormat.*;

public class SnakeCaseNamingStrategy extends PhysicalNamingStrategyStandardImpl {

  public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
    return new Identifier(
      UPPER_CAMEL.to(LOWER_UNDERSCORE, name.getText()),
      name.isQuoted()
    );
  }

  public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
    return new Identifier(
      LOWER_CAMEL.to(LOWER_UNDERSCORE, name.getText()),
      name.isQuoted()
    );
  }
}

Solution 4

Instead of

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

change to the new physical naming strategy and the new implementation CamelCaseToUnderscoresNamingStrategy, which behaves as the old ImprovedNamingStrategy:

jpaProperties.put("hibernate.physical_naming_strategy",
                  "org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy");

Available in Hibernate 5.6.1.FINAL

Solution 5

Every answer posts solution by implementing PhysicalNamingStrategy, but all you need (and should do) is to implement ImplicitNamingStrategy.

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. There are examples of the role of the org.hibernate.boot.model.naming.ImplicitNamingStrategy contract to determine a logical name when the mapping did not provide an explicit name.

And the code can be as easy as this (using the original addUnderscores as in other answers):

public class ImplicitNamingStrategyImpl extends ImplicitNamingStrategyJpaCompliantImpl {

    @Override
    protected Identifier toIdentifier(String stringForm, MetadataBuildingContext buildingContext) {
        return super.toIdentifier(addUnderscores(stringForm), buildingContext);
    }

    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);
    }
}
Share:
42,439

Related videos on Youtube

Anup
Author by

Anup

I am a Software Developer by profession. A keen Learner :)

Updated on December 27, 2021

Comments

  • Anup
    Anup over 2 years

    I have simple spring-jpa configuration where I have configured Hibernate's ImprovedNamingStrategy. This means if my entity class has a variable userName, then Hibernate should convert it to user_name for querying the database. But this naming conversion stopped working after I upgraded to Hibernate 5. I am getting the error:

    ERROR: Unknown column 'user0_.userName' in 'field list'

    This is my Hibernate config:

    @Configuration
    @EnableJpaRepositories("com.springJpa.repository")
    @EnableTransactionManagement
    public class DataConfig {
    
        @Bean
        public DataSource dataSource(){
            DriverManagerDataSource ds = new DriverManagerDataSource();
            ds.setDriverClassName("com.mysql.jdbc.Driver");
            ds.setUrl("jdbc:mysql://localhost:3306/test");
            ds.setUsername("root");
            ds.setPassword("admin");
            return ds;
        }
    
    
        @Bean
        public LocalContainerEntityManagerFactoryBean entityManagerFactory(){ 
    
            HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
            vendorAdapter.setShowSql(Boolean.TRUE);
            vendorAdapter.setDatabase(Database.MYSQL);
    
            LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
            factory.setJpaVendorAdapter(vendorAdapter);
            factory.setDataSource(dataSource());
            factory.setPackagesToScan("com.springJpa.entity");
    
    
            Properties jpaProperties = new Properties();
    
            jpaProperties.put("hibernate.ejb.naming_strategy","org.hibernate.cfg.ImprovedNamingStrategy");
            jpaProperties.put("hibernate.dialect","org.hibernate.dialect.MySQL5InnoDBDialect");
    
            factory.setJpaProperties(jpaProperties);
            factory.afterPropertiesSet();
            return factory;
        }
    
        @Bean
        public SharedEntityManagerBean entityManager() {
            SharedEntityManagerBean entityManager = new SharedEntityManagerBean();
            entityManager.setEntityManagerFactory(entityManagerFactory().getObject());
            return entityManager;
        }
    
    
    
        @Bean
        public PlatformTransactionManager transactionManager() {
            JpaTransactionManager txManager = new JpaTransactionManager();
            txManager.setEntityManagerFactory(entityManagerFactory().getObject());
            return txManager;
        }
    
        @Bean
        public ImprovedNamingStrategy namingStrategy(){
            return new ImprovedNamingStrategy();
        }
    }
    

    This is my Entity class:

    @Getter
    @Setter
    @Entity
    @Table(name="user")
    public class User{
    
        @Id
        @GeneratedValue
        private Long id;
    
        private String userName;
        private String email;
        private String password;
        private String role;
    
    }
    

    I don't want to explicitly name my database fields within the @Column annotations. I want my configuration which can implicitly convert camel case to underscore.

    Please guide.

    • T.D. Smith
      T.D. Smith over 8 years
      I don't understand why you don't want to use @Column
    • Anup
      Anup over 8 years
      @Tyler its just for ease of coding, adding "@Column" for each of variable is just annoying, instead i can configure Naming-Strategy which will map the variable name to db column names and i can avoid writing so many Column annotation
  • Glenn
    Glenn over 8 years
    Also, along with the custom physical naming strategy, the property hibernate.implicit_naming_strategy can be set to org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacy‌​HbmImpl
  • Anup
    Anup over 8 years
    Thanks @Samuel for explaining this and providing example for implementing ur own Naming_Strategy. Its helpful and even I am looking to implement similar Naming_Strategy
  • iberbeu
    iberbeu about 8 years
    great answer Samuel, it deserves to be the correct one and not the other. Does anyone know why they changed this behavior? It makes to me no sense at all
  • Jagger
    Jagger almost 8 years
    Why do you add implements Serializable while the org.hibernate.boot.model.naming.PhysicalNamingStrategyStanda‌​rdImpl already implements that?
  • Kostas Filios
    Kostas Filios almost 8 years
    Spring Boot 1.4 update: You can now use Spring's SpringPhysicalNamingStrategy like this (yml): spring.jpa.properties.hibernate.physical_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNam‌​ingStrategy
  • xathien
    xathien almost 8 years
    Awesome, though has some of its own flaws. For example, Hibernate's default discriminator column name is "DTYPE" which will get converted to "d_t_y_p_e".
  • davnicwil
    davnicwil almost 8 years
    @xathien thanks, was not aware of that. I guess a static mapping of special cases could be added to deal with things like this in a clear way
  • Marcel Stör
    Marcel Stör over 7 years
    I don't think that's a clever idea. ImprovedNamingStrategy implements a deprecated interface. While only the interface NamingStrategy is explicitly marked deprecated I'd assume once that one is removed all implementing classes will be removed as well.
  • Arjan Mels
    Arjan Mels about 7 years
    I don't think it is needed to touch the physical naming strategy, which will also override explicitly set column names. Just changing the implicit strategy (which is only applied when no explicit overrides are set) should be sufficient. There is a default implicit naming strategy available, which prefixes embedded classes: hibernate.implicit_naming_strategy=org.hibernate.boot.model.‌​naming.ImplicitNamin‌​gStrategyComponentPa‌​thImpl See also the hibernate documentation for more details: docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapt‌​ers/…
  • Rich
    Rich about 7 years
    @MarcelStör -- I think that's a feature of this approach, not a bug! If and when Hibernate get around to replacing the old "ImprovedNamingStrategy", they will hopefully remove that deprecated class, and this code will break nosily, giving us a prompt to switch over to the new official implementation and to remove this workaround.
  • ajkush
    ajkush almost 5 years
    I don't understand why this answer is accepted and it has many negative votes!
  • Prashant
    Prashant about 4 years
    That is because OP answered own question. It might have worked for the OP but the community thinks that either the approach taken by the OP is not correct or the answer provided by the OP is not a generic one.
  • DavidR
    DavidR over 2 years
    Hopefully people make it down this far! This is by far the cleanest solution and should be the accepted answer.