Java >> Java tutorial >  >> Tag >> class

Polymorfe associationskortlægninger af uafhængige klasser

JPA og Hibernate gør det meget nemt at modellere associationer mellem enheder. Du kan modellere associationer mellem 2 konkrete klasser eller modellere en polymorf association til et arvehierarki. Disse kortlægninger er mere end tilstrækkelige til næsten alle dine foreningskortlægninger. Men nogle gange vil du måske modellere en polymorf tilknytning til uafhængige enhedsklasser.

Desværre kan JPA ikke modellere denne slags foreninger uden nogen løsning. Men hvis du bruger Hibernate, kan du nemt modellere sådanne tilknytninger ved hjælp af Hibernates proprietære @Any foreningskortlægning.

Modellere en polymorf @Any og @ManyToAny foreninger

En polymorf associationskortlægning understøttet af JPA-specifikationen kræver, at dine klasser tilhører det samme arvehierarki. Det er ikke tilfældet, hvis du bruger Hibernates @Any kortlægning. Men disse klasser skal stadig have noget til fælles. Alle skal implementere den samme grænseflade.

Uafhængige enhedstilknytninger

I eksemplet med denne artikel er grænsefladen, som alle enheder implementerer, Afspilleren interface. Det er en meget grundlæggende grænseflade, der definerer 2 getter-metoder til at få antallet af sejre og tab for en spiller.

public interface Player {
    Integer getWins();

    Integer getLoses();
}

Enhedsklasserne ChessPlayer og MonopolyPlayer implementere spillergrænsefladen. Som du kan se i de følgende kodestykker, definerer hver sin egen, helt uafhængige kortlægning.

@Entity
public class ChessPlayer implements Player {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "chess_player_seq")
    @SequenceGenerator(name = "chess_player_seq", sequenceName = "chess_player_seq", initialValue = 100)
    private Long id;

    private String firstName;

    private String lastName;

    private Integer wins;

    private Integer loses;


    ...
}
@Entity
public class MonopolyPlayer implements Player {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "monopoly_player_seq")
    @SequenceGenerator(name = "monopoly_player_seq", sequenceName = "monopoly_player_seq", initialValue = 100)
    private Long id;

    private String firstName;

    private String lastName;

    private Integer wins;

    private Integer loses;

    ...
}

Ved at bruge JPAs standardtilknytningstilknytninger kunne du kun henvise til hver klasse i sin egen uafhængige foreningskortlægning.

Brug af Hibernates proprietære @Any , @ManyToAny, og @AnyMetaDef annoteringer, kan du modellere en polymorf tilknytning til en eller flere entiteter, der implementerer den samme grænseflade.

Definition af tilknytningsmetadata

Hvis din kortlægning kan referere til forskellige typer enheder, har du brug for mere end blot den primære nøgleværdi for at bevare din tilknytning. Du skal også gemme den type enhed, du refererer til. Denne information er defineret af en @AnyMetaDef annotation, som du henviser til i din @Any og @ManyToAny foreningskortlægning. Lad os se nærmere på dette først før brug i forskellige tilknytningskortlægninger.

Du kan anvende @AnyMetaDef anmærkning til den egenskab, der repræsenterer din forening. Men det gøres normalt på klasse- eller pakkeniveau. I det følgende kodestykke kan du se en pakke-info.java fil, der definerer denne tilknytning for en hel pakke.

@AnyMetaDef(name = "player",
		metaType = "string",
		idType = "long",
		metaValues = {
				@MetaValue(value = "Chess", targetEntity = ChessPlayer.class),
				@MetaValue(value = "Monopoly", targetEntity = MonopolyPlayer.class)
		}
	)
@AnyMetaDef(name = "team",
		metaType = "string",
		idType = "long",
		metaValues = {
				@MetaValue(value = "Chess", targetEntity = ChessPlayer.class),
				@MetaValue(value = "Monopoly", targetEntity = MonopolyPlayer.class)
		})		
package com.thorben.janssen.sample.model;

import org.hibernate.annotations.AnyMetaDef;
import org.hibernate.annotations.MetaValue;

idType attribut angiver typen af ​​den primære nøgle for de enhedsklasser, der er en del af denne tilknytning.

