Set table name in Spring JPA

时间:2016-07-28 20:20:59

标签: spring hibernate jpa naming

I think I'm trying to do something really simple. Using Spring Boot (1.3.3.RELEASE) with JPA I want to set a table name.

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

What I expect in my database is a table with "MyTable_name". Seems completely reasonable to me. But that doesn't happen. I get a table with name "MY_TABLE_NAME" (H2 backend) or "my_table_name" (Postgre backend). From here on I'll stick with Postgre since my goal is to read an existing DB where I don't control the table names.

After some research I find posts that say I should use the spring.jpa.hibernate.naming-strategy property. This doesn't help much. Setting to the most commonly recommended org.hibernate.cfg.ImprovedNamingStrategy produces the same behavior: "my_table_name". Setting to org.hibernate.cfg.EJB3NamingStrategy produces "mytable_name". Setting to org.hibernate.cfg.DefaultNamingStrategy causes application context errors in Spring's innards.

Resigned to writing my own, I started looking at org.hibernate.cfg.ImprovedNamingStrategy. I discovered it used the deprecated org.hibernate.cfg.NamingStrategy. That suggests using NamingStrategyDelegator instead. I looked at its Java docs but not sure how to apply. I found this post. As much as I appreciate the explanation, what is trying to be done there is more complex than what I need and I had trouble applying it.

My question then is how can I get Spring JPA to just use the name I specify? Is there a new property for NamingStrategyDelegator use? Do I need to write my own strategy?

=========== Update ==========================

I think I'm converging on an answer. I created a simple Spring startup application (separate from my production project). I use H2 for the backend DB.

This discussion on Hiberate 5 Naming is very helpful. With it I figured out how to set naming strategies in Hibernate 5 like the following (in application.properties).

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

I created a physical naming strategy that passed through the name (like org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl does) and prints out values. From that I see that tables names are what I want through the physical naming layer.

I then set hibernate.show_sql=true to show generate SQL. In the generated SQL the names are also correct.

I am examining table names using DatabaseMetaData.

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

    }
}

I still see table names in ALL CAPS when I use the above code. This leads me to believe that DatabaseMetaData is showing all caps for some reason but the rest of the code uses the correct names. [EDIT: This conclusion is not correct. I was just confused by everything else that was happening. Later testing shows DatabaseMetaData shows table names with correct case.]

This is not yet a complete answer because there is still some strangeness in my production code that I need to investigate. But it's close and I wanted to post an update so potential readers don't waste time.

Here is my pass through physical naming strategy in case anyone is interested. I know it can help to see what others have done, especially when trying to find classes and packages in the Spring labyrinth.

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

2 个答案:

答案 0 :(得分:7)

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

  1. SQL不区分大小写,但它并不那么简单。引用的名字是字面意思。不带引号的名称可以自由解释。例如,PostgreSQL将不带引号的名称转换为小写,而H2将它们转换为大写。因此,PostgreSQL中的select * from MyTable_name查找表mytable_name。在H2中,相同的查询会查找MYTABLE_NAME。在我的例子中,PostgreSQL表是使用引用的名称" 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. 设置一个未记录的属性以引用所有表名和列名:spring.jpa.properties.hibernate.globally_quoted_identifiers = true(请参阅this comment)。最后一个是我做的,因为我也有列名,我需要区分大小写。
  4. 另一个令我感到困惑的原因是许多网站引用了旧的命名变量hibernate.ejb.naming_strategy或它的春天等价物。对于已经过时的Hibernate 5。相反,正如我在我的问题更新中提到的,Hibernate 5具有隐式和物理命名策略。

    此外,我很困惑,因为有hibernate属性,然后有Spring属性。我正在使用this very helpful tutorial。然而,它显示了不必要的直接使用hibernate属性(我在我的更新中列出),然后显式配置LocalContainerEntityManagerFactoryBean和JpaTransactionManager。更容易使用Spring属性并自动拾取它们。与我相关的是命名策略。

    1. spring.jpa.hibernate.naming.implicit策略
    2. spring.jpa.hibernate.naming.physical策略
    3. 要实现物理命名策略,我需要创建一个实现org.hibernate.boot.model.naming.PhysicalNamingStrategy的类,如上所述。引用名称实际上非常简单,因为传递给方法的Identifier类管理或不管理引用。因此,以下方法将引用表名。

      @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正确报告表名,我只是被其他一切搞糊涂了。
      4. 设置spring.jpa.show-sql = true以查看生成的SQL。非常有用的调试。允许我看到正在使用正确的表名
      5. spring.jpa.hibernate.ddl-auto至少支持以下值。 create-drop:在条目上创建表,在退出时删除。 create:在输入时创建表但在退出时离开。 none:不要创建或删除。我看到人们使用"更新"作为一种价值,但对我来说失败了。 (例如here。)这是discussion on the options
      6. 我使用引用的列名在H2中遇到了麻烦,但没有进一步调查。
      7. Spring properties page很有帮助,但说明非常稀疏。

答案 1 :(得分:1)

名称在实体注释中指定

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