Hibernate 5中ImprovedNamingStrategy不再起作用

52

我的spring-jpa配置非常简单,其中我配置了Hibernate的ImprovedNamingStrategy。这意味着如果我的实体类有一个变量userName,那么Hibernate应该将其转换为user_name以查询数据库。但是在升级到Hibernate 5后,这种命名转换停止工作了。我收到了以下错误:

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

这是我的Hibernate配置:

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

这是我的实体类:

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

}

我不想在 @Column 注解中显式命名我的数据库字段。我希望我的配置可以隐式地将驼峰命名法转换成下划线命名法。

请指导。


1
我不明白为什么你不想使用@Column。 - T.D. Smith
9
@Tyler,这只是为了编码方便。为每个变量添加“@Column”很烦人,相反我可以配置命名策略,将变量名映射到数据库列名中,从而避免编写太多的Column注释。 - Anup
8个回答

78

感谢您发布自己的解决方案。它对我设置Hibernate 5命名策略非常有帮助!

早期的Hibernate 5.0的hibernate.ejb.naming_strategy属性似乎分成了两部分:

  • hibernate.physical_naming_strategy
  • hibernate.implicit_naming_strategy

这些属性的值不像hibernate.ejb.naming_strategy一样实现NamingStrategy接口。为此,这两个新接口是:

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

Hibernate 5仅提供了一个PhysicalNamingStrategy实现(PhysicalNamingStrategyStandardImpl),它假设物理标识符名称与逻辑标识符名称相同。

有几个ImplicitNamingStrategy的实现,但我没有找到任何一个等同于旧的ImprovedNamingStrategy。(参见:org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl

因此,我实现了自己的PhysicalNamingStrategy,它非常简单:

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

请注意,addUnderscores()方法来自原始的org.hibernate.cfg.ImprovedNamingStrategy

然后,我将这个物理策略设置到了persistence.xml文件中:

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

将Hibernate 5的命名策略设置为以前版本的设置是一个陷阱。


2
此外,除了自定义物理命名策略之外,还可以将属性 hibernate.implicit_naming_strategy 设置为 org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl - Glenn
感谢@Samuel的解释和提供实现自己的Naming_Strategy示例。这很有帮助,我也想实现类似的Naming_Strategy。 - Anup
2
很棒的回答,Samuel。它应该是正确的,而不是其他的。有人知道为什么他们改变了这种行为吗?对我来说完全没有意义。 - iberbeu
为什么在 org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 已经实现了 Serializable 接口的情况下,你还要添加 implements Serializable - Jagger
3
Spring Boot 1.4更新:现在你可以使用Spring的SpringPhysicalNamingStrategy,方法如下(yml):spring.jpa.properties.hibernate.physical_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy - Kostas Filios
1
我认为不需要触及物理命名策略,这也会覆盖明确设置的列名。只需更改隐式策略(仅在未设置显式覆盖时应用)即可。有一个默认的隐式命名策略可用,它为嵌入类添加前缀:hibernate.implicit_naming_strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl有关更多详细信息,请参阅Hibernate文档:https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/domain/naming.html - Arjan Mels

13

而不是

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

更改为新的物理命名策略和新的实现 CamelCaseToUnderscoresNamingStrategy,其行为与旧的 ImprovedNamingStrategy 相同:

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

Hibernate 5.6.1.FINAL 已发布


1
希望大家能看到这里!这绝对是最干净的解决方案,应该被接受为答案。 - DavidR

7
< p >感谢Samuel Andrés提供非常有帮助的答案,但最好避免手写蛇形逻辑。这里是使用Guava的相同解决方案。

它假设您的实体名称采用StandardJavaClassFormat编写,列名采用standardJavaFieldFormat

希望这能为未来一些人节省一些搜索时间:-)

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

很棒,但也有一些缺陷。例如,Hibernate的默认鉴别器列名为“DTYPE”,这将被转换为“d_t_y_p_e”。 - xathien
@xathien 谢谢,我之前不知道这个。我猜可以添加一个静态映射来处理这种情况,以清晰的方式解决问题。 - davnicwil

3
每个答案都通过实现PhysicalNamingStrategy来解决问题,但你需要(也应该)实现ImplicitNamingStrategy。
当实体没有显式地命名它映射到的数据库表时,我们需要隐式确定该表名。或者当特定属性没有显式地命名它映射到的数据库列时,我们需要隐式确定该列名。org.hibernate.boot.model.naming.ImplicitNamingStrategy合同的作用是在映射没有提供显式名称时确定逻辑名称。
代码可以像这样简单(使用其他答案中的原始addUnderscores):
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);
    }
}

3
感谢您的帖子。升级破坏了表和列名策略,这有点让人不爽。您可以使用委托而不是复制ImprovedNamingStrategy的逻辑来解决这个问题。
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);
    }
}

我认为那不是一个聪明的想法。ImprovedNamingStrategy 实现了一个已经被弃用的接口。虽然只有 NamingStrategy 接口被明确标记为弃用,但我认为一旦该接口被移除,所有实现该接口的类也将被移除。 - Marcel Stör
1
@MarcelStör -- 我认为这是这种方法的一个特性,而不是一个错误!如果Hibernate在替换旧的“ImprovedNamingStrategy”时,他们希望删除那个已弃用的类,那么这段代码将会出现嘈杂的错误提示,促使我们切换到新的官方实现并删除这个解决方法。 - Rich

2
希望这可以帮到你:
hibernate.implicit_naming_strategy=....ImplicitNamingStrategy hibernate.physical_naming_strategy=....PhysicalNamingStrategyImpl
以下是代码(只是从现有代码重新排列):
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);
    }

}

0

没有Guava和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()
        );
    }
}

-8

刚刚找到了问题所在,当使用 Hibernate 版本 < 5.0 时,配置绝对没有问题,但是对于 Hibernate >= 5.0 就不行了。

我使用的是 Hibernate 5.0.0.Final 和 Spring 4.2.0.RELEASE。我猜测 Hibernate 5 不完全兼容 Spring 4.2。我将 Hibernate 降级到 4.2.1.Final,事情就开始正常工作了。

Hibernate 的 NamingStrategy 类在 Hibernate 5 中已被弃用。


我不明白为什么这个答案被采纳了,但是却有很多负面评价! - ajkush
这是因为 OP 回答了自己的问题。它可能对 OP 有效,但社区认为,OP 所采取的方法不正确或者 OP 提供的答案不是通用的。 - Prashant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接