Java >> Programma Java >  >> Java

Come scegliere il tipo di dati più efficiente per molte associazioni:Borsa vs. Elenco vs. Set

Quale tipo di dati dovresti usare per mappare un'associazione a molti con Hibernate? È meglio usare un Set o un Elenco ?

Questa è una domanda molto comune e la maggior parte degli sviluppatori è sorpresa quando esamina la documentazione e scopre che queste non sono le uniche opzioni. Puoi anche usare una Borsa o una Mappa .

La mappatura su una Mappa è un caso speciale, e l'ho già spiegato in dettaglio in uno dei miei post precedenti. A differenza delle altre 3 opzioni, fornisce un accesso indicizzato alle entità associate. Ciò potrebbe essere utile per alcuni casi d'uso, ma per la creazione e la gestione della Mappa crea anche un sovraccarico. Pertanto, non dovrebbe essere la tua scelta predefinita.

Quindi, concentriamoci sulle altre 3 opzioni. Elenco , Imposta e Borsa rappresenta semplici raccolte di entità. Le prime 2 opzioni dovrebbero suonare familiari perché java.util il pacchetto contiene un Elenco e un Set interfaccia.

Ma cos'è una Borsa ? Non ci sono classi in java.util pacchetto con quel nome.

La differenza tra una borsa e una lista

La denominazione di Hibernate dei diversi tipi di raccolta è un po' confusa perché Elenco se Borsa s sono entrambi mappati da una java.util.List . La differenza tra loro è che un Elenco viene ordinato e una Borsa non è ordinato.

Quindi, se mappi la tua associazione to-many a un java.util.List senza specificare l'ordine degli elementi dell'associazione, stai utilizzando una Borsa e non una Lista. Questo dovrebbe essere il caso per la maggior parte delle tue associazioni perché il recupero dell'associazione in un ordine specifico rallenta le query del database. Dovresti usare meglio una query JPQL con una clausola ORDER BY per definire l'ordine se ne hai bisogno.

Quindi, per la maggior parte dei mapping di associazione, rimangono 2 opzioni. Devi decidere tra una Borsa e un Set .

Dovresti usare una borsa o un set?

Quando guardi solo i tipi Java, la risposta sembra essere facile. In generale, un java.util.List fornisce le migliori prestazioni durante un java.util.Set non contiene duplicati. Se implementi correttamente il caso d'uso create, un java.util.List sembra la scelta migliore ovvia per la mappatura dell'associazione.

Ma non è così facile. Un Elenco potrebbe essere più efficiente di un Set , ma il tipo influenza anche il modo in cui Hibernate gestisce l'associazione nel database. Quindi, ci sono alcune altre cose di cui devi tenere conto quando prendi una decisione.

Un bug critico nelle versioni precedenti di Hibernate

Prima di tutto, se stai usando una versione Hibernate precedente alla 5.0.8, dovresti essere a conoscenza del bug HHH-5855. Quando hai usato un java.util.List e ha unito l'entità padre, Hibernate ha generato 2 istruzioni INSERT per ogni nuova entità figlio.

Gestione inefficiente delle associazioni molti-a-molti

Quando stai mappando un'associazione molti-a-molti, dovresti sempre usare un java.util.Set .

Non utilizzare un Elenco per le associazioni molti-a-molti

Se modelli l'associazione come java.util.List , Hibernate gestisce la rimozione delle entità associate in modo molto inefficiente.

@Entity
public class Book {

	// DON'T DO THIS!!!
	@ManyToMany
	@JoinTable(name = "book_author", 
			joinColumns = { @JoinColumn(name = "fk_book") }, 
			inverseJoinColumns = { @JoinColumn(name = "fk_author") })
	private List authors = new ArrayList();
	
	...
}

Nel seguente frammento di codice, carico un Libro che è stato scritto da 2 Autore se rimuovere uno degli Autore s dall'associazione.

em = emf.createEntityManager();
em.getTransaction().begin();

// Get Book entity with 2 Authors
b = em.find(Book.class, 1L);

// Remove one of the Author
b.getAuthors().remove(a);

em.getTransaction().commit();
em.close();

Come puoi vedere nei messaggi di registro, Hibernate rimuove tutti i record dalla tabella di associazione prima di inserire un nuovo record per l'associazione rimanente.

Questo approccio è ovviamente molto inefficiente. A seconda del numero di entità associate, le istruzioni INSERT aggiuntive possono creare problemi di prestazioni.

...
09:54:28,876 DEBUG [org.hibernate.SQL] - update Book set title=?, version=? where id=? and version=?
09:54:28,878 DEBUG [org.hibernate.SQL] - delete from book_author where fk_book=?
09:54:28,882 DEBUG [org.hibernate.SQL] - insert into book_author (fk_book, fk_author) values (?, ?)

Usa un Set per mappare le associazioni molti-a-molti

Hibernate gestisce l'associazione molto meglio se la modelli come java.util.Set .

@Entity
public class Book {

	@ManyToMany
	@JoinTable(name = "book_author", 
			joinColumns = { @JoinColumn(name = "fk_book") }, 
			inverseJoinColumns = { @JoinColumn(name = "fk_author") })
	private Set authors = new HashSet();
	
	...
}

Se esegui di nuovo lo stesso test case, Hibernate ora rimuove solo il record che rappresenta l'associazione rimossa. Come previsto, tutti gli altri record del database non sono interessati dall'operazione di rimozione.

...
10:00:37,709 DEBUG [org.hibernate.SQL] - update Book set title=?, version=? where id=? and version=?
10:00:37,711 DEBUG [org.hibernate.SQL] - delete from book_author where fk_book=? and fk_author=?

Riepilogo

Come hai visto, mappare un'associazione come java.util.List può creare problemi che superano di gran lunga il piccolo guadagno in termini di prestazioni rispetto a un java.util.Set . Quindi, assicurati di aggiornare la tua versione di Hibernate e di utilizzare un Set per modellare associazioni molti-a-molti.


Etichetta Java