【问题标题】:Spring Boot JPA does not prepend schema name to tables in querySpring Boot JPA 不会在查询中为表添加模式名称
【发布时间】:2018-04-28 01:11:54
【问题描述】:

据我了解(例如,来自 here),如果我为我的实体指定架构,那么它应该在创建查询时使用该架构名称。

所以,如果我有一个实体:

@Entity
@Table(name="proposalstatuses",schema="sales")
public class ProposalStatus implements Serializable {
    private static final long serialVersionUID = 1L;
    private int proposalStatusID;
    private String proposalStatusName;

    public ProposalStatus() {}

    public ProposalStatus(String proposalStatusName) {
        this.proposalStatusName = proposalStatusName;
    }

    @Id
    @Column(name="pk_proposalstatusid")
    @GeneratedValue(strategy = GenerationType.AUTO)
    public int getProposalStatusID() {
        return proposalStatusID;
    }

    public void setProposalStatusID(int proposalStatusID) {
        this.proposalStatusID = proposalStatusID;
    }

    @Column(name="proposalstatusname", unique=true, nullable=false)
    public String getProposalStatusName() {
        return proposalStatusName;
    }

    public void setProposalStatusName(String proposalStatusName) {
        this.proposalStatusName = proposalStatusName;
    }
}

那么我希望 Hibernate 生成它的查询,例如 select ... from sales.proposalstatuses。但是,我看到的是:

select proposalst0_.pk_proposalstatusid as pk_propo1_8_, proposalst0_.proposalstatusname as proposal2_8_ 
from proposalstatuses proposalst0_ 
order by proposalst0_.proposalstatusname asc

对于这种情况,这不是什么大不了的事,但现在我希望能够对不同模式中的表使用连接,但这是失败的,因为它认为表不存在(而且它们不存在)在默认架构中)。

所以,我有一个具有多个架构的数据库(一个连接)。如何让 Hibernate 在引用表时使用模式名称?看起来应该很简单,但我一定遗漏了一些东西。
谢谢!

我正在使用 Spring Boot 1.5.7,它使用 Hibernate JPA 2.1 和 Hibernate Core 5.0.12。 如果我使用 H2 数据源,这确实有效。如果 MySQL 是数据源,我只会看到问题。

我已阅读有关在我要引用的表的默认架构中创建视图的信息。但是,这不是一个可行的选择,因为我必须创建很多视图;看起来 Hibernate 应该能够轻松处理这个问题。

以下是 application.properties 中的配置设置:

spring.datasource.url=jdbc:mysql://localhost/sales?verifyServerCertificate=false&useSSL=true
spring.datasource.username=user
spring.datasource.password=pass
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1
spring.jpa.show-sql=true
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy

更改 url 中架构的名称允许我访问该架构中的数据,但除了 url 中列出的架构之外,我无法访问任何其他架构。

