13 votes

Comment joindre des tables sur des colonnes de clés non primaires dans des tables secondaires ?

J'ai besoin de joindre des tables sur un objet dans une hiérarchie de classes ORM où la colonne de jointure n'est PAS la clé primaire de la classe de base. Voici un exemple de table :

CREATE TABLE APP.FOO
(
    FOO_ID INTEGER NOT NULL,
    TYPE_ID INTEGER NOT NULL,
    PRIMARY KEY( FOO_ID )
)

CREATE TABLE APP.BAR
(
    FOO_ID INTEGER NOT NULL,
    BAR_ID INTEGER NOT NULL,
    PRIMARY KEY( BAR_ID ),
    CONSTRAINT bar_fk FOREIGN KEY( FOO_ID ) REFERENCES APP.FOO( FOO_ID )
)

CREATE TABLE APP.BAR_NAMES
(
    BAR_ID INTEGER NOT NULL,
    BAR_NAME VARCHAR(128) NOT NULL,
    PRIMARY KEY( BAR_ID, BAR_NAME),
    CONSTRAINT bar_names_fk FOREIGN KEY( BAR_ID ) REFERENCES APP.BAR( BAR_ID )
)

Et voici les mappings (getters et setters éliminés par souci de concision)

@Entity
@Table(name = "FOO")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE_ID", discriminatorType =    javax.persistence.DiscriminatorType.INTEGER)
public abstract class Foo {
    @Id
    @Column(name = "FOO_ID")
    private Long fooId;
}

@Entity
@DiscriminatorValue("1")
@SecondaryTable(name = "BAR", pkJoinColumns = { @PrimaryKeyJoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID") })
public class Bar extends Foo{
    @Column(table = "BAR", name = "BAR_ID")
    Long barId;
 }    

Comment puis-je ajouter le mappage pour BAR_NAMES étant donné que sa colonne de jonction n'est pas FOO_ID pero BAR_ID ?

J'ai essayé ce qui suit :

@CollectionOfElements(fetch = FetchType.LAZY)
@Column(name = "BAR_NAME")
@JoinTable(name = "BAR_NAMES", joinColumns = @JoinColumn(table = "BAR", name = "BAR_ID", referencedColumnName="BAR_ID"))
List<String> names = new ArrayList<String>();

Cette opération échoue parce que le code SQL utilisé pour récupérer l'objet Bar tente d'obtenir une valeur BAR_ID à partir de la table FOO. J'ai également essayé de remplacer l'annotation JoinTable par

@JoinTable(name = "BAR_NAMES", joinColumns = @JoinColumn(name = "BAR_ID"))

Cela ne produit pas d'erreur SQL, mais ne permet pas non plus de récupérer des données, car la requête sur BAR_NAMES utilise le FOO_ID comme valeur de jointure au lieu du BAR_ID.

À des fins de test, j'ai alimenté la base de données avec les commandes suivantes

insert into FOO (FOO_ID, TYPE_ID) values (10, 1);
insert into BAR (FOO_ID, BAR_ID) values (10, 20);
insert into BAR_NAMES (BAR_ID, BAR_NAME) values (20, 'HELLO');

De nombreuses solutions qui semblent fonctionner renvoient une collection vide lorsqu'elles obtiennent l'objet Foo pour l'ID 10 (par opposition à une collection contenant 1 nom).

7voto

Jherico Points 12554

J'ai trouvé une solution à ce problème. Si vous mappez la classe Bar comme suit

@Entity
@DiscriminatorValue("1")
@SecondaryTable(name = "BAR", pkJoinColumns = { @PrimaryKeyJoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID") })
public class Bar extends Foo {
    @OneToOne
    @JoinColumn(table = "BAR", name = "BAR_ID")
    MiniBar miniBar;
}

et ajoutez la classe suivante

@Entity
@SqlResultSetMapping(name = "compositekey", entities = @EntityResult(entityClass = MiniBar.class, fields = { @FieldResult(name = "miniBar", column = "BAR_ID"), }))
@NamedNativeQuery(name = "compositekey", query = "select BAR_ID from BAR", resultSetMapping = "compositekey")
@Table(name = "BAR")
public class MiniBar {
    @Id
    @Column(name = "BAR_ID")
    Long barId;
} 

Vous pouvez ensuite ajouter n'importe quel type de mappage à l'élément MiniBar comme si barId était la clé primaire, puis la rendre disponible dans la classe externe Bar classe.

2voto

matt b Points 73770

Je ne suis pas sûr de savoir comment faire avec JPA/Annotations, mais avec les fichiers de cartographie XML d'Hibernate, ce serait quelque chose comme :

<class name="Bar" table="BAR">
    <id name="id" type="int">
        <column name="BAR_ID"/>
        <generator class="native"/>
    </id>
    <set name="barNames" table="BAR_NAMES">
        <!-- Key in BAR_NAMES table to map to this class's key -->
        <key column="BAR_ID"/> 
        <!-- The value in the BAR_NAMES table we want to populate this set with -->
        <element type="string" column="BAR_NAME"/>
    </set>
</class>

2voto

ChssPly76 Points 53452

Vous ne pourrez pas faire ce que vous voulez. @CollectionOfElements (et @OneToMany, d'ailleurs) sont des fonctions de siempre mappé via la clé primaire de l'entité du propriétaire.

La façon dont vous avez fait correspondre l'héritage Foo / Bar est également assez étrange - il est clair qu'ils ne sont pas dans la même table ; il semble que l'utilisation de JoinedSubclass serait une meilleure approche. Gardez à l'esprit que cela ne vous aidera toujours pas à établir une carte. bar_names a bar_id car la valeur de la clé primaire est partagée par la hiérarchie (même si le nom de la colonne peut être différent pour la sous-classe).

Une alternative possible est d'utiliser @OneToOne entre Foo et Bar au lieu de l'héritage. C'est la seule façon d'établir une correspondance entre bar_names a bar_id et le mappage le plus approprié pour votre structure de table (mais peut-être pas pour votre modèle de domaine).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X