改进的命名策略不再在 Hibernate 5 中工作

Posted

技术标签:

【中文标题】改进的命名策略不再在 Hibernate 5 中工作【英文标题】:ImprovedNamingStrategy no longer working in Hibernate 5 【发布时间】:2015-12-02 22:08:58 【问题描述】:

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

错误:“字段列表”中的未知列“user0_.userName”

这是我的 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 注释中明确命名我的数据库字段。我想要我的配置可以隐式地将驼峰大小写转换为下划线。

请指导。

【问题讨论】:

我不明白你为什么不想使用@Column @Tyler 只是为了便于编码,为每个变量添加“@Column”很烦人,相反我可以配置 Naming-Strategy,它将变量名映射到数据库列名,我可以避免写这么多Column注解 【参考方案1】:

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

pre-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 的命名策略设置为以前的版本设置是一个陷阱。

【讨论】:

另外,连同自定义物理命名策略,属性hibernate.implicit_naming_strategy可以设置为org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl 感谢 @Samuel 解释这一点并提供实施您自己的 Naming_Strategy 的示例。它很有帮助,甚至我也在寻求实现类似的 Naming_Strategy 很好的答案塞缪尔,它应该是正确的,而不是另一个。有谁知道他们为什么改变这种行为?这对我来说毫无意义 Spring Boot 1.4 更新:您现在可以像这样使用 Spring 的 SpringPhysicalNamingStrategy (yml):spring.jpa.properties.hibernate.physical_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 我认为不需要触及物理命名策略,这也会覆盖显式设置的列名。只需更改隐式策略(仅在未设置显式覆盖时应用)就足够了。有一个默认的隐式命名策略可用,它为嵌入的类添加前缀:hibernate.implicit_naming_strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl 有关更多详细信息,另请参阅 hibernate 文档:docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/…【参考方案2】:

感谢 Samuel Andrés 提供的非常有帮助的答案并 +1,不过最好避免使用手写的蛇形外壳逻辑。这是使用番石榴的相同解决方案。

假设您的实体名称写在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 谢谢,不知道这一点。我想可以添加一个特殊情况的静态映射来以清晰的方式处理这样的事情【参考方案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 被明确标记为已弃用,但我假设一旦删除该接口,所有实现类也将被删除。 @MarcelStör -- 我认为这是这种方法的一个特点,而不是一个错误!如果当 Hibernate 开始替换旧的“ImprovedNamingStrategy”时,他们有望删除该已弃用的类,并且此代码将吵闹,提示我们切换到新的官方实现并删除此解决方法。【参考方案4】:

每个答案都通过实现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);
    

【讨论】:

【参考方案5】:

希望这会有所帮助:

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


【讨论】:

【参考方案6】:

没有 Guava 和 Apache 工具

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

【讨论】:

【参考方案7】:

代替

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 中可用

【讨论】:

【参考方案8】:

刚刚发现问题,使用= 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 中已弃用。

【讨论】:

我不明白为什么这个答案被接受并且有很多反对票! 那是因为 OP 回答了自己的问题。它可能对 OP 有效,但社区认为 OP 采用的方法不正确,或者 OP 提供的答案不是通用的。

以上是关于改进的命名策略不再在 Hibernate 5 中工作的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate 命名策略更改表名

Hibernate命名策略

Hibernate 命名策略被忽略

如何在 Spring Data JPA 中设置 Hibernate 命名策略

hibernate 命名策略

Spring Boot 的 Hibernate 字段命名问题(命名策略)