Spring - Hibernate 5 命名策略配置

34

我正在使用postgresql数据库和spring+hibernate框架编写应用程序。

我将spring框架从4.1.5.RELEASE升级到4.2.0.RELEASE版本,并将hibernate框架从4.3.7.Final升级到5.0.0.Final版本。

在升级后,我遇到了命名策略的问题。在postgresql数据库中,表列名以小写字母分隔符下划线形式呈现,在应用程序层中,bean属性则是驼峰格式。

这是旧版本的工作spring配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="fms" />

    <bean id="microFmsDataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="***" />
        <property name="username" value="***" />
        <property name="password" value="***" />

        <property name="testOnBorrow" value="true" />
        <property name="testOnReturn" value="true" />
        <property name="testWhileIdle" value="true" />
        <property name="validationQuery" value="select 1" />
        <property name="initialSize" value="5" />
        <property name="minIdle" value="10" />
        <property name="maxIdle" value="100" />
        <property name="maxActive" value="100" />
        <property name="removeAbandoned" value="true" />
    </bean>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="microFmsDataSource"/>

        <property name="packagesToScan">
            <list>
                <value>fms</value>
            </list>
        </property>

        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>

        <property name="jpaPropertyMap">
            <map>
                <entry key="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider" />
                <entry key="hibernate.hbm2ddl.auto" value="validate" />
                <entry key="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
                <entry key="hibernate.show_sql" value="true" />
                <entry key="hibernate.format_sql" value="true" />
                <entry key="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" />
            </map>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

升级后我更改了NamingStrategy配置:

<entry key="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" />

就像这样:

<entry key="hibernate.implicit_naming_strategy" value="***" />

我尝试了Hibernate javadoc中列出的所有选项变体:https://docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/cfg/AvailableSettings.html#IMPLICIT_NAMING_STRATEGY,但没有成功。

请问在Hibernate 5中有哪些ImprovedNamingStrategy的替代方案,并提供一个可工作的配置示例吗?


我使用了<prop key="hibernate.implicit_naming_strategy">legacy-jpa</prop>,并且它对我有效。你可以试试吗? - Sharadr
可能是 Hibernate 5 中 ImprovedNamingStrategy 不再起作用 的重复问题。 - Rich
6个回答

32

我想我找到了解决方案。

为了实现我的目标,我使用了hibernate.physical_naming_strategy配置,而不是hibernate.implicit_naming_strategy

我创建了一个PhysicalNamingStrategy接口的实现,模拟了原始ImprovedNamingStrategy类的部分功能:

package fms.util.hibernate;

import org.apache.commons.lang.StringUtils;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

public class ImprovedNamingStrategy implements PhysicalNamingStrategy {

    @Override
    public Identifier toPhysicalCatalogName(Identifier identifier, JdbcEnvironment jdbcEnv) {
        return convert(identifier);
    }

    @Override
    public Identifier toPhysicalColumnName(Identifier identifier, JdbcEnvironment jdbcEnv) {
        return convert(identifier);
    }

    @Override
    public Identifier toPhysicalSchemaName(Identifier identifier, JdbcEnvironment jdbcEnv) {
        return convert(identifier);
    }

    @Override
    public Identifier toPhysicalSequenceName(Identifier identifier, JdbcEnvironment jdbcEnv) {
        return convert(identifier);
    }

    @Override
    public Identifier toPhysicalTableName(Identifier identifier, JdbcEnvironment jdbcEnv) {
        return convert(identifier);
    }

    private Identifier convert(Identifier identifier) {
        if (identifier == null || StringUtils.isBlank(identifier.getText())) {
            return identifier;
        }

        String regex = "([a-z])([A-Z])";
        String replacement = "$1_$2";
        String newName = identifier.getText().replaceAll(regex, replacement).toLowerCase();
        return Identifier.toIdentifier(newName);
    }
}

创建这个类之后,我将配置从以下内容进行了更改:

<entry key="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" />

到这个:

<entry key="hibernate.physical_naming_strategy" value="fms.util.hibernate.ImprovedNamingStrategy" />

现在一切都正常了。

这个解决方案仅涵盖了 ImprovedNamingStrategy 的一小部分。在我的项目中,对于表映射和联接映射,我总是明确指定表名或联接列的名称。我仅依赖隐式命名转换来处理列名。因此,对我来说,这个简单的解决方案是可以接受的。

这是我的 Spring 配置文件的完整示例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="fms" />

    <bean id="microFmsDataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="***" />
        <property name="username" value="***" />
        <property name="password" value="***" />

        <property name="testOnBorrow" value="true" />
        <property name="testOnReturn" value="true" />
        <property name="testWhileIdle" value="true" />
        <property name="validationQuery" value="select 1" />
        <property name="initialSize" value="5" />
        <property name="minIdle" value="10" />
        <property name="maxIdle" value="100" />
        <property name="maxActive" value="100" />
        <property name="removeAbandoned" value="true" />
    </bean>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="microFmsDataSource"/>

        <property name="packagesToScan">
            <list>
                <value>fms</value>
            </list>
        </property>

        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>

        <property name="jpaPropertyMap">
            <map>
                <entry key="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider" />
                <entry key="hibernate.hbm2ddl.auto" value="validate" />
                <entry key="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
                <entry key="hibernate.show_sql" value="true" />
                <entry key="hibernate.format_sql" value="true" />
                <entry key="hibernate.physical_naming_strategy" value="fms.util.hibernate.ImprovedNamingStrategy" />
            </map>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

我希望这个解决方案对某人有所帮助。 :)


2
谢谢分享。我刚遇到了这个问题。你的解决方案对我没用。它只修复了驼峰命名转下划线命名的问题,但没有解决ImprovedNamingStrategy和JPA标准之间的另一个差异,即_id后缀。标准在外键列上创建了_id后缀,而ImprovedNamingStrategy没有。你有没有对此也有解决方案? - Aleksander Blomskøld
@AleksanderBlomskøld 您是正确的,这个解决方案只涵盖了ImprovedNamingStrategy的一小部分。在我的项目中,对于表映射和连接映射,我总是明确指定表名或连接列的名称。我仅依赖隐式名称转换来处理列名。因此,这个简单的解决方案对我来说是可以接受的。 - Yoh0xFF
1
说“为了完成我想要的任务,必须使用‘hibernate.physical_naming_strategy’配置”是不正确的。通过使用ImplicitNamingStrategy的实现可以达到相同的效果。说“没有PhysicalNamingStrategy接口的实现”也是不正确的,请参见PhysicalNamingStrategyStandardImpl作为一个例子。 - v.ladynev
@v.ladynev 感谢您的纠正,我已经更正了答案。 - Yoh0xFF
1
另请参见@samuel-andrés的解决方案,以扩展PhysicalNamingStrategyStandardImpl 此处 - Glenn
1
Spring Boot 1.4更新:现在您可以像这样(yml)使用Spring的[SpringPhysicalNamingStrategy](http://docs.spring.io/spring-boot/docs/1.4.0.RC1/api/org/springframework/boot/orm/jpa/hibernate/SpringPhysicalNamingStrategy.html):spring.jpa.properties.hibernate.physical_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy - Kostas Filios

11

如果有人正在寻找Java配置解决方案

hibernate.ejb.naming_strategy属性在Hibernate 5.X中似乎被拆分为两个部分:

hibernate.physical_naming_strategy

hibernate.implicit_naming_strategy

Spring提供了SpringImplicitNamingStrategySpringPhysicalNamingStrategy来实现这一目的。

这是我的方法:

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@DependsOn("myDataSource")
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "myEntityManagerFactory",
        basePackages={"com.myapp.repo"},
        transactionManagerRef="myTransactionManager"
        )
public class MyJpaConfig {

    private Map<String, Object> properties;

    public MyJpaConfig() {
        properties = new HashMap<>();
        properties.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
        properties.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());

    }

    @Bean(name = "myEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder,
            @Qualifier("systemDataSource") DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean build = builder.dataSource(dataSource)
                .packages("com.myapp.entity")
                .properties(properties)
                .build();
        return build;

    }

    @Bean(name = "myTransactionManager")
    public PlatformTransactionManager myTransactionManager(
            @Qualifier("myEntityManagerFactory") EntityManagerFactory myEntityManagerFactory) {
        return new JpaTransactionManager(myEntityManagerFactory);
    }
}

6

我曾经遇到过完全相同的问题。我使用Spring Boot的默认实现解决了这个问题:

<property name="hibernate.implicit_naming_strategy" value="org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy" />
<property name="hibernate.physical_naming_strategy" value="org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy" />

5
这对我很有帮助,来源于http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/orm/hibernate5/LocalSessionFactoryBean.html
<bean id="sessionFactory" 
    class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">

    <!-- convert aaBb to aa_bb -->
    <property name="PhysicalNamingStrategy">
        <bean class="fms.util.hibernate.ImprovedNamingStrategy" />
    </property>

    <!-- convert aa_bb to aaBb -->
    <property name="ImplicitNamingStrategy">
        <bean class="org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl" />
    </property>
...
</bean>   

2

试试这个:

@Bean
public LocalSessionFactoryBean getSessionFactory() {
    LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
    localSessionFactoryBean.setDataSource(getDataSource());
    localSessionFactoryBean.setHibernateProperties(getHibernateProperties());
    localSessionFactoryBean.setPackagesToScan(new String[]{"com.xxx.pojo"});
    // -----important-----
    localSessionFactoryBean.setPhysicalNamingStrategy(new CustomNamingStrategy());
    return localSessionFactoryBean;
}

public class CustomNamingStrategy extends PhysicalNamingStrategyStandardImpl {***

1
请看下面三个我在命名策略工作中发现的要点:
  1. 如果您在实体类中使用 @Table@Column 注释,并且使用下划线提供名称,例如 user_id, 即 @Column(name="user_id"),它将采用列名 user_id;如果您使用无策略或隐式策略(具体来说是 org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl),则会将其更改为 user_id 如果您将其设置为 userid。因此,如果您想要一种策略,其中实体属性名称更改为使用下划线和小写字母的名称,例如从 userId 更改为 user_id,则应使用隐式或无策略(实际上使用隐式策略)。

  2. 如果您不希望命名策略向列名或类名添加下划线,则需要使用以下策略:spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl。您在注释 @Table@Column 的名称属性中提供的内容将保持不变。

  3. 如果您不想提供注释并希望手动处理表名和列名,则应扩展类 org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 并覆盖所需方法。如果您仍在某些情况下使用注释,请记住覆盖的方法将应用于这些注释中编写的名称。


@MohamadShahrestani,你能否提出一些修改建议? - Raj Kundalia
请阅读以下链接以更好地在Stackoverflow上回答问题:https://meta.stackexchange.com/questions/7656/how-do-i-write-a-good-answer-to-a-question - Mohammad Reza Shahrestani

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