【问题讨论】:

    标签: hibernate jpa spring-boot


    【解决方案1】:

    我刚刚将一个应用程序从使用手工制作的EntityManager 配置迁移到 Spring Boot 2.0,然后这些问题立即出现。

    正确的做事方式是使用

    @Table(name="proposalstatuses",catalog = "sales")
    

    ...正如@suneet-khurana 上面所建议的那样。注意 MySQL 不支持 schema,需要使用 catalog 代替。

    但是,在我们的案例中,这需要更改大量实体,并且在短期内我想避免代码更改并使用配置来恢复将架构名称嵌入表名的旧行为。

    经过一番研究和调试,我发现显式设置以下属性可以解决问题:

    spring.jpa.properties.hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    

    看起来 Spring Boot JPA 中的默认实现是 SpringPhysicalNamingStrategy,它通过将所有点替换为下划线来清理名称。

    【讨论】:

      【解决方案2】:

      我缺少的关键在于配置和数据库类型。在 MySQL 中,数据库和模式之间没有真正的区别。使用 @Table 注释中的 schema 属性,它引用的是“真实”模式,但不是 MySQL 中定义的。这就解释了为什么 schema 属性适用于 H2 数据库而不适用于 MySQL。

      @Table(name="SCHEMA_NAME.TABLE_NAME") 注释最初不起作用,因为我的数据源 URL 中有一个默认架构。网址需要是

      spring.datasource.url=jdbc:mysql://localhost?verifyServerCertificate=false&useSSL=true
      

      有了这个改变,一切都可以在 MySQL 上运行。

      进一步说明,为了使其也适用于 H2,请确保您定义 hibernate.default_schema 属性。

      【讨论】:

        【解决方案3】:

        处理此问题的方法是为您的实体指定您的架构,这些实体存在名称冲突。

        使用

        @Table(name="TABLE_NAME", schema="SCHEMA_NAME")
        

        @Table(name="SCHEMA_NAME.TABLE_NAME")
        

        【讨论】:

        • 是的,第一个选项是我已经拥有它的方式。我先尝试了第二个选项,但这似乎也不起作用。
        • 如果我使用第二个选项,比如说在名为“testschema”的模式中名为“test”的表,那么它会给出一个错误,说 sales.testschema_test 不存在。因此,无论 Table 注释中指定了什么,它仍在尝试使用配置 url 中的模式。
        • 我需要在生产中与 DB2 交谈,在开发中与 H2 交谈。不幸的是,这些都不能同时在两者上工作。
        【解决方案4】:

        以下配置中无需删除默认数据库名称。

        spring.datasource.url=jdbc:mysql://localhost:3306/defaultDb?autoReconnect=true&useUnicode=true&connectionCollat​​ion=utf8_general_ci&characterSetResults=utf8&zeroDateTimeBehavior=convertToNull&verifyServerCertificate=false&useSSL=false&rewriteBatchedStatements=true&profileSQL=false

        我找到了两个选项来使用 Spring JPA 在 mysql 查询中动态添加数据库名称。

        1. 如果您将在@Table 注释中添加目录选项,那么目录选项将只考虑数据库名称,并且数据库名称将附加在您的查询中,例如“sales.proposalstatuses”。

          @Entity
          @Table(name="proposalstatuses",schema="sales",catalog = "sales")
          public class ProposalStatus implements Serializable {
          
        2. 在@Table 注释中可用的“名称”选项中附加数据库名称和表名称,如下所示。

          @Entity
          @Table(name="sales.proposalstatuses")
          public class ProposalStatus implements Serializable {
          

        我已经编辑了https://stackoverflow.com/users/1100847/tim 共享的上述示例,以上示例适用于我的 spring boot(2.0.0.RELEASE)、Sping JPA 和 mysql 数据库(6.0.6)

        【讨论】:

          【解决方案5】:

          在 Spring Boot 项目中处理休眠环境时,我偶然发现了同样的问题。我的审计表保存在一个单独的模式中,但由于我想将用户信息添加到修订信息中,我必须创建一个修订实体,默认情况下它会转到默认模式而不是审计模式。由于在这个项目中,开发服务器是 MySQL,但测试和生产服务器必须是 SQL Server,在 @Table 中指定 catalog 不是一个选项,因为 catalog 对 SQL Server 有不同的含义(而 according to this rejected bug report 这是预期的行为)。

          Sergei Ivanov 的方法给我带来了问题,因为他们使用的 NamingStrategy 采用不同的方法进行 CamelCase 转换,我不想为每个实体添加显式的 @Table(name="...") 注释。最后,我通过创建一个自定义 NamingStrategy 来采用他们的方法的一个改编版本,它扩展了默认值,并且仅覆盖表名转换,因此点不会被下划线替换:

          public class TableDotSpringPhysicalNamingStrategy extends SpringPhysicalNamingStrategy {
            public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
              return this.apply(name, jdbcEnvironment);
            }
          
            private Identifier apply(Identifier name, JdbcEnvironment jdbcEnvironment) {
              if (name == null) {
                return null;
              } else {
                StringBuilder builder = new StringBuilder(name.getText());
          
                for(int i = 1; i < builder.length() - 1; ++i) {
                  if (this.isUnderscoreRequired(builder.charAt(i - 1), builder.charAt(i), builder.charAt(i + 1))) {
                    builder.insert(i++, '_');
                  }
                }
          
                return this.getIdentifier(builder.toString(), name.isQuoted(), jdbcEnvironment);
              }
            }
          
            private boolean isUnderscoreRequired(char before, char current, char after) {
              return Character.isLowerCase(before) && Character.isUpperCase(current) && Character.isLowerCase(after);
            }
          }
          

          然后在我的application.properties中添加相关配置:

          spring.jpa.properties.hibernate.physical_naming_strategy=my.package.path.TableDotSpringPhysicalNamingStrategy
          

          这样,在不取消默认模式的情况下指定不同的模式可以独立于数据库是否为 MySQL。

          【讨论】:

            【解决方案6】:

            感谢您的所有回复,我想在调查结果中再补充一点

            1. 我们在@Column 注释的table 属性中给出的name 应该与我们在@Table 和@SecondaryTable 中给出的名称匹配 注解。
            2. 因此,当我们在 @Table 中添加模式名称时,请 确保我们在@Column 中这样做

            @Table(name="schema1.table")
            @SecondaryTables({
                @SecondaryTable(name = "schema2.table")
            })
            

            @Column( name = "col1", table = "schema2.table")
            

            注意:您不必为主表列指定表属性

            【讨论】:

              猜你喜欢
              • 2016-04-03
              • 2017-04-01
              • 1970-01-01
              • 2019-04-29
              • 1970-01-01
              • 2021-10-13
              • 2016-09-12
              • 1970-01-01
              • 2022-01-22
              相关资源
              最近更新 更多