web-dev-qa-db-fra.com

JPA 2.0 plusieurs à plusieurs avec colonne supplémentaire

J'essaie de faire une relation ManyToMany dans JPA 2.0 (JBoss 7.1.1) avec une colonne supplémentaire (en gras, ci-dessous) dans la relation, comme:

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

Je ne voudrais pas avoir d'attributs en double, je voudrais donc appliquer la deuxième solution présentée dans http://giannigar.wordpress.com/2009/09/04/mapping-a-many-to-many -join-table-with-extra-column-using-jpa / . Mais je ne peux pas le faire fonctionner, je reçois plusieurs erreurs comme:

  1. La classe d'ID intégrée ne doit pas contenir de mappages de relations (en fait, la spécification le dit);
  2. Dans l'attribut 'employerDeliveryAgent', la valeur "mappée par" pk.deliveryAgent 'ne peut pas être résolue en attribut sur l'entité cible;
  3. Dans l'attribut 'employerDeliveryAgent', la valeur "mappée par" pk.employer 'ne peut pas être résolue en un attribut sur l'entité cible;
  4. Le type persistant d'attribut de substitution "pk.deliveryAgent" ne peut pas être résolu;
  5. Le type persistant d'attribut de substitution "pk.employer" ne peut pas être résolu;

Beaucoup de gens sur ce lien ont dit que cela fonctionnait bien, donc je suppose que quelque chose est différent dans mon environnement, peut-être la version JPA ou Hibernate. Ma question est donc la suivante: comment réaliser un tel scénario avec JPA 2.0 (Jboss 7.1.1/utiliser Hibernate comme implémentation JPA)? Et pour compléter cette question: dois-je éviter d'utiliser des clés composites et utiliser à la place un identifiant généré simplement et une contrainte unique?

Merci d'avance.

Obs .: Je n'ai pas copié mon code source ici car il s'agit essentiellement d'une copie de celui du lien ci-dessus, juste avec différentes classes et noms d'attributs, donc je suppose que ce n'est pas nécessaire.

18
Renan

Tout d'abord, vous devez générer une classe EmployerDeliveryAgentPK car elle a plusieurs PK:

@Embeddable
public class EmployerDeliveryAgentPK implements Serializable {

    @Column(name = "EMPLOYER_ID")
    private Long employer_id;

     @Column(name = "DELIVERY_AGENT_ID")
    private Long deliveryAgent_id;
}

Ensuite, vous devez créer une classe EmployerDeliveryAgent. Cette classe représente plusieurs à plusieurs tables de Employer et 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;    
}

Après cela, dans la classe Employer, vous devez ajouter:

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

Et dans la classe DeliveryAgent Vous devez ajouter:

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

C'est tout! Bonne chance!!

36
Erik Lucio

Les deux réponses de Eric Lucio et Renan ont aidé, mais l'utilisation des identifiants dans la table d'association est redondante. Vous avez à la fois les entités associées et leurs identifiants dans la classe. Ce n'est pas obligatoire. Vous pouvez simplement mapper l'entité associée dans la classe d'association avec le @Id sur le champ d'entité associé.

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

La classe d'association

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

Encore besoin de la classe d'association PK. Notez que les noms de champs doivent correspondre exactement aux noms de champ dans la classe d'association, mais les types doivent être le type de l'id dans le type associé.

public class EmployerDeliveryAgentId implements Serializable {

    private int employer;
    private int deliveryAgent;

    // getters/setters and most importantly equals() and hashCode()
}
38
Paul Samsotha

OK, je l'ai fait fonctionner sur la base de la solution disponible sur

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

Cette solution ne génère pas d'attributs en double sur la base de données, mais elle génère des attributs en double dans mes entités JPA (ce qui est très acceptable, car vous pouvez relayer le travail supplémentaire à un constructeur ou à une méthode - cela finit par être transparent). Les clés primaires et étrangères générées dans la base de données sont correctes à 100%.

Comme indiqué sur le lien, je n'ai pas pu utiliser @PrimaryKeyJoinColumn et j'ai plutôt utilisé @JoinColumn (name = "projectId", updatable = false, insertable = false, referencedColumnName = "id"). Une autre chose qui mérite d'être mentionnée: j'ai dû utiliser EntityManager.persist (association), ce qui manque dans l'exemple du lien.

Donc ma dernière solution est:

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

}
10
Renan