The following rules apply for composite primary keys: The primary key class must be public and must have a public no-arg constructor. If property-based access is used, the properties of the primary key class must be public or protected. The primary key class must be serializable .
A composite primary key is mapped using an Embeddable type in hibernate. We'll first create an Embeddable type called EmployeeIdentity containing the employeeId and companyId fields, and then create the Employee entity which will embed the EmployeeIdentity type.
A composite primary key, also called a composite key, is a combination of two or more columns to form a primary key for a table. In JPA, we have two options to define the composite keys: the @IdClass and @EmbeddedId annotations.
To map a composite key, you can use the EmbeddedId
or the IdClass
annotations. I know this question is not strictly about JPA but the rules defined by the specification also applies. So here they are:
2.1.4 Primary Keys and Entity Identity
...
A composite primary key must correspond to either a single persistent field or property or to a set of such fields or properties as described below. A primary key class must be defined to represent a composite primary key. Composite primary keys typically arise when mapping from legacy databases when the database key is comprised of several columns. The
EmbeddedId
andIdClass
annotations are used to denote composite primary keys. See sections 9.1.14 and 9.1.15....
The following rules apply for composite primary keys:
- The primary key class must be public and must have a public no-arg constructor.
- If property-based access is used, the properties of the primary key class must be public or protected.
- The primary key class must be
serializable
.- The primary key class must define
equals
andhashCode
methods. The semantics of value equality for these methods must be consistent with the database equality for the database types to which the key is mapped.- A composite primary key must either be represented and mapped as an embeddable class (see Section 9.1.14, “EmbeddedId Annotation”) or must be represented and mapped to multiple fields or properties of the entity class (see Section 9.1.15, “IdClass Annotation”).
- If the composite primary key class is mapped to multiple fields or properties of the entity class, the names of primary key fields or properties in the primary key class and those of the entity class must correspond and their types must be the same.
IdClass
The class for the composite primary key could look like (could be a static inner class):
public class TimePK implements Serializable {
protected Integer levelStation;
protected Integer confPathID;
public TimePK() {}
public TimePK(Integer levelStation, Integer confPathID) {
this.levelStation = levelStation;
this.confPathID = confPathID;
}
// equals, hashCode
}
And the entity:
@Entity
@IdClass(TimePK.class)
class Time implements Serializable {
@Id
private Integer levelStation;
@Id
private Integer confPathID;
private String src;
private String dst;
private Integer distance;
private Integer price;
// getters, setters
}
The IdClass
annotation maps multiple fields to the table PK.
EmbeddedId
The class for the composite primary key could look like (could be a static inner class):
@Embeddable
public class TimePK implements Serializable {
protected Integer levelStation;
protected Integer confPathID;
public TimePK() {}
public TimePK(Integer levelStation, Integer confPathID) {
this.levelStation = levelStation;
this.confPathID = confPathID;
}
// equals, hashCode
}
And the entity:
@Entity
class Time implements Serializable {
@EmbeddedId
private TimePK timePK;
private String src;
private String dst;
private Integer distance;
private Integer price;
//...
}
The @EmbeddedId
annotation maps a PK class to table PK.
@EmbeddedId
somehow communicates more clearly that the key is a composite key and IMO makes sense when the combined pk is either a meaningful entity itself or it reused in your code.@IdClass
is useful to specify that some combination of fields is unique but these do not have a special meaning.They also affect the way you write queries (making them more or less verbose):
with IdClass
select t.levelStation from Time t
with EmbeddedId
select t.timePK.levelStation from Time t
You need to use @EmbeddedId
:
@Entity
class Time {
@EmbeddedId
TimeId id;
String src;
String dst;
Integer distance;
Integer price;
}
@Embeddable
class TimeId implements Serializable {
Integer levelStation;
Integer confPathID;
}
Assuming you have the following database tables:
First, you need to create the @Embeddable
holding the composite identifier:
@Embeddable
public class EmployeeId implements Serializable {
@Column(name = "company_id")
private Long companyId;
@Column(name = "employee_number")
private Long employeeNumber;
public EmployeeId() {
}
public EmployeeId(Long companyId, Long employeeId) {
this.companyId = companyId;
this.employeeNumber = employeeId;
}
public Long getCompanyId() {
return companyId;
}
public Long getEmployeeNumber() {
return employeeNumber;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EmployeeId)) return false;
EmployeeId that = (EmployeeId) o;
return Objects.equals(getCompanyId(), that.getCompanyId()) &&
Objects.equals(getEmployeeNumber(), that.getEmployeeNumber());
}
@Override
public int hashCode() {
return Objects.hash(getCompanyId(), getEmployeeNumber());
}
}
With this in place, we can map the Employee
entity which uses the composite identifier by annotating it with @EmbeddedId
:
@Entity(name = "Employee")
@Table(name = "employee")
public class Employee {
@EmbeddedId
private EmployeeId id;
private String name;
public EmployeeId getId() {
return id;
}
public void setId(EmployeeId id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The Phone
entity which has a @ManyToOne
association to Employee
, needs to reference the composite identifier from the parent class via two @JoinColumn
mappings:
@Entity(name = "Phone")
@Table(name = "phone")
public class Phone {
@Id
@Column(name = "`number`")
private String number;
@ManyToOne
@JoinColumns({
@JoinColumn(
name = "company_id",
referencedColumnName = "company_id"),
@JoinColumn(
name = "employee_number",
referencedColumnName = "employee_number")
})
private Employee employee;
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
The primary key class must define equals and hashCode methods
http://www.laliluna.de/jpa-hibernate-guide/ch06s06.html
Looks like you are doing this from scratch. Try using available reverse engineering tools like Netbeans Entities from Database to at least get the basics automated (like embedded ids). This can become a huge headache if you have many tables. I suggest avoid reinventing the wheel and use as many tools available as possible to reduce coding to the minimum and most important part, what you intent to do.
Let's take a simple example. Let's say two tables named test
and customer
are there described as:
create table test(
test_id int(11) not null auto_increment,
primary key(test_id));
create table customer(
customer_id int(11) not null auto_increment,
name varchar(50) not null,
primary key(customer_id));
One more table is there which keeps the track of test
s and customer
:
create table tests_purchased(
customer_id int(11) not null,
test_id int(11) not null,
created_date datetime not null,
primary key(customer_id, test_id));
We can see that in the table tests_purchased
the primary key is a composite key, so we will use the <composite-id ...>...</composite-id>
tag in the hbm.xml
mapping file. So the PurchasedTest.hbm.xml
will look like:
<?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="entities.PurchasedTest" table="tests_purchased">
<composite-id name="purchasedTestId">
<key-property name="testId" column="TEST_ID" />
<key-property name="customerId" column="CUSTOMER_ID" />
</composite-id>
<property name="purchaseDate" type="timestamp">
<column name="created_date" />
</property>
</class>
</hibernate-mapping>
But it doesn't end here. In Hibernate we use session.load (entityClass
, id_type_object
) to find and load the entity using primary key. In case of composite keys, the ID object should be a separate ID class (in above case a PurchasedTestId
class) which just declares the primary key attributes like below:
import java.io.Serializable;
public class PurchasedTestId implements Serializable {
private Long testId;
private Long customerId;
// an easy initializing constructor
public PurchasedTestId(Long testId, Long customerId) {
this.testId = testId;
this.customerId = customerId;
}
public Long getTestId() {
return testId;
}
public void setTestId(Long testId) {
this.testId = testId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
@Override
public boolean equals(Object arg0) {
if(arg0 == null) return false;
if(!(arg0 instanceof PurchasedTestId)) return false;
PurchasedTestId arg1 = (PurchasedTestId) arg0;
return (this.testId.longValue() == arg1.getTestId().longValue()) &&
(this.customerId.longValue() == arg1.getCustomerId().longValue());
}
@Override
public int hashCode() {
int hsCode;
hsCode = testId.hashCode();
hsCode = 19 * hsCode+ customerId.hashCode();
return hsCode;
}
}
Important point is that we also implement the two functions hashCode()
and equals()
as Hibernate relies on them.
Using hbm.xml
<composite-id>
<!--<key-many-to-one name="productId" class="databaselayer.users.UserDB" column="user_name"/>-->
<key-property name="productId" column="PRODUCT_Product_ID" type="int"/>
<key-property name="categoryId" column="categories_id" type="int" />
</composite-id>
Using Annotation
Composite Key Class
public class PK implements Serializable{
private int PRODUCT_Product_ID ;
private int categories_id ;
public PK(int productId, int categoryId) {
this.PRODUCT_Product_ID = productId;
this.categories_id = categoryId;
}
public int getPRODUCT_Product_ID() {
return PRODUCT_Product_ID;
}
public void setPRODUCT_Product_ID(int PRODUCT_Product_ID) {
this.PRODUCT_Product_ID = PRODUCT_Product_ID;
}
public int getCategories_id() {
return categories_id;
}
public void setCategories_id(int categories_id) {
this.categories_id = categories_id;
}
private PK() { }
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
PK pk = (PK) o;
return Objects.equals(PRODUCT_Product_ID, pk.PRODUCT_Product_ID ) &&
Objects.equals(categories_id, pk.categories_id );
}
@Override
public int hashCode() {
return Objects.hash(PRODUCT_Product_ID, categories_id );
}
}
Entity Class
@Entity(name = "product_category")
@IdClass( PK.class )
public class ProductCategory implements Serializable {
@Id
private int PRODUCT_Product_ID ;
@Id
private int categories_id ;
public ProductCategory(int productId, int categoryId) {
this.PRODUCT_Product_ID = productId ;
this.categories_id = categoryId;
}
public ProductCategory() { }
public int getPRODUCT_Product_ID() {
return PRODUCT_Product_ID;
}
public void setPRODUCT_Product_ID(int PRODUCT_Product_ID) {
this.PRODUCT_Product_ID = PRODUCT_Product_ID;
}
public int getCategories_id() {
return categories_id;
}
public void setCategories_id(int categories_id) {
this.categories_id = categories_id;
}
public void setId(PK id) {
this.PRODUCT_Product_ID = id.getPRODUCT_Product_ID();
this.categories_id = id.getCategories_id();
}
public PK getId() {
return new PK(
PRODUCT_Product_ID,
categories_id
);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With