51

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.

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
Anup
  • 556
  • 1
  • 5
  • 11
  • 1
    I don't understand why you don't want to use @Column – T.D. Smith Sep 07 '15 at 11:44
  • 9
    @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 – Anup Sep 08 '15 at 06:40

8 Answers8

77

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.

edur
  • 1,567
  • 1
  • 11
  • 10
Samuel Andrés
  • 771
  • 5
  • 3
  • 2
    Also, along with the custom physical naming strategy, the property `hibernate.implicit_naming_strategy` can be set to `org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl` – Glenn Jan 29 '16 at 21:22
  • 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 – Anup Feb 12 '16 at 05:49
  • 2
    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 – iberbeu Mar 04 '16 at 13:21
  • Why do you add `implements Serializable` while the `org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl` already implements that? – Jagger Jun 09 '16 at 06:51
  • 3
    Spring Boot 1.4 update: You can now use Spring's [SpringPhysicalNamingStrategy](http://docs.spring.io/spring-boot/docs/1.4.0.RC1/api/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategy.html) like this (yml): `spring.jpa.properties.hibernate.physical_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy` – Kostas Filios Jul 29 '16 at 13:06
  • 1
    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.ImplicitNamingStrategyComponentPathImpl` See also the hibernate documentation for more details: https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/domain/naming.html – Arjan Mels Mar 27 '17 at 18:04
13

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

manuelvigarcia
  • 1,696
  • 1
  • 22
  • 32
  • 1
    Hopefully people make it down this far! This is by far the cleanest solution and should be the accepted answer. – DavidR Feb 28 '22 at 19:57
7

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()
    );
  }
}
davnicwil
  • 28,487
  • 16
  • 107
  • 123
  • 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". – xathien Aug 26 '16 at 03:49
  • @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 – davnicwil Aug 26 '16 at 08:26
3

thanks for that post. Little anoying that upgrade breaks the table and column name strategy. Instead of copying the logic from ImprovedNamingStrategy you could also use delegation.

public class TableNamingStrategy extends PhysicalNamingStrategyStandardImpl {
    private static final String TABLE_PREFIX = "APP_";
    private static final long serialVersionUID = 1L;
    private static final ImprovedNamingStrategy STRATEGY_INSTANCE = new ImprovedNamingStrategy();

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

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

    private String classToTableName(String className) {
        return STRATEGY_INSTANCE.classToTableName(TABLE_PREFIX + className);
    }
}
Michael Hegner
  • 5,555
  • 9
  • 38
  • 64
  • 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. – Marcel Stör Dec 09 '16 at 07:14
  • 1
    @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. – Rich Apr 21 '17 at 10:31
3

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);
    }
}
ThreeDots
  • 617
  • 7
  • 13
2

hope this helps:

hibernate.implicit_naming_strategy=....ImplicitNamingStrategy hibernate.physical_naming_strategy=....PhysicalNamingStrategyImpl

and here is the code (just re-arrenged from existing code):

import java.io.Serializable;
import java.util.Locale;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

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

}
Kerem Baydoğan
  • 10,475
  • 1
  • 43
  • 50
Ignacio
  • 331
  • 6
  • 15
0

No Guava and Apache utils

public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardImpl {

    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        return context.getIdentifierHelper().toIdentifier(
                name.getText().replaceAll("((?!^)[^_])([A-Z])", "$1_$2").toLowerCase(),
                name.isQuoted()
        );
    }

    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
        return context.getIdentifierHelper().toIdentifier(
                name.getText().replaceAll("((?!^)[^_])([A-Z])", "$1_$2").toLowerCase(),
                name.isQuoted()
        );
    }
}
SoBeRich
  • 682
  • 2
  • 8
  • 15
-8

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.

Glenn
  • 8,932
  • 2
  • 41
  • 54
Anup
  • 556
  • 1
  • 5
  • 11
  • I don't understand why this answer is accepted and it has many negative votes! – ajkush Jul 24 '19 at 15:26
  • 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. – Prashant Mar 08 '20 at 08:02