Java 改进的命名策略不再适用于 Hibernate 5

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32437202/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 12:32:45  来源:igfitidea点击:

ImprovedNamingStrategy no longer working in Hibernate 5

javahibernatejpaspring-data-jpahibernate-5.x

提问by Anup

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_namefor querying the database. But this naming conversion stopped working after I upgraded to Hibernate 5. I am getting the error:

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

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

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

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.

我不想在 @Column 注释中明确命名我的数据库字段。我希望我的配置可以将驼峰式大小写隐式转换为下划线。

Please guide.

请指导。

采纳答案by Anup

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

刚刚发现问题,使用 Hibernate 版本 < 5.0 时配置绝对没问题,但不适用于 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 5.0.0.Final 和 Spring 4.2.0.RELEASE。我猜 Hibernate 5 与 Spring 4.2 不完全兼容。我刚刚将 Hibernate 降级到 4.2.1.Final 并且一切开始正常。

Hibernate's NamingStrategyclass is deprecated in Hibernate 5.

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

回答by Samuel Andrés

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

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

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

hibernate.ejb.naming_strategyPre-Hibernate 5.0的属性似乎分为两部分:

  • hibernate.physical_naming_strategy
  • hibernate.implicit_naming_strategy
  • hibernate.physical_naming_strategy
  • hibernate.implicit_naming_strategy

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

这些属性的值NamingStrategy不像以前那样实现接口hibernate.ejb.naming_strategy。有两个用于这些目的的新接口:

  • org.hibernate.boot.model.naming.PhysicalNamingStrategy
  • org.hibernate.boot.model.naming.ImplicitNamingStrategy
  • 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.

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

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

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

So, I implemented my own PhysicalNamingStrategywhich is very simple:

所以,我实现了我自己的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);
 }
}

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

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

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

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

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

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

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

回答by Ignacio

hope this helps:

希望这可以帮助:

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

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

}

回答by davnicwil

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.

感谢塞缪尔·安德烈斯 (Samuel Andrés) 的 +1 非常有用的答案,但是避免手写蛇套逻辑可能是个好主意。这是使用 Guava 的相同解决方案。

It assumes your entity names are written in the StandardJavaClassFormatand column names in the standardJavaFieldFormat

它假定你的实体名称都写在StandardJavaClassFormat和列名在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()
    );
  }
}

回答by Michael Hegner

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

谢谢你的帖子。升级打破了表和列名策略的小烦恼。除了从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);
    }
}

回答by SoBeRich

No Guava and Apache utils

没有 Guava 和 Apache utils

public class PhysicalNamingStrategyImpl extends PhysicalNamingStrategyStandardImpl {

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

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

回答by ThreeDots

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

每个答案都通过实现来发布解决方案PhysicalNamingStrategy,但您需要(并且应该做的)就是实现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.

当实体没有显式命名它映射到的数据库表时,我们需要隐式确定该表名。或者当特定属性没有明确命名它映射到的数据库列时,我们需要隐式确定该列名。有一些例子说明了 org.hibernate.boot.model.naming.ImplicitNamingStrategy 合同在映射未提供显式名称时确定逻辑名称的作用。

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

代码可以像这样简单(使用原始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);
    }
}