JPA 3-way join annotation

Blessed Geek picture Blessed Geek · Oct 2, 2012 · Viewed 17.2k times · Source

There are three tables modeled as three entities:

@Entity
@Table(name="event")
public class Event {
  public Long datetime;
  public String name;
  public Long processId;
}

@Entity
@Table(name="process")
public class Process {
  public Long Id;
  public Long sequence;
  public Name name;
}

@Entity
@Table(name="operation")
public class Operation {
  public Long Id;
  public Long datetime;
  public Long sequence;
}

Any unique single record of process-operation-sequence is obtained by the SQL that has a 3-way join constraint:

SELECT *
FROM event e, process p, operation q 
WHERE e.processId = p.id
  AND e.datetime = q.datetime
  AND p.sequence = q.sequence

To implement that in JPA, I have to make a list of operations, which would be explicitly narrowed down to a single record thro the JQPL equality p.sequence = q.sequence

@Entity
@Table(name="event")
public class Event {
  public Long datetime;
  public String name;
  public Long processId;
  @OneToOne
  @JoinColumn(
    name = "processId", referencedColumnName="id",
    insertable=false, updatable=false)
  private Process process;

  @OneToMany
  @JoinColumn(
    name = "datetime", referencedColumnName="datetime",
    insertable=false, updatable=false)
  private List<Operation> operations;
}

Where the JPQL specifies the transitive 3rd join constraint:

SELECT e FROM Event e
INNER JOIN FETCH e.process p
INNER JOIN FETCH e.operations q
WHERE p.sequence = q.sequence

However, I want all three constraints to be modeled inside the entity POJO. Shouldn't there be way to use JPA annotations alone to three-way join? As the following entity pseudo-code illustrates:

@Entity
@Table(name="event")
public class Event {
  public Long datetime;
  public String name;
  public Long processId;
  @OneToOne
  @JoinColumn(
    name = "processId", referencedColumnName="id",
    insertable=false, updatable=false)
  private Process process;

  @OneToOne
  @JoinColumn(
    name = "datetime", referencedColumnName="datetime",
    insertable=false, updatable=false)
  @JoinColumn(
    name = "process.sequence", referencedColumnName="sequence",
    insertable=false, updatable=false)
  private Operation operations;
}

So that it would not be necessary to specify transitive-join constraint in the JPQL

SELECT e FROM Event e
INNER JOIN FETCH e.process p
INNER JOIN FETCH e.operations q

How do I model a transitive join using JPA annotations?

Answer

James picture James · Oct 3, 2012

You seem to be trying to model a query, instead of your data. You should model your data correctly, then write your query.

You seem to have

Event

  • process
  • ManyToOne (processId)

Process

  • events - OneToMany
  • operations - OneToMany

Operation

  • process - ManyToOne (sequence) (this one is a little odd, as sequence is not the Id, this is outside of the JPA spec, but some JPA provider may support it)

To query all operations for an event, you could use,

Select o from Operation o join o.process p join p.events e where e.datetime = o.datetime

To get all the object back use,

Select o, p, e from Operation o join o.process p join p.events e where e.datetime = o.datetime

If you really need to model the query as a relationship, this is outside of the JPA spec, but some JPA providers may support it. In EclipseLink you can use a DescriptorCustomizer to configure any relationship to use any expression criteria, or you own SQL.