J'ai deux entités que je voudrais joindre à travers plusieurs colonnes. Ces colonnes sont partagées par un @Embeddable
objet partagé par les deux entités. Dans l'exemple ci-dessous, Foo
ne peut avoir qu'un Bar
mais Bar
peut avoir plusieurs Foo
s (où AnEmbeddableObject
est une clé unique pour Bar
). Voici un exemple:
@Entity
@Table(name = "foo")
public class Foo {
@Id
@Column(name = "id")
@GeneratedValue(generator = "seqGen")
@SequenceGenerator(name = "seqGen", sequenceName = "FOO_ID_SEQ", allocationSize = 1)
private Long id;
@Embedded
private AnEmbeddableObject anEmbeddableObject;
@ManyToOne(targetEntity = Bar.class, fetch = FetchType.LAZY)
@JoinColumns( {
@JoinColumn(name = "column_1", referencedColumnName = "column_1"),
@JoinColumn(name = "column_2", referencedColumnName = "column_2"),
@JoinColumn(name = "column_3", referencedColumnName = "column_3"),
@JoinColumn(name = "column_4", referencedColumnName = "column_4")
})
private Bar bar;
// ... rest of class
}
Et la classe Bar:
@Entity
@Table(name = "bar")
public class Bar {
@Id
@Column(name = "id")
@GeneratedValue(generator = "seqGen")
@SequenceGenerator(name = "seqGen", sequenceName = "BAR_ID_SEQ", allocationSize = 1)
private Long id;
@Embedded
private AnEmbeddableObject anEmbeddableObject;
// ... rest of class
}
Enfin la classe AnEmbeddedObject
:
@Embeddable
public class AnEmbeddedObject {
@Column(name = "column_1")
private Long column1;
@Column(name = "column_2")
private Long column2;
@Column(name = "column_3")
private Long column3;
@Column(name = "column_4")
private Long column4;
// ... rest of class
}
Evidemment le schéma est mal normalisé, c'est une restriction que les champs de AnEmbeddedObject
soient répétés dans chaque table.
Le problème que j'ai est que je reçois cette erreur lorsque j'essaie de démarrer Hibernate:
org.hibernate.AnnotationException: referencedColumnNames(column_1, column_2, column_3, column_4) of Foo.bar referencing Bar not mapped to a single property
J'ai essayé de marquer que les JoinColumns ne sont pas insérables et modifiables, mais sans succès. Existe-t-il un moyen d'exprimer cela avec des annotations Hibernate/JPA?
Cela a fonctionné pour moi. Dans mon cas, 2 tableaux foo et boo doivent être joints sur la base de 3 colonnes différentes.Veuillez noter que dans mon cas, les 3 colonnes communes ne sont pas la clé primaire
c'est-à-dire un mappage un à un basé sur 3 colonnes différentes
@Entity
@Table(name = "foo")
public class foo implements Serializable
{
@Column(name="foocol1")
private String foocol1;
//add getter setter
@Column(name="foocol2")
private String foocol2;
//add getter setter
@Column(name="foocol3")
private String foocol3;
//add getter setter
private Boo boo;
private int id;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "brsitem_id", updatable = false)
public int getId()
{
return this.id;
}
public void setId(int id)
{
this.id = id;
}
@OneToOne
@JoinColumns(
{
@JoinColumn(updatable=false,insertable=false, name="foocol1", referencedColumnName="boocol1"),
@JoinColumn(updatable=false,insertable=false, name="foocol2", referencedColumnName="boocol2"),
@JoinColumn(updatable=false,insertable=false, name="foocol3", referencedColumnName="boocol3")
}
)
public Boo getBoo()
{
return boo;
}
public void setBoo(Boo boo)
{
this.boo = boo;
}
}
@Entity
@Table(name = "boo")
public class Boo implements Serializable
{
private int id;
@Column(name="boocol1")
private String boocol1;
//add getter setter
@Column(name="boocol2")
private String boocol2;
//add getter setter
@Column(name="boocol3")
private String boocol3;
//add getter setter
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "item_id", updatable = false)
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
}
Si cela ne fonctionne pas, je suis à court d'idées. De cette façon, vous obtenez les 4 colonnes dans les deux tables (comme Bar
les possède et Foo
les utilise pour référencer Bar
) et les ID générés dans les deux entités. L'ensemble de 4 colonnes doit être unique dans Bar
pour que la relation plusieurs-à-un ne devienne pas un plusieurs-à-plusieurs.
@Embeddable
public class AnEmbeddedObject
{
@Column(name = "column_1")
private Long column1;
@Column(name = "column_2")
private Long column2;
@Column(name = "column_3")
private Long column3;
@Column(name = "column_4")
private Long column4;
}
@Entity
public class Foo
{
@Id
@Column(name = "id")
@GeneratedValue(generator = "seqGen")
@SequenceGenerator(name = "seqGen", sequenceName = "FOO_ID_SEQ", allocationSize = 1)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns({
@JoinColumn(name = "column_1", referencedColumnName = "column_1"),
@JoinColumn(name = "column_2", referencedColumnName = "column_2"),
@JoinColumn(name = "column_3", referencedColumnName = "column_3"),
@JoinColumn(name = "column_4", referencedColumnName = "column_4")
})
private Bar bar;
}
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {
"column_1",
"column_2",
"column_3",
"column_4"
}))
public class Bar
{
@Id
@Column(name = "id")
@GeneratedValue(generator = "seqGen")
@SequenceGenerator(name = "seqGen", sequenceName = "BAR_ID_SEQ", allocationSize = 1)
private Long id;
@Embedded
private AnEmbeddedObject anEmbeddedObject;
}
Hibernate ne vous permettra pas de faire ce que vous essayez de faire facilement. De la documentation Hibernate :
Notez que lorsque vous utilisez referencedColumnName dans une colonne de clé non primaire, la classe associée doit être sérialisable. Notez également que le ReferencedColumnName à une colonne de clé non primaire doit être mappé à une propriété ayant une seule colonne (d'autres cas peuvent ne pas fonctionner). (italique ajouté)
Donc, si vous ne souhaitez pas faire de AnEmbeddableObject
l'identifiant de la barre, Hibernate ne va pas paresseusement, récupérer automatiquement la barre pour vous. Bien sûr, vous pouvez toujours utiliser HQL pour écrire des requêtes qui se joignent à AnEmbeddableObject
, mais vous perdez la récupération automatique et la maintenance du cycle de vie si vous insistez pour utiliser une clé non primaire à plusieurs colonnes pour Bar.