JPA 2.0 many-to-many com coluna extra

Estou tentando fazer um relacionamento ManyToMany no JPA 2.0 (JBoss 7.1.1) com uma coluna extra (em negrito, abaixo) no relacionamento, como:

Employer EmployerDeliveryAgent DeliveryAgent (id,...) (employer_id, deliveryAgent_id, **ref**) (id,...) 

Eu não gostaria de ter atributos duplicados, então gostaria de aplicar a segunda solução apresentada em http://giannigar.wordpress.com/2009/09/04/mapping-a-many-to-many-join-table -with-extra-column-using-jpa / . Mas eu não consigo fazer isso funcionar, eu recebo vários erros como:

  1. A class de ID incorporada não deve conter mapeamentos de relacionamento (na verdade, a especificação diz isso);
  2. No atributo ’employerDeliveryAgent’, o valor “mapeado por” ‘pk.deliveryAgent’ não pode ser resolvido para um atributo na entidade de destino;
  3. No atributo ’employerDeliveryAgent’, o valor “mapeado por” ‘pk.employer’ não pode ser resolvido para um atributo na entidade de destino;
  4. Tipo persistente de atributo de substituição “pk.deliveryAgent” não pode ser resolvido;
  5. O tipo persistente de atributo de substituição “pk.employer” não pode ser resolvido;

Muitas pessoas nesse link disseram que funcionava bem, então suponho que algo é diferente no meu ambiente, talvez na versão JPA ou Hibernate. Então, minha pergunta é: como eu alcanço esse cenário com o JPA 2.0 (Jboss 7.1.1 / usando o Hibernate como implementação do JPA)? E para complementar essa pergunta: devo evitar usar chaves compostas e, em vez disso, usar id gerada simples e uma restrição exclusiva?

Desde já, obrigado.

Obs .: Eu não copiei meu código fonte aqui porque é essencialmente uma cópia daquele no link acima, apenas com classs e atributos diferentes, então eu acho que não é necessário.

Primeiro de tudo Você precisa gerar uma class EmployerDeliveryAgentPK porque tem um múltiplo PK:

 @Embeddable public class EmployerDeliveryAgentPK implements Serializable { @Column(name = "EMPLOYER_ID") private Long employer_id; @Column(name = "DELIVERY_AGENT_ID") private Long deliveryAgent_id; } 

Em seguida, você precisa criar uma class EmployerDeliveryAgent . Esta class representa muitos para muitos tabela de Employer e 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; } 

Depois disso, na class Employer você precisa adicionar:

 @OneToMany(mappedBy = "deliveryAgent") private Set employerDeliveryAgent = new HashSet(); 

E na class DeliveryAgent você precisa adicionar:

 @OneToMany(mappedBy = "employer") private Set employer = new HashSet(); 

Isso é tudo! Boa sorte!!

Ambas as respostas de Eric Lucio e Renan ajudaram, mas o uso dos ids na tabela de associação é redundante. Você tem as entidades associadas e seus ids na class. Isso não é obrigatório. Você pode mapear simples a entidade associada na class de associação com o @Id no campo de entidade associada.

 @Entity public class Employer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @OneToMany(mappedBy = "employer") private List deliveryAgentAssoc; // other properties and getters and setters } @Entity public class DeliveryAgent { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @OneToMany(mappedBy = "deliveryAgent") private List employerAssoc; // other properties and getters and setters } 

A class de associação

 @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; } 

Ainda precisa da associação da class PK. Observe que os nomes dos campos devem corresponder exatamente aos nomes dos campos na class de associação, mas os tipos devem ser o tipo do id no tipo associado.

 public class EmployerDeliveryAgentId implements Serializable { private int employer; private int deliveryAgent; // getters/setters and most importantly equals() and hashCode() } 

OK, consegui trabalhar com base na solução disponível em

http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#Mapping_a_Join_Table_with_Additional_Columns .

Essa solução não gera atributos duplicados no database, mas gera atributos duplicados em minhas entidades JPA (o que é muito aceitável, já que você pode retransmitir o trabalho extra para um construtor ou método – ele acaba sendo transparente). As chaves primária e externa geradas no database são 100% corretas.

Como indicado no link, não pude usar @PrimaryKeyJoinColumn e, em vez disso, usei @JoinColumn (name = “projectId”, atualizável = false, insertable = false, referencedColumnName = “id”). Outra coisa que vale a pena mencionar: Eu tive que usar EntityManager.persist (associação), que está faltando no exemplo no link.

Então minha solução final é:

 @Entity public class Employee { @Id private long id; ... @OneToMany(mappedBy="employee") private List projects; ... } @Entity public class Project { @PersistenceContext EntityManager em; @Id private long id; ... @OneToMany(mappedBy="project") private List 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; } }