Recently, We faced a tricky situation with hibernate association mapping with a composite id field. We needed to have bidirectional association with one-to-may and many-to-one.Our tow tables was "REPORT" and "REPORT_SUMMARY" which has one-to-many relationship from REPORT to REPORT_SUMMARY and many-to-one relationship from REPORT_SUMMARY to REPORT table. The primary key of REPORT_SUMMARY table is defined as a composite primary key which consists of auto increment id field and the primary key of REPORT table.
CREATE TABLE REPORT (
ID INT(10) NOT NULL AUTO_INCREMENT,
NAME VARCHAR(45) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE REPORT_SUMMARY (
ID INT(10) NOT NULL AUTO_INCREMENT,
NAME VARCHAR(45) NOT NULL,
RPT_ID INT(10) NOT NULL,
PRIMARY KEY (`ID`,`RPT_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
The hibernate entity classes are as fallows.
Report.java
package com.semika.autoac.entities;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
public class Report implements Serializable{
private static final long serialVersionUID = 9146156921169669644L;
private Integer id;
private String name;
private Set<ReportSummary> reportSummaryList = new HashSet<ReportSummary>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<ReportSummary> getReportSummaryList() {
return reportSummaryList;
}
public void setReportSummaryList(Set<ReportSummary> reportSummaryList) {
this.reportSummaryList = reportSummaryList;
}
}
ReportSummary.java
package com.semika.autoac.entities;
import java.io.Serializable;
public class ReportSummary implements Serializable {
private static final long serialVersionUID = 8052962961003467437L;
private ReportSummaryId id;
private String name;
public ReportSummaryId getId() {
return id;
}
public void setId(ReportSummaryId id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReportSummary other = (ReportSummary) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
ReportSummaryId.java
package com.semika.autoac.entities;
import java.io.Serializable;
public class ReportSummaryId implements Serializable{
private static final long serialVersionUID = 6911616314813390449L;
private Integer id;
private Report report;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Report getReport() {
return report;
}
public void setReport(Report report) {
this.report = report;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((report == null) ? 0 : report.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReportSummaryId other = (ReportSummaryId) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (report == null) {
if (other.report != null)
return false;
} else if (!report.equals(other.report))
return false;
return true;
}
}
Report object has a collection of ReportSummary objects and ReportSummaryId has a reference to Report object.
The most important part of this implementation is hibernate mapping files.
Report.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.semika.autoac.entities.Report" table="REPORT" >
<id name="id" type="int" column="id" >
<generator class="native"/>
</id>
<property name="name">
<column name="NAME" />
</property>
<set name="reportSummaryList" table="REPORT_SUMMARY" cascade="all" inverse="true">
<key column="RPT_ID" not-null="true"></key>
<one-to-many class="com.semika.autoac.entities.ReportSummary"/>
</set>
</class>
</hibernate-mapping>
ReportSummary.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.semika.autoac.entities.ReportSummary" table="REPORT_SUMMARY" >
<composite-id name="id" class="com.semika.autoac.entities.ReportSummaryId">
<key-property name="id" column="ID"></key-property>
<key-many-to-one name="report"
class="com.semika.autoac.entities.Report"
column="RPT_ID"</key-many-to-one>
</composite-id>
<property name="name">
<column name="NAME" />
</property>
</class>
</hibernate-mapping>