0

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 (i.e. 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

BobbaFett_666
  • 71
  • 1
  • 9
  • If a `@OneToMany `works then map it as such (using a list) and simply hide that implementation detail from client code by providing getters and setters which access/modify the list element at index 0 – Alan Hay Jan 10 '17 at 20:49

1 Answers1

-1

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.

Klaus Groenbaek
  • 4,820
  • 2
  • 15
  • 30
  • The PK at table2 is composed by field1 + field2 + field3, but for some reason that escapes me, table1 does not contain a FK that matches 100% with a PK in table2, in consequence the 3rd component "must" be a constant value to effectively retrieve one and only one unique record from table2 that associates with the record in table1. And yes, the other option would be the JPA Query, which is actually was I was trying to avoid :) – BobbaFett_666 Jan 10 '17 at 19:04
  • Okay, not I understand better. If you have a One-To-One where the target has a composite key, you should use PrimaryKeyJoinColumn and not JoinColumn, but I don't know if it if one of the columns is a constant. Take a look at http://stackoverflow.com/a/4626455/2433323 – Klaus Groenbaek Jan 10 '17 at 19:14
  • *JoinColumns are used with Many-To-Many relations* - and `OneToOne` and `ManyToOne` and (unidirectional) `OneToMany`. *you should use PrimaryKeyJoinColumn*- maybe in the case of a shared PK but its not always the case you have a shared PK. – Alan Hay Jan 10 '17 at 20:45