在 Spring JPA 中设置 table 名称

Set table name in Spring JPA

我想我正在尝试做一些非常简单的事情。在 JPA 中使用 Spring Boot (1.3.3.RELEASE) 我想设置一个 table 名称。

@Entity
@Table(name = "MyTable_name")
public class MyTableData {
  ...
}

我在我的数据库中期望的是 table 和 "MyTable_name"。对我来说似乎完全合理。但那不会发生。我得到一个名称为 "MY_TABLE_NAME"(H2 后端)或 "my_table_name"(Postgre 后端)的 table。从这里开始,我将坚持使用 Postgre,因为我的目标是读取一个我无法控制 table 名称的现有数据库。

经过一些研究,我发现 post 说我应该使用 spring.jpa.hibernate.naming 策略 属性。这没有多大帮助。设置为最常推荐的 org.hibernate.cfg.ImprovedNamingStrategy 会产生相同的行为:"my_table_name"。设置为 org.hibernate.cfg.EJB3NamingStrategy 会生成 "mytable_name"。设置为 org.hibernate.cfg.DefaultNamingStrategy 会导致 Spring 的内部出现应用程序上下文错误。

放弃自己写,开始看org.hibernate.cfg.ImprovedNamingStrategy。我发现它使用了已弃用的 org.hibernate.cfg.NamingStrategy。这建议改用 NamingStrategyDelegator。我看了看它的Java docs but not sure how to apply. I found this post。尽管我很欣赏你的解释,但那里试图做的事情比我需要的更复杂,我在应用它时遇到了麻烦。

我的问题是如何让 Spring JPA 只使用我指定的名称?是否有新的 属性 供 NamingStrategyDelegator 使用?我需要编写自己的策略吗?

===========更新==========================

我想我正在收集一个答案。我创建了一个简单的 Spring 启动应用程序(与我的生产项目分开)。我将 H2 用于后端数据库。

This discussion on Hiberate 5 Naming 很有帮助。有了它,我想出了如何在 Hibernate 5 中设置命名策略,如下所示(在 application.properties 中)。

hibernate.implicit_naming_strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl
hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

我创建了一个通过名称传递的物理命名策略(就像 org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 那样)并打印出值。从那里我看到 tables 名称是我想要通过物理命名层。

然后我设置 hibernate.show_sql=true 以显示生成 SQL。在生成的 SQL 中,名称也是正确的。

我正在使用 DatabaseMetaData 检查 table 名称。

private void showTables() throws SQLException {
    DatabaseMetaData dbMetadata = getConnection().getMetaData();
    ResultSet result = dbMetadata.getTables(null, null, null, new String[] { "TABLE" });
    if (result != null) {
        boolean haveTable = false;
        while (result.next()) {
            haveTable = true;
            getLogger().info("Found table {}", result.getString("TABLE_NAME"));
        }
        if (!haveTable) {
            getLogger().info("No tables found");
        }

    }
}

当我使用上面的代码时,我仍然看到 table 个名字全部大写。这让我相信 DatabaseMetaData 出于某种原因显示全部大写,但其余代码使用正确的名称。 [编辑:这个结论是不正确的。我只是对正在发生的一切感到困惑。后来的测试显示 DatabaseMetaData 显示 table 个大小写正确的名称。]

这还不是一个完整的答案,因为我的生产代码中仍有一些奇怪的地方需要调查。但它接近了,我想 post 更新,这样潜在的读者就不会浪费时间了。

这是我通过物理命名策略的过程,以防有人感兴趣。我知道看看别人做了什么会有所帮助,尤其是当试图在 Spring 迷宫中找到 类 和包裹时。

package my.domain.eric;

import java.io.Serializable;

import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class NamingStrategyPhysicalLeaveAlone implements PhysicalNamingStrategy, Serializable {
    private static final long serialVersionUID = -5937286882099274612L;

    private static final Logger LOGGER = LoggerFactory.getLogger(NamingStrategyPhysicalLeaveAlone.class);

    protected Logger getLogger() {
        return LOGGER;
    }

    @Override
    public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment context) {
        String nameText = name == null ? "" : name.getText();
        getLogger().info("toPhysicalCatalogName name: {}", nameText);
        return name;
    }

    @Override
    public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment context) {
        String nameText = name == null ? "" : name.getText();
        getLogger().info("toPhysicalSchemaName name: {}", nameText);
        return name;
    }

    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        String nameText = name == null ? "" : name.getText();
        getLogger().info("toPhysicalTableName name: {}", nameText);
        return name;
    }

    @Override
    public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment context) {
        String nameText = name == null ? "" : name.getText();
        getLogger().info("toPhysicalSequenceName name: {}", nameText);
        return name;
    }

    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
        String nameText = name == null ? "" : name.getText();
        getLogger().info("toPhysicalColumnName name: {}", nameText);
        return name;
    }
}

