Java >> Java tutorial >  >> Tag >> hibernate

Modellering af sekvensbaserede sammensatte primærnøgler med Hibernate

Nogle primære nøgler består af mere end 1 enhedsattribut eller databasekolonne. Disse kaldes sammensatte primærnøgler. De indeholder ofte en fremmednøglereference til et overordnet objekt eller repræsenterer en kompleks naturlig nøgle. En anden grund til at bruge sammensatte nøgler er at kombinere en domænespecifik værdi med en simpel tæller, f.eks. en ordretype og ordrenummeret. Hvis denne tæller øges uafhængigt, ønsker de fleste udviklere at bruge en databasesekvens til det.

På databaseniveau kan du nemt modellere dette. Din primærnøgledefinition refererer til alle kolonner, der er en del af den primære nøgle. En sekvens er et selvstændigt objekt. Du anmoder om en værdi under din indsats og indstiller den som værdien af ​​din tæller.

Kortlægningen til en JPA-enhed omfatter et par udfordringer, som vi vil løse i denne artikel.

Modellering af en sammensat primær nøgle

Hvis du vil modellere en JPA-entitet, der bruger en sammensat primærnøgle, skal du angive 1 klasse, der repræsenterer denne nøgle. Din persistensudbyder og cacheimplementeringer bruger objekter af denne klasse internt til at identificere et entitetsobjekt. Klassen skal modellere alle attributter, der er en del af den primære nøgle. Den skal også have en no-args-konstruktør og implementere den Serialiserbare  grænsefladen og lig med og hashCode metoder.

Når du har implementeret den klasse, skal du beslutte, om du vil bruge den som en @EmbeddedId eller en @IdClass . Der er et par vigtige forskelle mellem disse kortlægninger, som jeg forklarer mere detaljeret i min Advanced Hibernate Online Training. I denne artikel vil jeg kun give dig en hurtig introduktion til begge muligheder. Jeg vil også forklare, hvorfor dette er en af ​​de få situationer, hvor en @IdClass er din bedre mulighed.

Kortlægning af et @EmbeddedId

Som navnet indikerer, kortlægningen af ​​en sammensat primær nøgle som en @EmbeddedId kræver en indlejrbar. Denne simple Java-klasse definerer et genanvendeligt stykke kortlægningsinformation, der bliver en del af enheden. Hvis du vil bruge den indlejrede som en identifikator, skal den også opfylde JPA's tidligere nævnte krav til en identifikator.

Her kan du se et simpelt eksempel på et ChessGameId indlejret, der modellerer attributterne id og turneringskode . Jeg vil bruge dem som de identificerende attributter for mit Skakspil enhedsklasse.

@Embeddable
public class ChessGameId implements Serializable {

    private Long id;

    private String tournamentCode;

    public ChessGameId() {
    }

    public ChessGameId(Long id, String tournamentCode) {
        this.id = id;
        this.tournamentCode = tournamentCode;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, tournamentCode);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        ChessGameId chessGameId = (ChessGameId) obj;
        return id.equals(chessGameId.id) && tournamentCode.equals(chessGameId.tournamentCode);
    }
	
    // getter and setter methods
}

Det eneste specielle ved denne kortlægning er @Embeddable anmærkning. Det fortæller persistensudbyderen, at alle attributter og kortoplysninger skal blive en del af den enhed, der bruger ChessGameId  som en attributtype.

Dernæst bruger jeg denne indlejring i mit Skakspil enhedsklasse og annoter den med @EmbeddedId . Det fortæller Hibernate at inkludere alle tilknyttede attributter for ChessGameId i denne enhed og brug dem som den primære nøgle.

@Entity
public class ChessGame {

    @EmbeddedId
    private ChessGameId chessGameId;

    private LocalDate date;

    private int round;

    @Version
    private int version;

    @ManyToOne(fetch = FetchType.LAZY)
    private ChessTournament chessTournament;

    @ManyToOne(fetch = FetchType.LAZY)
    private ChessPlayer playerWhite;
    
    @ManyToOne(fetch = FetchType.LAZY)
    private ChessPlayer playerBlack;
	
    // getter and setter methods
}

@EmbeddedId s understøtter ikke genererede attributter

Alt dette kan ligne en ligetil kortlægning af en sammensat primærnøgle. Og det ville være tilfældet, hvis du ikke ønsker at bruge en databasesekvens eller en auto-inkrementeret kolonne til at generere den primære nøgleværdi.

@GeneratedValue annotation formodes at blive brugt på en attribut, der er kommenteret med @Id . Men ingen af ​​attributterne for ChessGameId klasse er kommenteret med den anmærkning. På grund af det ignorerer Hibernate @GeneratedValue annotation i følgende kodestykke.

@Embeddable
public class ChessGameId implements Serializable {

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

