Mappatura di una singola entità a più tabelle in JPA

Persistenza in alto

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO

1. Introduzione

JPA semplifica la gestione dei modelli di database relazionali dalle nostre applicazioni Java. Le cose sono semplici quando mappiamo ogni tabella su una singola classe di entità. Ma a volte abbiamo motivi per modellare le nostre entità e tabelle in modo diverso:

  • Quando vogliamo creare gruppi logici di campi, possiamo mappare più classi a una singola tabella
  • Se è coinvolta l'ereditarietà, possiamo mappare una gerarchia di classi a una struttura di tabella
  • Nei casi in cui i campi correlati sono sparsi tra più tabelle e si desidera modellare quelle tabelle con una singola classe

In questo breve tutorial vedremo come affrontare quest'ultimo scenario.

2. Modello di dati

Supponiamo di gestire un ristorante e di voler memorizzare i dati su ogni pasto che serviamo:

  • nome
  • descrizione
  • prezzo
  • che tipo di allergeni contiene

Poiché ci sono molti possibili allergeni, raggrupperemo questo set di dati insieme. Inoltre, modelleremo anche questo utilizzando le seguenti definizioni di tabella:

Ora vediamo come possiamo mappare queste tabelle su entità utilizzando annotazioni JPA standard.

3. Creazione di più entità

La soluzione più ovvia è creare un'entità per entrambe le classi.

Iniziamo definendo l' entità Pasto :

@Entity @Table(name = "meal") class Meal { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") Long id; @Column(name = "name") String name; @Column(name = "description") String description; @Column(name = "price") BigDecimal price; @OneToOne(mappedBy = "meal") Allergens allergens; // standard getters and setters }

Successivamente, aggiungeremo l' entità Allergens :

@Entity @Table(name = "allergens") class Allergens { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "meal_id") Long mealId; @OneToOne @PrimaryKeyJoinColumn(name = "meal_id") Meal meal; @Column(name = "peanuts") boolean peanuts; @Column(name = "celery") boolean celery; @Column(name = "sesame_seeds") boolean sesameSeeds; // standard getters and setters }

Nell'esempio sopra, possiamo vedere che meal_id è sia la chiave primaria che la chiave esterna. Ciò significa che dobbiamo definire la colonna della relazione uno a uno utilizzando @PrimaryKeyJoinColumn .

Tuttavia, questa soluzione presenta due problemi:

  • Vogliamo sempre conservare gli allergeni per un pasto e questa soluzione non impone questa regola
  • Logicamente, i dati relativi a pasti e allergeni appartengono insieme, quindi potremmo voler memorizzare queste informazioni nella stessa classe Java anche se abbiamo creato più tabelle per loro

Una possibile soluzione al primo problema è aggiungere l' annotazione @NotNull al campo allergeni sulla nostra entità Pasto . JPA non ci permetterà di persistere il pasto se abbiamo un allergeni nulli .

Tuttavia, questa non è una soluzione ideale; ne vogliamo uno più restrittivo, dove non abbiamo nemmeno la possibilità di provare a persistere un Pasto senza Allergeni.

4. Creazione di una singola entità con @SecondaryTable

Possiamo creare una singola entità specificando che abbiamo colonne in tabelle diverse usando l' annotazione @SecondaryTable :

@Entity @Table(name = "meal") @SecondaryTable(name = "allergens", pkJoinColumns = @PrimaryKeyJoinColumn(name = "meal_id")) class Meal { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") Long id; @Column(name = "name") String name; @Column(name = "description") String description; @Column(name = "price") BigDecimal price; @Column(name = "peanuts", table = "allergens") boolean peanuts; @Column(name = "celery", table = "allergens") boolean celery; @Column(name = "sesame_seeds", table = "allergens") boolean sesameSeeds; // standard getters and setters }

Dietro le quinte, JPA unisce la tabella primaria con la tabella secondaria e popola i campi. Questa soluzione è simile alla relazione @OneToOne , ma in questo modo possiamo avere tutte le proprietà nella stessa classe.

E 'importante notare che se abbiamo una colonna che si trova in una tabella secondaria, dobbiamo specificare con il tavolo argomento del @Column annotazione. Se una colonna si trova nella tabella primaria, possiamo omettere l' argomento della tabella poiché JPA cerca le colonne nella tabella primaria per impostazione predefinita.

Inoltre, nota che possiamo avere più tabelle secondarie se le incorporiamo in @SecondaryTables . In alternativa, da Java 8, possiamo contrassegnare l'entità con più annotazioni @SecondaryTable poiché è un'annotazione ripetibile.

5. La combinazione @SecondaryTable Con @Embedded

Come abbiamo visto, @SecondaryTable associa più tabelle alla stessa entità. Sappiamo anche che @Embedded e @ Embeddable fanno il contrario e mappano una singola tabella a più classi.

Vediamo che cosa si ottiene quando si combinano @SecondaryTable con @Embedded e @Embeddable :

@Entity @Table(name = "meal") @SecondaryTable(name = "allergens", pkJoinColumns = @PrimaryKeyJoinColumn(name = "meal_id")) class Meal { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") Long id; @Column(name = "name") String name; @Column(name = "description") String description; @Column(name = "price") BigDecimal price; @Embedded Allergens allergens; // standard getters and setters } @Embeddable class Allergens { @Column(name = "peanuts", table = "allergens") boolean peanuts; @Column(name = "celery", table = "allergens") boolean celery; @Column(name = "sesame_seeds", table = "allergens") boolean sesameSeeds; // standard getters and setters }

È un approccio simile a quello che abbiamo visto usando @OneToOne . Tuttavia, ha un paio di vantaggi:

  • JPA gestisce i due tavoli insieme per noi, quindi possiamo essere sicuri che ci sarà una riga per ogni pasto in entrambi i tavoli
  • Inoltre, il codice è un po 'più semplice, poiché abbiamo bisogno di meno configurazione

Tuttavia, questa soluzione simile a uno a uno funziona solo quando le due tabelle hanno ID corrispondenti.

Vale la pena ricordare che se vogliamo riutilizzare la classe Allergens , sarebbe meglio se definissimo le colonne della tabella secondaria nella classe Meal con @AttributeOverride .

6. Conclusione

In questo breve tutorial, abbiamo visto come possiamo mappare più tabelle alla stessa entità utilizzando l' annotazione JPA @SecondaryTable .

Abbiamo anche visto i vantaggi della combinazione @SecondaryTable con @Embedded e @Embeddable per ottenere un rapporto simile a uno-a-uno.

Come al solito, gli esempi sono disponibili su GitHub.

Fondo di persistenza

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO