I'm trying to do a ManyToMany relationship in JPA 2.0 (JBoss 7.1.1) with an extra column (in bold, below) in the relationship, like:
Employer EmployerDeliveryAgent DeliveryAgent
(id,...) (employer_id, deliveryAgent_id, **ref**) (id,...)
I wouldn't like to have duplicate attributes, so I would like to apply the second solution presented in http://giannigar.wordpress.com/2009/09/04/mapping-a-many-to-many-join-table-with-extra-column-using-jpa/ . But I can't get it to work, I get several errors like:
Many people on that link said that it worked fine, so I suppose something is different in my environment, perhaps JPA or Hibernate version. So my question is: how do I achieve such scenario with JPA 2.0 (Jboss 7.1.1 / using Hibernate as JPA implementation)? And to complement that question: should I avoid using composite keys and instead use plain generated id and a unique constraint?
Thanks in advance.
Obs.: I didn't copy my source code here because it is essentially a copy of the one at the link above, just with different classes and attributes names, so I guess it is not necessary.
Both answers from Eric Lucio and Renan helped, but their use of the ids in the association table is redundant. You have both the associated entities and their ids in the class. This is not required. You can simply map the associated entity in the association class with the @Id
on the associated entity field.
@Entity public class Employer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @OneToMany(mappedBy = "employer") private List<EmployerDeliveryAgent> deliveryAgentAssoc; // other properties and getters and setters } @Entity public class DeliveryAgent { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @OneToMany(mappedBy = "deliveryAgent") private List<EmployerDeliveryAgent> employerAssoc; // other properties and getters and setters }
The association class
@Entity @Table(name = "employer_delivery_agent") @IdClass(EmployerDeliveryAgentId.class) public class EmployerDeliveryAgent { @Id @ManyToOne @JoinColumn(name = "employer_id", referencedColumnName = "id") private Employer employer; @Id @ManyToOne @JoinColumn(name = "delivery_agent_id", referencedColumnName = "id") private DeliveryAgent deliveryAgent; @Column(name = "is_project_lead") private boolean isProjectLead; }
Still need the association PK class. Notice the field names should correspond exactly to the field names in the association class, but the types should be the type of the id in the associated type.
public class EmployerDeliveryAgentId implements Serializable { private int employer; private int deliveryAgent; // getters/setters and most importantly equals() and hashCode() }
First of all You need to generate a EmployerDeliveryAgentPK
class because It has a multiple PK:
@Embeddable public class EmployerDeliveryAgentPK implements Serializable { @Column(name = "EMPLOYER_ID") private Long employer_id; @Column(name = "DELIVERY_AGENT_ID") private Long deliveryAgent_id; }
Next, You need to create a EmployerDeliveryAgent
class. This class represent the relation many to many between Employer
and DeliveryAgent
:
@Entity @Table(name = "EmployerDeliveryAgent") public class EmployerDeliveryAgent implements Serializable { @EmbeddedId private EmployerDeliveryAgentPK id; @ManyToOne @MapsId("employer_id") //This is the name of attr in EmployerDeliveryAgentPK class @JoinColumn(name = "EMPLOYER_ID") private Employer employer; @ManyToOne @MapsId("deliveryAgent_id") @JoinColumn(name = "DELIVERY_AGENT_ID") private DeliveryAgent deliveryAgent; }
After that, in Employer
class You need to add:
@OneToMany(mappedBy = "deliveryAgent") private Set<EmployerDeliveryAgent> employerDeliveryAgent = new HashSet<EmployerDeliveryAgent>();
And in DeliveryAgent
class You need to add:
@OneToMany(mappedBy = "employer") private Set<EmployerDeliveryAgent> employer = new HashSet<EmployerDeliveryAgent>();
This is all! Good luck!!
OK, I got it working based on the solution available at
http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#Mapping_a_Join_Table_with_Additional_Columns.
This solution does not generate duplicate attributes on the database, but it does generate duplicate attributes in my JPA entities (which is very acceptable, since you can relay the extra work to a constructor or method - it ends up being transparent). The primary and foreign keys generated in the database are 100% correct.
As stated on the link, I couldn't use @PrimaryKeyJoinColumn and instead used @JoinColumn(name = "projectId", updatable = false, insertable = false, referencedColumnName = "id"). Another thing worth mentioning: I had to use EntityManager.persist(association), which is missing on the example at the link.
So my final solution is:
@Entity
public class Employee {
@Id
private long id;
...
@OneToMany(mappedBy="employee")
private List<ProjectAssociation> projects;
...
}
@Entity
public class Project {
@PersistenceContext
EntityManager em;
@Id
private long id;
...
@OneToMany(mappedBy="project")
private List<ProjectAssociation> employees;
...
// Add an employee to the project.
// Create an association object for the relationship and set its data.
public void addEmployee(Employee employee, boolean teamLead) {
ProjectAssociation association = new ProjectAssociation();
association.setEmployee(employee);
association.setProject(this);
association.setEmployeeId(employee.getId());
association.setProjectId(this.getId());
association.setIsTeamLead(teamLead);
em.persist(association);
this.employees.add(association);
// Also add the association object to the employee.
employee.getProjects().add(association);
}
}
@Entity
@Table(name="PROJ_EMP")
@IdClass(ProjectAssociationId.class)
public class ProjectAssociation {
@Id
private long employeeId;
@Id
private long projectId;
@Column(name="IS_PROJECT_LEAD")
private boolean isProjectLead;
@ManyToOne
@JoinColumn(name = "employeeId", updatable = false, insertable = false,
referencedColumnName = "id")
private Employee employee;
@ManyToOne
@JoinColumn(name = "projectId", updatable = false, insertable = false,
referencedColumnName = "id")
private Project project;
...
}
public class ProjectAssociationId implements Serializable {
private long employeeId;
private long projectId;
...
public int hashCode() {
return (int)(employeeId + projectId);
}
public boolean equals(Object object) {
if (object instanceof ProjectAssociationId) {
ProjectAssociationId otherId = (ProjectAssociationId) object;
return (otherId.employeeId == this.employeeId)
&& (otherId.projectId == this.projectId);
}
return false;
}
}
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