metatypen og metaValue egenskaber arbejder sammen. De definerer, hvordan Hibernate bevarer den enhedstype, som dette tilknytningselement repræsenterer. metaTypen specificerer typen af ​​kolonnen, hvor metaValues blive vedholdende. metaværdien attribut indeholder en matrix af @MetaValue anmærkninger. Hver af disse annoteringer specificerer tilknytningen mellem en enhedsklasse og dens identifikator.

I dette eksempel gemmer Hibernate strengen Skak i kolonnen player_type, og værdien 1 i kolonnen player_id for at fortsætte en tilknytning til en Skakspiller enhed med id 1.

Baseret på disse definitioner kan du derefter modellere din @Any og @ManyToAny foreninger

Definition af en @Any forening

Jeg bruger en @Any tilknytning i min PlayerScore enhed, som kortlægger scoren for en Skakspiller eller MonopolyPlayer .

@Entity
public class PlayerScore {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "player_score_seq")
    @SequenceGenerator(name = "player_score_seq", sequenceName = "player_score_seq", initialValue = 100)
    private Long id;

    private Integer score;

    @Any(metaDef = "player", metaColumn = @Column(name = "player_type"), fetch = FetchType.LAZY)
    @JoinColumn(name = "player_id")
    private Player player;

    ...
}

I modsætning til JPA's foreningskortlægninger er en @Any tilknytning kræver altid en @JoinColumn anmærkning. Den angiver navnet på den kolonne, der indeholder den primære nøgle for det tilknyttede objektobjekt. I dette eksempel fortæller den Hibernate, at bordet PlayerScore har kolonnen player_id , som indeholder den primære nøgleværdi for en afspiller .

Som forklaret tidligere, skal du også referere til en @AnyMetaDef definition. Det gør du ved at angive navnet på den definition som værdien af ​​metaDef attribut. I denne kortlægning refererer jeg til@AnyMetaDef med navnet spiller . Det er den, vi diskuterede i det foregående afsnit.

Når du modellerer en @Any forening, husk venligst, at det er en to-en forening. Som alle to-one-foreninger bliver den som standard hentet ivrigt. Dette kan give problemer med ydeevnen, og du bør bedre indstille FetchType til DOV .

Og det er alt, du skal gøre for at definere din kortlægning. Du kan nu bruge det på samme måde som enhver anden tilknytningsmapping.

PlayerScore ps1 = em.find(PlayerScore.class, playerScore1.getId());
log.info("Get player ...");
ps1.getPlayer().getWins();

Når du kører denne kode, kan du se i din logfil, at Hibernate udfører 1 SQL SELECT-sætning for at få PlayerScore . Hibernate udfører en anden SQL SELECT-sætning for at hente posten fra ChessPlayer tabel, når du bruger den modellerede tilknytning til at få adgang til afspilleren.

13:27:47,690 DEBUG SQL:144 - 
    select
        playerscor0_.id as id1_3_0_,
        playerscor0_.player_type as player_t2_3_0_,
        playerscor0_.player_id as player_i3_3_0_,
        playerscor0_.score as score4_3_0_ 
    from
        PlayerScore playerscor0_ 
    where
        playerscor0_.id=?
13:27:47,704  INFO TestSample:81 - Get player ...
13:27:47,705 DEBUG SQL:144 - 
    select
        chessplaye0_.id as id1_0_0_,
        chessplaye0_.firstName as firstnam2_0_0_,
        chessplaye0_.lastName as lastname3_0_0_,
        chessplaye0_.loses as loses4_0_0_,
        chessplaye0_.wins as wins5_0_0_ 
    from
        ChessPlayer chessplaye0_ 
    where
        chessplaye0_.id=?

Definition af en @ManyToAny forening

Hvis du vil modellere en til-mange-forening, kan du bruge en @ManyToAny anmærkning. I det følgende kodestykke bruger jeg denne kortlægning til at tildele forskellige typer spillere til et hold. Som du kan se, er definitionen af ​​en sådan kortlægning meget lig den forrige.

