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:
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.
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.
Here's what I have so far:
@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<Table2> 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<Table2> getTable2s() {
return table2s;
}
public void setTable2s(List<Table2> 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() {
...
}
}
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.
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.