简体   繁体   中英

Spring JPA OneToOne relationship with a constant value

I am converting a legacy application to a Spring Boot, Groovy, JPA solution and found myself with a query in the form of:

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

As you can see, the join is perform on three fields, 2 of them that are based on data from table1 and a third field that is a constant string.

So, for what I googled around (ie http://docs.oracle.com/cd/E13189_01/kodo/docs40/full/html/ref_guide_mapping_notes_nonstdjoins.html ) I found that a solution would be:

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

When I run this I am getting a "Unable to find column with logical name: 'ABC' in table2" exception:

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]

However this solution only seems to work with a @OneToMany relationship, as pointed by the referred example.

I am trying to connect to a legacy Informix database for which changing the schema is completely out of the question. In addition to that, my build.gradle looks like this (the relevant portion anyways):

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

Has anyone run into a problem like this? Or what would be the correct approach to address situations like this?

Thanks a lot in advance for your help

I think you are mixing some terminology. JoinColumns are used with Many-To-Many relations. You are trying to build the join into the relation between entities, this is not how JPA works.

An Entity is a mapping of a table. In you legacy query table1 and table2 each have a field1 which has the same value and you use it in a query to create a join. This does not mean that Table1 has a one-To-One relation to Table2, it means that the two tables have a column that happens to have the same value (this could be a PK of a third entity). If two tables have a One-To-One relation, one of the tables will hold a foreign key column, containing the primary key of the related table.

You can still do a JPA query which will select the data, but it will not be pretty.

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();

As you can see this looks almost exactly your native SQL, but the result is a list of Object[] , each with two objects, the first is t1, the second is t2.

I have worked with JPA for a long time, and it is extremely rare to see Object[] as result type, it almost always means you have modelled something wrong.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM