2016-11-21 1 views
2

J'ai une table parent, c'est-à-dire audit_log (parent) qui contient un seul identifiant de colonne. Pour un identifiant donné dans audit_log, j'ai une liste d'identifiants de fournisseurs. Je les stocke dans une table séparée audit_log_vendorid (table enfant). Je veux que la table enfant obtienne l'ID de la table parent comme l'une des colonnes (parent_id). Voici le schéma de la table.Hibernate JPA Mappage parent-enfant

audit_log

+-------+------------+------+-----+---------+-------+ 
| Field | Type  | Null | Key | Default | Extra | 
+-------+------------+------+-----+---------+-------+ 
| id | bigint(19) | NO | PRI | NULL |  | 
+-------+------------+------+-----+---------+-------+ 

audit_log_vendorid

+-----------+------------+------+-----+---------+----------------+ 
| Field  | Type  | Null | Key | Default | Extra   | 
+-----------+------------+------+-----+---------+----------------+ 
| id  | bigint(19) | NO | PRI | NULL | auto_increment | 
| vendor_id | bigint(19) | NO |  | NULL |    | 
| parent_id | bigint(19) | NO |  | NULL |    | 
+-----------+------------+------+-----+---------+----------------+ 

I'have défini mes cours de mise en veille prolongée comme suit

@Entity 
@Table(name="audit_log") 
public class AuditLog { 

private List<AuditVendorPair> vendorIDs; 


public AuditLog(List<AuditVendorPair> vendorIds) throws Exception { 
    this.vendorIDs = vendorIDs; 
} 

@OneToMany(cascade=CascadeType.ALL) 
@JoinTable(name = "audit_log_vendorid", 
    joinColumns = { @JoinColumn(name = "parent_id", referencedColumnName="id") }) 
public List<AuditVendorPair> getVendors() { 
    return vendorIDs; 
} 

@Id @Column(name="ID") 
public Long getId() { 
    return super.getId(); 
} 

public void setHostServices(List<AuditVendorPair> vendorIDs){ 
    this.vendorIDs = vendorIDs; 
} 

} 

Ma classe de cartographie mise en veille prolongée pour audit_log_vendorid est ci-dessous. Je passe un identificateur de fournisseur et j'attends que les deux autres champs soient remplis par hibernate. Le champ parent_id que je veux du champ "id" dans audit_log. Il est initialisé comme nul à partir de maintenant provoquant une exception de contrainte mysql.

@Entity 
@Table(name="audit_log_vendorid") 
public class AuditVendorPair { 

private Long id; 
private Long parent_id; 
private Long vendor_id; 
public AuditVendorPair(Long vendor_id){ 
    this.vendor_id = vendor_id; 
} 

@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
@Column(name="id") 
public Long getId(){ 
    return id; 
} 

public void setId(Long id){ 
    this.id = id; 
} 

@Column(name="vendor_id") 
public Long getVendorID() { 
    return vendor_id; 
} 

public void setVendorID(Long vendor_id){ 
    this.vendor_id = vendor_id; 
} 


@Column(name="parent_id") 
public Long getParentId() { 
    return parent_id; 
} 

public void setParentId(Long parentID){ 
    this.parent_id = parentID; 
} 
} 

Je suis curieux de savoir si mes annotations sont correctes. Je veux essentiellement que l'id de la table audit_log soit rempli dans le champ parent_id de la table audit_log_vendorid par hibernate.

Répondre

1

Non, ils ne sont pas corrects. audit_log_vendorid n'est pas une table de jointure. Une table de jointure est une table qui n'est pas mappée à une entité et qui contient les ID de deux entités associées, mappées à d'autres tables.

Vous ne devriez également pas avoir un champ parent_id dans le AuditVendorPair. Non seulement parce que cela ne respecte pas les conventions de nommage de Java, mais aussi parce qu'il devrait être remplacé par une référence à AuditLog, mappée avec ManyToOne. Bref, vous devriez avoir une association OneToMany bidirectionnelle, mappée comme expliqué au the documentation.

+0

Oui audit_log_vendorid n'est pas une table de jointure. Quelle est la raison de se référer à AuditLog, mappé avec ManyToOne? Je pensais que oneToMany unidirectionnel fonctionnerait. A l'aide de la clé étrangère, indiquons-nous d'hiberner qu'il doit extraire la valeur de parent_id à partir de la colonne référencée? Est-ce que les choses vont changer si ma clé primaire dans la table audit_log est plus que l'identifiant? – skeptic01

+0

Vous pouvez utiliser OneToMany unidirectionnel, mais la colonne parent_id dans audit_log_vendorid sera gérée par l'association OneToMany (vous aurez besoin de '@JoinColumn (" parent_id ")'), et ne doit pas être mappée une seconde fois dans l'entité AuditVendorPair. Si vous devez accéder au parent à partir de l'entité AuditVendorPair, il doit être bidirectionnel. –

1

Je pense que vous ignorez un concept clé dans JPA, qui est les entités sont des objets, donc vous n'auriez jamais une entité se référant à son parent en utilisant directement l'ID, vous référer à l'objet (JPA utilisera l'ID quand DB interroger le)

@Entity 
@Table(name="audit_log") 
public class AuditLog { 

    @OneToMany(cascade= CascadeType.ALL, mappedBy = "auditLog") 
    private Collection<AuditVendorPair> vendorIDs; 

    @Id @Column(name="id") 
    private Long id; 

    public AuditLog() { 
    } 

    public Collection<AuditVendorPair> getVendors() { 
     if (vendorIDs == null) { 
      vendorIDs = new ArrayList<>(); 
     } 
     return vendorIDs; 
    } 

    public long getId() { 
     return id; 
    } 
} 

et

@Entity 
@Table(name = "audit_log_vendorid") 
public class AuditVendorPair { 

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column(name = "id") 
    private Long id; 

    @JoinColumn(nullable = false, name = "parent_id") 
    @ManyToOne(optional = false) 
    private AuditLog auditLog; 

    @Column(name = "vendor_id") 
    private Long vendorId; 

    public AuditVendorPair() { 
    } 

    public long getVendorId() { 
     return vendorId; 
    } 

    public void setVendorId(long vendorId) { 
     this.vendorId = vendorId; 
    } 

    public AuditLog getAuditLog() { 
     return auditLog; 
    } 

    public void setAuditLog(AuditLog auditLog) { 
     this.auditLog = auditLog; 
    } 
} 

AuditVendorPair fait référence à l'aide AuditLog l'entité, et puisque c'est une relation d'entité, vous devez utiliser @JoinColumn pour spécifier le nom.

Quelques meilleures pratiques pour JPA/Hibernate

  • Aucun constructeur args est requis par la spécification.
  • N'utilisez pas de setter pour la clé primaire, sauf si vous l'avez générée en code.
  • Vous ne pouvez pas utiliser de primitives pour les champs. Toutefois, si la base de données impose NOT NULL, vous devez utiliser une primitive long dans les getters et les setters, afin que votre IDE puisse vous avertir au sujet de NPE, au lieu d'attendre l'échec de vos tests.
  • Utiliser Collection Non Liste, car la liste implique la commande.
  • L'utilisation Terminé permet de souligner les noms de champ, mais de les utiliser lors de la spécification du nom de la colonne.
  • Notez que la création de vérification et de collection nulle dans getVendors() permet d'éviter NPE lors de la création de l'objet. Cela signifie que vous pouvez utiliser la même logique pour ajouter un AuditVendorPair indépendamment du fait que le AuditLog vient juste d'être créé, ou s'il est chargé depuis le DB. Cela signifie également que je ne crée pas setVendors() sauf si j'ai besoin de remplacer la liste entière, ce qui est rarement le cas, et quand c'est le cas, vous devez généralement supprimer toutes les entités de la liste.
+0

Merci pour l'information. Je reçois ce qui suit lorsque j'essaie et persiste le AuditLog: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: La colonne 'audit_id' ne peut pas être nulle Dois-je ajouter une référence de clé étrangère de parent_id enfant à la colonne d'identification parent? – skeptic01

+0

Voir la réponse ci-dessous pour un exemple de code. –

0

Il semble que vous devriez lire un peu plus sur les principes derrière JPA. Voici un exemple JPA «brut» de ce à quoi ressemblera le code, si vous utilisez Spring-data et autowire @PersistanceUnit, vous n'avez pas besoin de gérer le entityManager vous-même.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("PU-Name"); 
EntityManager em = emf.createEntityManager(); 

try { 
    long primaryKey = 1L; // comes from somewhere else 
    long vendorId = 1L; // comes from somewhere 
    AuditLog log = em.find(AuditLog.class, primaryKey); // loaded from DB 

    AuditVendorPair pair = new AuditVendorPair(); 
    pair.setAuditLog(log); 
    pair.setVendorId(vendorId); 
    em.getTransaction().begin(); 
    em.persist(pair); 
    em.getTransaction().commit(); 
} finally { 
    em.close(); 
} 

Vous devez connecter les entités en code, et toutes les entités existantes doivent être chargées à partir de la base de données. Si vous stockez un nouveau AuditVendorPair, vous devez d'abord charger l'objet AuditLog, puis le définir sur le nouvel AuditVendorPair. Généralement, le fournisseur sera également une entité, il faudra donc le consulter.

Remarque: L'exemple ci-dessus ne conserve pas la relation bidirectionnelle entre AuditLog et AuditVendorPair, puisque AuditVendorPair n'est pas ajouté aux collections de fournisseurs dans AuditLog. Puisque la colonne définissant la relation est sur AuditVendorPair (qui est stockée) ce n'est pas un problème, car la prochaine fois que vous chargerez l'instance AuditLog à partir de la base de données, AuditVendorPair fera partie de la collection du fournisseur. Toutefois, si vous utilisez AuditLog après la fermeture du contexte de persistance, vous pouvez conserver la relation bidirectionnelle.

Remarque: l'exemple ci-dessus suppose que vous obtenez des demandes avec des clés primaires. En général, vous ne devez jamais exposer des clés primaires dans un système frontal. Dans nos systèmes, nous générons un champ unique (UUID) pour chaque entité exposée dans une interface utilisateur.