@Entity
public class MultiGameTeam {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "team_seq")
    @SequenceGenerator(name = "team_seq", sequenceName = "team_seq", initialValue = 100)
    private Long id;

    private String name;

    @ManyToAny(metaDef = "team", metaColumn = @Column(name = "player_type"))
    @JoinTable(name = "team_players", joinColumns = @JoinColumn(name = "team_id"),
            inverseJoinColumns = @JoinColumn(name = "player_id"))
    private List<Player> players;

    ...
}

Denne forening kan referere til flere spillere. Derfor skal du bruge en @JoinTable i stedet for en @JoinColumn anmærkning. Denne tabel indeholder metaColumn defineret af @ManyToAny annotation og de 2 fremmednøglekolonner for at referere til holdet og spilleren.

Og du skal også referere til en @AnyMetaDef definition. Vi har allerede diskuteret denne annotation meget detaljeret i et tidligere afsnit. Så det springer jeg over her.

Når du har defineret denne kortlægning, kan du bruge den på samme måde som enhver anden til-mange-forening.

MultiGameTeam gameTeam = em.find(MultiGameTeam.class, team.getId());
log.info("Get the team");
assertThat(gameTeam.getPlayers().size()).isEqualTo(2);
log.info("Check ChessPlayer");
assertThat(gameTeam.getPlayers().contains(chessPlayer)).isTrue();
log.info("Check MonopolyPlayer");
assertThat(gameTeam.getPlayers().contains(monopolyPlayer)).isTrue();

Som standard hentes alle til-mange associationer dovent. Så når du får et MultiGameTeam enhed fra databasen, vælger Hibernate kun den tilsvarende post fra MultiGameTeam bord. Når du så får adgang til players-attributten for første gang, vælger Hibernate tilknytningsposterne fra JoinTable, før den udfører en SQL SELECT-sætning for hver spiller på holdet.

13:40:31,341 DEBUG SQL:144 - 
    select
        multigamet0_.id as id1_2_0_,
        multigamet0_.name as name2_2_0_ 
    from
        MultiGameTeam multigamet0_ 
    where
        multigamet0_.id=?
13:40:31,351  INFO TestSample:130 - Get team members
13:40:31,353 DEBUG SQL:144 - 
    select
        players0_.team_id as team_id1_4_0_,
        players0_.player_type as player_t2_4_0_,
        players0_.player_id as player_i3_4_0_ 
    from
        team_players players0_ 
    where
        players0_.team_id=?

13:40:31,359 DEBUG SQL:144 - 
    select
        chessplaye0_.id as id1_0_0_,
        chessplaye0_.firstName as firstnam2_0_0_,
        chessplaye0_.lastName as lastname3_0_0_,
        chessplaye0_.loses as loses4_0_0_,
        chessplaye0_.wins as wins5_0_0_ 
    from
        ChessPlayer chessplaye0_ 
    where
        chessplaye0_.id=?
13:40:31,363 DEBUG SQL:144 - 
    select
        monopolypl0_.id as id1_1_0_,
        monopolypl0_.firstName as firstnam2_1_0_,
        monopolypl0_.lastName as lastname3_1_0_,
        monopolypl0_.loses as loses4_1_0_,
        monopolypl0_.wins as wins5_1_0_ 
    from
        MonopolyPlayer monopolypl0_ 
    where
        monopolypl0_.id=?
13:40:31,404  INFO TestSample:132 - Check ChessPlayer
13:40:31,405  INFO TestSample:134 - Check MonopolyPlayer

Som du kan se, kan det kræve mange udsagn at hente alle spillere på et hold. Derfor er denne kortlægning ikke den mest effektive. Hvis det er muligt, bør du i stedet bruge en standard tilknytningsmapping.

Oversigt

Du kan bruge JPAs standardtilknytningstilknytninger til at referere til en anden konkret enhedsklasse eller henvise til et arvehierarki. Men JPA kan ikke modellere en tilknytning til flere uafhængige enhedsklasser.

Hvis du har brug for en sådan tilknytning, kan du bruge Hibernates @Any og @ManyToAny foreningskortlægning. Det giver dig mulighed for at modellere en tilknytning til flere enhedsklasser, der alle implementerer den samme grænseflade. Denne tilknytning kræver en ekstra @AnyMetaDef annotation, der hjælper Hibernate med at kortlægge hver tilknytningspost til en specifik enhedsklasse og databasetabel.


Java tag