    private String tournamentCode;

    public ChessGameId() {
    }

    public ChessGameId(Long id, String tournamentCode) {
        this.id = id;
        this.tournamentCode = tournamentCode;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, tournamentCode);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        ChessGameId chessGameId = (ChessGameId) obj;
        return id.equals(chessGameId.id) && tournamentCode.equals(chessGameId.tournamentCode);
    }
	
    // getter and setter methods
}

Når du fortsætter med et nyt Skakspil enhedsobjekt, værdien af ​​id attribut forbliver null.

15:09:29,337 DEBUG SQL:144 - insert into ChessGame (chessTournament_id, date, playerBlack_country, playerBlack_id, playerWhite_country, playerWhite_id, round, version, id, tournamentCode) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
15:09:29,348  WARN SqlExceptionHelper:137 - SQL Error: 0, SQLState: 23502
15:09:29,348 ERROR SqlExceptionHelper:142 - ERROR: null value in column "id" violates not-null constraint

Kortlægning af en IdClass

Hvis du vil kortlægge en sammensat primærnøgle og generere værdien af ​​en af ​​dens attributter ved hjælp af en sekvens eller auto-inkrementeret kolonne, skal du bruge en IdClass . Den største forskel i forhold til den tidligere kortlægning er, at du modellerer alle enhedsattributter på din enhedsklasse. Attributterne for IdClass bliver ikke en del af enhedsdefinitionen. De afspejler kun de identificerende attributter.

IdClass i sig selv er en grundlæggende Java-klasse. Som defineret af JPA-specifikationen kræver det en standardkonstruktør og skal implementere den Serialiserbare grænsefladen og lig med og hashCode metoder.

public class ChessPlayerId implements Serializable {

    private Long id;

    private String country;

    public ChessPlayerId() {
    }

    public ChessPlayerId(Long id, String country) {
        this.id = id;
        this.country = country;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, country);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        ChessPlayerId chessPlayerId = (ChessPlayerId) obj;
        return id.equals(chessPlayerId.id) && country.equals(chessPlayerId.country);
    }
}

Typen og navnet på IdClass attributter skal matche attributterne for den enhedsklasse, som du har annoteret med @Id . Din persistensudbyder, i mit tilfælde Hibernate, holder derefter begge sæt attributter automatisk synkroniseret.

Når du har defineret din IdClass , skal du annotere din enhedsklasse med en @IdClass  anmærkning og henvisning til den pågældende klasse. I modsætning til det foregående eksempel kortlægger enhedsklassen alle databasekolonner, inklusive dem, der er en del af identifikatoren. Du skal annotere disse attributter med et @Id anmærkning. Dette er en åbenlys forskel fra det foregående eksempel. Det giver dig også mulighed for at kommentere en eller flere af dem med en @GeneratedValue annotation.

@Entity
@IdClass(ChessPlayerId.class)
public class ChessPlayer {

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

    private String lastName;

    private String firstName;

    private LocalDate birthDate;

    @OneToMany(mappedBy = "playerWhite")
    private Set<ChessGame> gamesWhite;

    @OneToMany(mappedBy = "playerBlack")
    private Set<ChessGame> gamesBlack;

    @Version
    private int version;
	
    // getter and setter methods
}

I denne kortlægning er id attribut for ChessPlayer enhedsklassen er kommenteret med et @Id og en @GeneratedValue anmærkning. Persistensudbyderen ignorerer ikke længere @GeneratedValue annotation og får en værdi fra databasesekvensen, før et nyt objektobjekt vedbliver.

15:42:35,368 DEBUG SQL:144 - select nextval ('player_seq')
15:42:35,388 DEBUG SQL:144 - insert into ChessPlayer (birthDate, firstName, lastName, version, country, id) values (?, ?, ?, ?, ?, ?)

Konklusion

JPA og Hibernate understøtter 2 tilknytninger til modellering af sammensatte primærnøgler. Jeg foretrækker generelt kortlægningen af ​​sammensatte primærnøgler som @EmbeddedId s. Men det understøtter ikke genererede identifikatorværdier og kan ikke bruges i denne situation. Det er fordi du kun kan bruge @GeneratedValue annotering på en attribut, der er kommenteret med @Id . Og når du bruger et @EmbeddedId , ingen af ​​dine primære nøgleattributter er annoteret med @Id .

Kun kortlægningen som en IdClass  understøtter sammensatte primærnøgler, der bruger genererede identifikationsværdier. Du modellerer alle attributter på din enhedsklasse og annoterer dem med de påkrævede kortlægningsannotationer. Du skal også annotere din enhedsklasse med en @IdClass annotering og reference til en klasse, der indeholder alle identificerende attributter og opfylder JPA's krav til en identifikatorklasse.


Java tag