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.