title: Hibernate OneToMany with Mismatched Composite Primary Keys
body: <p>I have two tables in my database that are mapped together via composite primary key/foreign keys, and I'm having a hell of a time getting Hibernate to work with them. My database looks like this:</p>
  
  <p><img src="https://i.imgur.com/3U6Kja9.png" alt="Simple ERD"></p>
  
  <p>TABLE1 has a composite primary key of foreign keys, mapping to TABLE_A and TABLE_B. TABLE2 also has a composite primary key of foreign keys, mapping to TABLE_A, TABLE_B, and TABLE_D. In the database, TABLE2 maps back to TABLE1 using just the first two foreign keys. No problems there. It's translating this to Hibernate that is killing me.</p>
  
  <p>Because TABLE2 requires an embedded id with three columns, I cannot use the @OneToMany annotation's mappedBy parameter. I get the expected error of the number of foreign keys not matching the primary key columns. So, I utilized @JoinColumns instead. And that worked perfectly fine for saving new entities. However, when I attempt to delete some mappings from TABLE2, I run into an issue where Hibernate is attempting to update TABLE2 before deleting, setting FK_TABLE_A to null, which is obviously not allowed. The best I've been able to find is that the use of inverse="true" in a mapping xml might resolve the problem, ensuring that Hibernate knows that despite the use of @JoinColumn, the TABLE1 entity should be the owner of the relationship. But I'm not using the XML, and I cannot figure out what the equivalent might be via annotations.</p>
  
  <p>Here's what I have so far:</p>
  
  <pre><code>@Entity
  @AssociationOverrides({
      @AssociationOverride(name = "pk.tableA",
              joinColumns = @JoinColumn(name = "FK_TABLE_A")),
      @AssociationOverride(name = "pk.tableB",
              joinColumns = @JoinColumn(name = "FK_TABLE_B")) })
  @Table(name="TABLE1")
  public class Table1 extends BaseObject implements Serializable
  {
  private static final long serialVersionUID = 1L;
  
  private Table1Id pk = new Table1Id();
  
  @EmbeddedId
  public Table1Id getPk() {
      return pk;
  }
  public void setPk(Table1Id pk) {
      this.pk = pk;
  }
  
  private TableC tableC;
  @ManyToOne
  @JoinColumn(name = "FK_TABLE_C", referencedColumnName = "ID", nullable = true)
  public TableC getTableC () {
      return this.tableC;
  }
  public void setTableC(TableC tableC) {
      this.tableC= tableC;
  }
  
  private List&lt;Table2&gt; table2s;
  @OneToMany(cascade = {CascadeType.ALL}, orphanRemoval=true, fetch=FetchType.EAGER)
  @JoinColumns({
      @JoinColumn(name="FK_TABLE_A", referencedColumnName="FK_TABLE_A"),
      @JoinColumn(name="FK_TABLE_B", referencedColumnName="FK_TABLE_B")
  })
  public List&lt;Table2&gt; getTable2s() {
      return table2s;
  }
  public void setTable2s(List&lt;Table2&gt; table2s) {
      this.table2s= table2s;
  }
  
  @Override
  public boolean equals(Object o) {
      ...
  }
  
  @Override
  public int hashCode() {
      ...
  }
  
  @Override
  public String toString() {
      ...
  }
  }
  
  @Embeddable
  public class Table1Id extends BaseObject implements Serializable 
  {
      private static final long serialVersionUID = 1L;
  
      private TableA tableA;
      private TableB tableB;
  
      @ManyToOne
      public TableA getTableA() {
          return tableA;
      }
      public void setTableA(TableA tableA) {
          this.tableA = tableA;
      }
  
      @ManyToOne
      public TableB getTableB() {
          return tableB;
      }
      public void setTableB(TableB tableB) {
          this.tableB= tableB;
      }
  
      @Override
      public boolean equals(Object o) {
          ...
      }
  
      @Override
      public int hashCode() {
          ...
      }
  
      @Override
      public String toString() {
          ...
      }
  }
  
  
  @Entity
  @AssociationOverrides({
          @AssociationOverride(name = "pk.tableA",
                  joinColumns = @JoinColumn(name = "FK_TABLE_A")),
          @AssociationOverride(name = "pk.tableB",
                  joinColumns = @JoinColumn(name = "FK_TABLE_B")),
          @AssociationOverride(name = "pk.tableD",
                  joinColumns = @JoinColumn(name = "FK_TABLE_D")) })
  @Table(name="TABLE2")
  public class Table2 extends BaseObject implements Serializable
  {
      private static final long serialVersionUID = 1L;
  
      private Table2Id pk = new Table2Id ();
  
      @EmbeddedId
      public Table2Id getPk() {
          return pk;
      }
      public void setPk(Table2Id pk) {
          this.pk = pk;
      }
  
      private Double value;
      @Column(name = "VALUE", nullable = false, insertable = true, updatable = true, precision = 2)
      @Basic
      public Double getValue() {
          return this.value;
      }
      public void setValue(Double value) {
          this.goal = goal;
      }
  
      @Override
      public boolean equals(Object o) {
          ...
      }
  
      @Override
      public int hashCode() {
          ...
      }
  
      @Override
      public String toString() {
          ...
      }
  }
  
  @Embeddable
  public class Table2Id extends BaseObject implements Serializable 
  {
      private static final long serialVersionUID = 1L;
  
      private TableA tableA;
      @ManyToOne
      public TableA getTableA() {
          return tableA;
      }
      public void setTableA(TableA tableA) {
          this.tableA= tableA;
      }
  
      private TableB tableB;
      @ManyToOne
      public TableB getTableB() {
          return tableB;
      }
      public void setTableB(TableB tableB) {
          this.tableB= tableB;
      }
  
      private TableD tableD;
      @ManyToOne
      public TableD getTableD() {
          return this.tableD;
      }
      public void setTableD(TableD tableD) {
          this.tableD= tableD;
      }
  
      @Override
      public boolean equals(Object o) {
          ...
      }
  
      @Override
      public int hashCode() {
          ...
      }
  
      @Override
      public String toString() {
          ...
      }
  }
  </code></pre>
  
  <p>Normally for relationships like this, I just use the mappedBy value of the @OneToMany annotation, and everything works fine - updates, inserts, and deletes execute as expected and desired. But given the admittedly odd way the underlying tables are constructed, I cannot do that. Mapping to only a single record in the Table2Id (mappedBy="pk.tableA" or mappedBy="pk.tableB") would result in completely incorrect data. I require both fields to have an appropriate match, but near as I can tell I cannot have multiple columns listed in mappedBy. mappedBy="pk.tableA, pk.tableB" fails.</p>
  
  <p>I know that I can resolve this easily by just modifying the database and adding a single ID primary key to TABLE1, and a single FK_TABLE1 primary key to TABLE2. Then I could just use my standard approach of @OneToMany(mappedBy="table1"...). But I was really hoping to avoid that, if for no other reason that I obviously don't need to do that on the database level. I'm hoping there's a way to tell Hibernate that Table1 is the owner, and that all changes to Table2 are dependent on it.</p>