【问题标题】:Spring JPA OneToOne relationship with a constant valueSpring JPA OneToOne 具有恒定值的关系
【发布时间】:2017-01-10 16:34:51
【问题描述】:

我正在将遗留应用程序转换为 Spring Boot、Groovy、JPA 解决方案,并发现自己遇到了以下形式的查询:

SELECT ...
FROM table1, table2
WHERE table1.field1 = table2.field1 AND
  table1.field2 = table2.field2 and
  table2.field3 = 'ABC'

如您所见,连接是在三个字段上执行的,其中两个基于 table1 中的数据,第三个字段是常量字符串。

因此,对于我搜索的内容(即 http://docs.oracle.com/cd/E13189_01/kodo/docs40/full/html/ref_guide_mapping_notes_nonstdjoins.html ),我发现解决方案是:

@Entity
@Table(name = "Table1")
class Table1 {

  // other column definitions here

  @OneToOne(cascade = CascadeType.ALL)
  @JoinColumns([
    @JoinColumn(name="field1", referencedColumnName="field1"),
    @JoinColumn(name="field2", referencedColumnName="field2"),
    @JoinColumn(name="table2.field3", referencedColumnName="'ABC'")]
    )
  Table2 table2
}

当我运行此程序时,我收到“无法找到具有逻辑名称的列:table2 中的‘ABC’”异常:

Caused by: org.hibernate.MappingException: Unable to find column with logical name: 'ABC' in table2
    at org.hibernate.cfg.Ejb3JoinColumn.checkReferencedColumnsType(Ejb3JoinColumn.java:854) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
    at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:241) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
    at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:100) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processEndOfQueue(InFlightMetadataCollectorImpl.java:1786) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processFkSecondPassesInOrder(InFlightMetadataCollectorImpl.java:1730) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1617) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:278) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:847) ~[hibernate-entitymanager-5.0.9.Final.jar:5.0.9.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:874) ~[hibernate-entitymanager-5.0.9.Final.jar:5.0.9.Final]
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60) ~[spring-orm-4.3.2.RELEASE.jar:4.3.2.RELEASE]

但是,正如所引用的示例所指出的,此解决方案似乎仅适用于 @OneToMany 关系。

我正在尝试连接到完全不可能更改架构的旧 Informix 数据库。 除此之外,我的 build.gradle 看起来像这样(反正相关部分):

buildscript {
  repositories {
     // ...
  }
  dependencies { classpath('org.springframework.boot:spring-boot-gradle-plugin:1.4.0.RELEASE') }
}

apply plugin: 'groovy'
apply plugin: 'spring-boot'

defaultTasks 'clean', 'assemble'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
   // ...
}

dependencies {
  compile "org.springframework.boot:spring-boot-starter-web"
  compile "org.springframework.boot:spring-boot-starter-actuator"
  compile "org.springframework.boot:spring-boot-starter-data-jpa"
  compile "org.springframework:spring-jms"
  compile "org.apache.cxf:cxf-rt-frontend-jaxws:${cxfVersion}"
  compile "org.apache.cxf:cxf-rt-transports-http:${cxfVersion}"

  compile "org.codehaus.groovy:groovy-all:2.4.4"
  compile "informix:informix-jdbc:${informixVersion}"
  compile "informix:informix-jdbcx:${informixVersion}"

  testCompile "org.springframework.boot:spring-boot-starter-test"
  testCompile "org.spockframework:spock-core:${spockVersion}"
  testCompile "org.spockframework:spock-spring:${spockVersion}"
  testCompile "junit:junit:4.12"
  testCompile "org.springframework:spring-test"
}

有人遇到过这样的问题吗?或者解决这种情况的正确方法是什么?

非常感谢您的帮助

【问题讨论】:

  • 如果 @OneToMany works 然后将其映射为这样(使用列表),并通过提供访问/修改索引 0 处的列表元素的 getter 和 setter 来简单地从客户端代码中隐藏该实现细节跨度>

标签: java spring jpa groovy spring-boot


【解决方案1】:

我认为您混合了一些术语。 JoinColumns 用于多对多关系。您正在尝试将连接构建到实体之间的关系中,这不是 JPA 的工作方式。

实体是表的映射。在您的旧查询 table1 和 table2 中,每个都有一个 field1 具有相同的值,您在查询中使用它来创建连接。这并不意味着 Table1 与 Table2 具有一对一的关系,这意味着这两个表具有恰好具有相同值的列(这可能是第三个实体的 PK)。如果两个表具有一对一关系,则其中一个表将包含一个外键列,其中包含相关表的主键。

您仍然可以执行 JPA 查询来选择数据,但它不会很漂亮。

List<Object[]> resultList = em.createQuery(
   "select t1, t2 from Table1 t1, Table2 t2 
    where t1.field1 = t2.field1 and t1.field2 = t2.field2 
    and t2.field3='ABC'", Object[].class).getResultList();

您可以看到,这看起来几乎与您的原生 SQL 完全一样,但结果是一个 Object[] 列表,每个都有两个对象,第一个是 t1,第二个是 t2。

我与 JPA 合作了很长时间,很少看到 Object[] 作为结果类型,这几乎总是意味着你建模了一些错误。

【讨论】:

  • table2 的 PK 由 field1 + field2 + field3 组成,但由于某些我无法理解的原因,table1 不包含与 table2 中的 PK 100% 匹配的 FK,因此是第三个组件“必须”是一个常数值,以有效地从 table2 中检索一个且唯一一个与 table1 中的记录相关联的唯一记录。是的,另一个选择是 JPA 查询,这实际上是我试图避免的 :)
  • 好吧,不是我理解的更好。如果您有一个一对一的目标,其中目标有一个复合键,您应该使用 PrimaryKeyJoinColumn 而不是 JoinColumn,但我不知道是否其中一列是常量。看看stackoverflow.com/a/4626455/2433323
  • JoinColumns 用于多对多关系 - 和OneToOneManyToOne 和(单向)OneToMany您应该使用 PrimaryKeyJoinColumn- 也许在共享 PK 的情况下,但并非总是这样,您有共享 PK。
猜你喜欢
  • 1970-01-01
  • 2019-08-12
  • 2021-05-22
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多