名称在实体注释中指定

@Entity(name = "MyTable_name")
public class MyTableData {
  ...
}

我的问题的答案涉及以下内容。

  1. SQL 不区分大小写,但并不是那么简单。引用的名字是按字面意思取的。未加引号的名称可以自由解释。例如,PostgreSQL 将不带引号的名称转换为小写,而 H2 将它们转换为大写。因此 select * from MyTable_name in PostgreSQL 寻找 table mytable_name。在 H2 中,相同的查询查找 MYTABLE_NAME。在我的例子中,PostgreSQL table 是使用引号 "MyTable_name" 创建的,所以 select * from MyTable_name 失败,而 select * from "MyTable_name"成功。
  2. Spring JPA/Hibernate 将不带引号的名称传递给 SQL.
  3. 在Spring JPA/Hibernate中有三种方法可以用来传递引用的名字
    1. 明确引用名字:@Table(name = "\"MyTable_name\"")
    2. 实施引用名称的物理命名策略(详情如下)
    3. 设置一个未记录的属性以引用所有 table 和列名称:spring.jpa.properties.hibernate.globally_quoted_identifiers=true(参见 this comment)。最后这是我所做的,因为我还有需要区分大小写的列名。

另一个让我感到困惑的来源是许多网站引用旧的命名变量 hibernate.ejb.naming_strategy 或者它是 spring 等价物。对于已过时的 Hibernate 5。相反,正如我在问题更新中提到的,Hibernate 5 具有隐式和物理命名策略。

另外,我一头雾水,因为有hibernate的属性,然后还有Spring的属性。我正在使用 this very helpful tutorial。然而,它显示了不必要的直接使用休眠属性(正如我在更新中列出的那样),然后是 LocalContainerEntityManagerFactoryBean 和 JpaTransactionManager 的显式配置。使用 Spring 属性并自动拾取它们要容易得多。与我相关的是命名策略。

  1. spring.jpa.hibernate.naming.implicit-策略
  2. spring.jpa.hibernate.naming.physical-策略

要实施物理命名策略,需要创建一个 class 来实施 org.hibernate.boot.model.naming.PhysicalNamingStrategy,如我在上面的更新中所示。引用名称实际上非常容易,因为传递给方法的标识符 class 管理是否引用。因此下面的方法将引用 table names.

@Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
    if (name == null) {
        return null;
    }
    return Identifier.quote(name);
}

我学到的其他东西可能对来这里寻找答案的人有帮助。

  1. 使用 spring.jpa 属性自动选择 SQL 方言。使用直接休眠时,当我切换到 Postgre 时出现 SQL 错误。
  2. 虽然 Spring 应用程序上下文失败很常见,但仔细阅读错误通常会指出解决方案。
  3. DatabaseMetaData 报告 table 名称正确,我只是被其他一切弄糊涂了。
  4. 设置spring.jpa.show-sql=true 以查看生成的SQL。对调试很有帮助。让我看到正在使用正确的 table 名称
  5. spring.jpa.hibernate.ddl-auto 至少支持以下值。 create-drop: 在进入时创建 tables,在退出时删除。创建:在进入时创建 tables 但在退出时离开。 none: 不创建或删除。我看到人们使用 "update" 作为值,但这对我来说失败了。 (例如 .) Here is a discussion on the options.
  6. 我在 H2 中使用引用的列名时遇到了问题,但没有进一步调查。
  7. Spring properties page 很有帮助,但描述非常稀疏。

要将 @Table(...) 中指定的确切名称作为数据库中的 table 名称,想出了这个解决方案:

application.yaml 文件

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/<dbname>?currentSchema=<schema-name>
    username: <username>
    password: <password>

  jpa:
    show-sql: true

    hibernate:
      # comment out ddl-auto as needed
      ddl-auto: create-drop
      naming:
        # the following property is important for this topic:
        # note that you are going implement the following java class:
        physical-strategy: paul.tuhin.sbtutorial.NamingStrategy

    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        default_schema: <schema-name>
        format_sql: true
        # this will quote your schema name as well:
        globally_quoted_identifiers: true

paul.tuhin.sbtutorial.NamingStrategy.java 文件:

package paul.tuhin.sbtutorial;

import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

public class NamingStrategy extends PhysicalNamingStrategyStandardImpl {
    private static final long serialVersionUID = 1L;

    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
        //The table name all converted to uppercase
        String tableName = name.getText();
        
        return Identifier.quote(Identifier.toIdentifier(tableName));
    }
    
    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
        String colnumName = name.getText();

        return Identifier.quote(Identifier.toIdentifier(colnumName));
    }
}

解决方案改编自(感谢):https://titanwolf.org/Network/Articles/Article?AID=b0f17470-3cfe-4ebc-9c45-25a462115be5