We’ve been providing several examples so far, most of the examples are being provided weren’t use complex persistent entity states; we are using a primitive data types such (int, String, BigDecimal, Date and etc ..).
But what’s happened if you we’ve created an entity whose persistent states are other types (classes). No doubt that it should make the developed applications more easier rather enclose the entity states to be only primitive types. Such that situation is happened frequently, so the JPA specifier takes in its consideration in that cases.
This tutorial will cover @Embeddable, @Embedded and @EmbeddedId, in that the @Embeddable annotation is used to specify a class whose instances are stored as an intrinsic part of an owning entity and share the identity of the entity. Also, @Embedded is used to specify a persistent field or property of an entity whose value is an instance of an embeddable class. And finally the @EmbeddedId is applied to a persistent field or property of an entity class or mapped superclass to denote the composite primary key that is an embeddable class.
Let’s start discussing the @Embeddable, cause it’s the entry point for understanding the new concept.
@Embeddable
As we’ve mentioned before and from the definition of the @Embeddable, it is used to specify or define a class (Type) whose instances are stored as an intrinsic part of an owning entity and share the identity of the entity. So the @Embeddable annotation has never been used with the properties or attributes.
Anatomy of @Embeddable Annoation
The specification of the @Embeddable is:
@Target({TYPE}) @Retention (Runtime) public @interface Embeddable {}
- Target: Types
- Uses: @Embeddable
- Arguments: No argument provided
Classes Design
If you’ve read through the previous examples that provided before (See EclipseLink – JPA Tutorial), you’ll find an Employee entity, by adding a new property (not an association or relation; just a property) into Employee entity called (employeePeriod) which contains startDate and endDate as a primitive types. See Figure 1.0 that depict what should Employee and EmployeePeriod entities looks like.
Figure 1.0
- Employee entity does composite an EmployeePeriod type.
- EmployeePeriod type is used to declare the employeePeriod property within an Employee.
Employee Table
See Figure 1.2 that shows you the impact of adding an EmployeePeriod into Employee Table.
Figure 1.2
- A new EmployeeStartDate and EmployeeEndDate columns are added into Employee Table.
- The new columns are added into Employee Table, because of the Embeddable class (Type) persistent states are persisted into the owning entity.
EmployeePeriod Class (Type)
As you’ve noted at the classes design, the EmployeePeriod is an attribute defined within an Employee entity, so when Entity Manager comes to persist an Employee, it should take care of EmployeePeriod.
EmployeePeriod.java
package net.javabeat.eclipselink.data; import java.util.Date; import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.Temporal; import javax.persistence.TemporalType; @Embeddable public class EmployeePeriod { @Column(name="EmployeeStartDate") @Temporal(TemporalType.DATE) private Date startDate; @Column(name="EmployeeEndDate") @Temporal(TemporalType.DATE) private Date endDate; public Date getStartDate() { return startDate; } public void setStartDate(Date startDate) { this.startDate = startDate; } public Date getEndDate() { return endDate; } public void setEndDate(Date endDate) { this.endDate = endDate; } }
- The @Embeddable class (type) isn’t specify a Table, cause the the Embeddable class (type) should depends on the owning entity when it comes to persist.
- The properties that defined in the EmployeePeriod embeddable class (type) defines their mapping information and the columns that should be referred to using @Column.
- As defined properties are Date, so it is important to specify the @Temporal annotation.
Employee Entity With @Embedded
Employee.java
package net.javabeat.eclipselink.data; import java.util.List; import javax.persistence.Basic; import javax.persistence.CascadeType; import javax.persistence.DiscriminatorColumn; import javax.persistence.DiscriminatorValue; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.OneToOne; @Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="EmployeeType") @DiscriminatorValue(value="EMP") public class Employee { @Id private int employeeId; @Basic(optional=false) private String employeeName; @Embedded private EmployeePeriod employeePeriod; @OneToMany(mappedBy="employee", cascade=CascadeType.ALL) private List<Phone> phones; @ManyToMany(cascade=CascadeType.ALL) @JoinTable( joinColumns={@JoinColumn(name="employeeId")}, inverseJoinColumns={@JoinColumn(table="project",name="projectId"),@JoinColumn(table="localproject",name="projectId"),@JoinColumn(table="globalproject",name="projectId")} ) private List<Project> projects; @OneToOne(cascade=CascadeType.ALL) @JoinColumn(name="AddressId") private Address address; public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public int getEmployeeId() { return employeeId; } public void setEmployeeId(int employeeId) { this.employeeId = employeeId; } public String getEmployeeName() { return employeeName; } public void setEmployeeName(String employeeName) { this.employeeName = employeeName; } public List<Phone> getPhones() { return phones; } public void setPhones(List<Phone> phones) { this.phones = phones; } public List<Project> getProjects() { return projects; } public void setProjects(List<Project> projects) { this.projects = projects; } public EmployeePeriod getEmployeePeriod() { return employeePeriod; } public void setEmployeePeriod(EmployeePeriod employeePeriod) { this.employeePeriod = employeePeriod; } }
- The Employee defines a new property named (employeePeriod) that’s embeddable type.
- The properties of employeePeriod depend on Employee entity for specifying the table that they are going to persist into.
- The JPA doesn’t know if you were using an Embeddable class (type) or not, except if you were specifying the @Embedded.
- The Embedded notify the JPA implementation that, it has an Embeddable class (Type), so consider the mapping information in the Embeddable class in the context of the owning (Employee) entity.
Persistence Configuration Changes Required
Nothing needs a change, except the adding of new EmployeePeriod class (type) in the Persistence Context. For the detailed configuration, please read our previous tutorials.
<class>net.javabeat.eclipselink.data.EmployeePeriod</class>
Executable Application
JPAImpl.java
package net.javabeat.eclipselink; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import net.javabeat.eclipselink.data.Address; import net.javabeat.eclipselink.data.ContractDeveloper; import net.javabeat.eclipselink.data.Developer; import net.javabeat.eclipselink.data.DriverLicense; import net.javabeat.eclipselink.data.Employee; import net.javabeat.eclipselink.data.EmployeePeriod; import net.javabeat.eclipselink.data.FreelanceDeveloper; import net.javabeat.eclipselink.data.GlobalProject; import net.javabeat.eclipselink.data.ICDLComputerLicense; import net.javabeat.eclipselink.data.LocalProject; import net.javabeat.eclipselink.data.Phone; import net.javabeat.eclipselink.data.Project; public class JPAImpl { static EntityManagerFactory factory = null; static EntityManager em = null; static { factory = Persistence.createEntityManagerFactory("EclipseLink-JPA-Installation"); em = factory.createEntityManager(); } public static void main(String [] args){ // Begin a Transaction em.getTransaction().begin(); createEmployee(); // Commit em.getTransaction().commit(); } public static void createEmployee(){ // Create an address entity Address address = new Address(); address.setAddressId(2); address.setAddressCountry("United Kingdom"); address.setAddressCity("London"); // Create an employee entity Employee employee = new Employee(); employee.setEmployeeId(2); employee.setEmployeeName("John Smith"); // Create an Employee Period Instance EmployeePeriod period = new EmployeePeriod(); period.setStartDate(new Date()); period.setEndDate(new Date()); employee.setEmployeePeriod(period); // Associate the address with the employee employee.setAddress(address); // Create a Phone entity Phone firstPhone = new Phone(); firstPhone.setPhoneId(3); firstPhone.setPhoneNumber("+221 4050 615"); firstPhone.setEmployee(employee); // Create a new phone entity Phone secondPhone = new Phone(); secondPhone.setPhoneId(4); secondPhone.setPhoneNumber("+221 4050 619"); // Use the old employee entity secondPhone.setEmployee(employee); // Create a list of phone List<Phone> phones = new ArrayList<Phone>(); phones.add(firstPhone); phones.add(secondPhone); // Create a list of projects List<Project> projects = new ArrayList<Project>(); // Set the project into employee employee.setProjects(projects); // Set the phones into your employee employee.setPhones(phones); // Persist the employee em.persist(employee); } }
- If the Embeddable class (Type) is used within an Employee, so no overrides required on the mapping information that provided by the EmployeePeriod properties. But in case you’ve needed to use the EmployeePeriod in an entities rather Employee and these entities reference a different column names, so you should use an @OverrideAttributes annotation.
- Let’s assume that we’ve considered a columns that differ from the mapping information that’s provided in the EmplyeePeriod Embeddable class (type), in that you have to consider the @OverrideAttributes change the take place inside Employee entity. See the following snippet of code.
@Embedded @AttributesOverride({ @AttributeOverride(name="startDate",column=@Column(name="EMP_START")), @AttributeOverride(name="endDate",column=@Column(name="EMP_END")) }) private EmployeePeriod employeePeriod; // employeePeriod property
@EmbeddedId
- The EmbeddedId isn’t vary differs as a concept from the previous concepts that discussed before about Embedded and Embeddable, except it is covering a composite primary key point.
- The EmbeddedId annotation is applied to a persistent field or property of an entity class or mapped superclass to denote a composite primary key that is an embeddable class.
- Let’s assume that we’ve a new Embeddable class (Type) named (AddressPK), that’s should determine the primary key of the Address entity.(See previous examples that depict the Address entity by referring the eclipselink jpa tutorial).
AddressPK.java
package net.javabeat.eclipselink.data; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Embeddable; @Embeddable public class AddressPK implements Serializable{ @Column(name="AddressCountryId") private int addressCountryId; @Column(name="AddressCityId") private int addressCityId; @Column(name="AddressId") private int addressId; public int getAddressCountryId() { return addressCountryId; } public void setAddressCountryId(int addressCountryId) { this.addressCountryId = addressCountryId; } public int getAddressCityId() { return addressCityId; } public void setAddressCityId(int addressCityId) { this.addressCityId = addressCityId; } public int getAddressId() { return addressId; } public void setAddressId(int addressId) { this.addressId = addressId; } public boolean equals(Object obj){ if(obj instanceof AddressPK){ AddressPK addPK = (AddressPK) obj; if(this.addressId == addPK.getAddressId() && this.addressCountryId == addPK.getAddressCountryId() && this.addressCityId == addPK.getAddressCityId()){ return true; } } else { return false; } return false; } public int hashCode(){ return super.hashCode(); } }
- The AddressPK Embeddable class (Type) defines three properties and they are addressId, addressCountryId and addressCityId, so the Adress entities should be identified by the group of properties rather one addressId property.
- When the developer uses the Embeddable class (Type) for primary key, he should override equals and hashCode(). The JPA itself doesn’t accept the Embeddable class if it was used as primary key unless it ensure that the two methods are overridden.
- The AddressPK should implement the Serializable.
- By using AddressPK, we have been using a new version of primary key that could depend on to identify the Address entities. But if you’ve reviewed the eclipselink jpa previous examples, you should be realized that the Employee entity related to the Address entity by OneToOne association. Let’s see the required changes that should take place once the primary key of the Address has been changed.
Employee.java
package net.javabeat.eclipselink.data; import java.util.List; import javax.persistence.Basic; import javax.persistence.CascadeType; import javax.persistence.DiscriminatorColumn; import javax.persistence.DiscriminatorValue; import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.OneToOne; @Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="EmployeeType") @DiscriminatorValue(value="EMP") public class Employee { @Id private int employeeId; @Basic(optional=false) private String employeeName; @Embedded private EmployeePeriod employeePeriod; @OneToMany(mappedBy="employee", cascade=CascadeType.ALL) private List<Phone> phones; @ManyToMany(cascade=CascadeType.ALL) @JoinTable( joinColumns={@JoinColumn(name="employeeId")}, inverseJoinColumns={@JoinColumn(table="project",name="projectId"),@JoinColumn(table="localproject",name="projectId"),@JoinColumn(table="globalproject",name="projectId")} ) private List<Project> projects; // Updated Mapping information that took a consideration for changing the Address's primary key @OneToOne(cascade=CascadeType.ALL) @JoinColumns({ @JoinColumn(name="AddressId",referencedColumnName="AddressId"), @JoinColumn(name="EmployeeAddressCountryId",referencedColumnName="AddressCountryId"), @JoinColumn(name="EmployeeAddressCityId",referencedColumnName="AddressCityId")} ) private Address address; public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public int getEmployeeId() { return employeeId; } public void setEmployeeId(int employeeId) { this.employeeId = employeeId; } public String getEmployeeName() { return employeeName; } public void setEmployeeName(String employeeName) { this.employeeName = employeeName; } public List<Phone> getPhones() { return phones; } public void setPhones(List<Phone> phones) { this.phones = phones; } public List<Project> getProjects() { return projects; } public void setProjects(List<Project> projects) { this.projects = projects; } public EmployeePeriod getEmployeePeriod() { return employeePeriod; } public void setEmployeePeriod(EmployeePeriod employeePeriod) { this.employeePeriod = employeePeriod; } }
- The Employee defines a new mapping information cause the Address entity primary key has been changed.
- The Employee references three columns at the Address Table. (In the next section, you could see the Employee Table is also has been changed by adding a new two columns).
- The referencedColumnName isn’t mandatory when we are going to use the @JoinColumns.It does define the name of the column referenced by the foreign keys column defined in the Employee table. Employee Table has (AddressId, AddressCountryId, AddressCityId) as foreign keys.
Database Design
Figure 1.3 shows you the changes that made on the Employee and Address Table.
Figure 1.3
- Address Table add a new two columns (AddressCountryId and AddressCityId).
- Employee Table does reference the new columns that was added into Address Table.
Executable Application
package net.javabeat.eclipselink; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import net.javabeat.eclipselink.data.Address; import net.javabeat.eclipselink.data.AddressPK; import net.javabeat.eclipselink.data.ContractDeveloper; import net.javabeat.eclipselink.data.Developer; import net.javabeat.eclipselink.data.DriverLicense; import net.javabeat.eclipselink.data.Employee; import net.javabeat.eclipselink.data.EmployeePeriod; import net.javabeat.eclipselink.data.FreelanceDeveloper; import net.javabeat.eclipselink.data.GlobalProject; import net.javabeat.eclipselink.data.ICDLComputerLicense; import net.javabeat.eclipselink.data.LocalProject; import net.javabeat.eclipselink.data.Phone; import net.javabeat.eclipselink.data.Project; public class JPAImpl { static EntityManagerFactory factory = null; static EntityManager em = null; static { factory = Persistence.createEntityManagerFactory("EclipseLink-JPA-Installation"); em = factory.createEntityManager(); } public static void main(String [] args){ // Begin a Transaction em.getTransaction().begin(); createEmployee(); // Commit em.getTransaction().commit(); } public static void createEmployee(){ // Create an address entity Address address = new Address(); // Address Embeddable class (Type) instantiation AddressPK addressPK = new AddressPK(); addressPK.setAddressId(1); addressPK.setAddressCountryId(1); addressPK.setAddressCityId(1); address.setAddressId(addressPK); address.setAddressCountry("United Kingdom"); address.setAddressCity("London"); // Create an employee entity Employee employee = new Employee(); employee.setEmployeeId(2); employee.setEmployeeName("John Smith"); // Create an Employee Period Instance EmployeePeriod period = new EmployeePeriod(); period.setStartDate(new Date()); period.setEndDate(new Date()); employee.setEmployeePeriod(period); // Associate the address with the employee employee.setAddress(address); // Create a Phone entity Phone firstPhone = new Phone(); firstPhone.setPhoneId(3); firstPhone.setPhoneNumber("+221 4050 615"); firstPhone.setEmployee(employee); // Create a new phone entity Phone secondPhone = new Phone(); secondPhone.setPhoneId(4); secondPhone.setPhoneNumber("+221 4050 619"); // Use the old employee entity secondPhone.setEmployee(employee); // Create a list of phone List<Phone> phones = new ArrayList<Phone>(); phones.add(firstPhone); phones.add(secondPhone); // Create a list of projects List<Project> projects = new ArrayList<Project>(); // Set the project into employee employee.setProjects(projects); // Set the phones into your employee employee.setPhones(phones); // Persist the employee em.persist(employee); } }
Database Records That Are Persisted Using a New Primary Key
Figure 1.4 shows the results in of the executable application.
Summary
This tutorial experiment using of Embeddedable, Embedded and EmbeddedId annotations. In general the Embeddable annotation should define a new class (Type) that’s going to be persisted with the owning entity. The JPA implementation requires the developer to mention those properties that considered as an Embeddable class (Type). The EmbeddedId works at the same kind as the Embedded except the EmbeddedId defines an Embeddable primary key.