veel-tot-veel associaties zijn een van de meest gebruikte associaties met JPA en Hibernate. U kunt veel voorbeelden voor hen vinden in de echte wereld, en u kunt ze in kaart brengen met JPA en Hibernate als een uni – of bidirectionele Vereniging in uw domein model.
maar u weet waarschijnlijk ook dat deze mappings verschillende valkuilen bieden. In dit artikel zal ik u 5 best practices laten zien die u zullen helpen om deze valkuilen te vermijden en efficiënte mappings te implementeren. Je zult leren:
- het meest efficiënte gegevenstype voor uw associatie
- waarom u gebruiksmethoden nodig hebt om uw associatie te beheren
- het juiste FetchType voor een efficiënte toewijzing
- wanneer en hoe query-specifieke fetching
- het CascadeType dat u ten koste van alles moet vermijden
Ik zal niet ingaan op de details van een basis-toewijzing van veel op veel. Als je niet precies weet hoe je een dergelijke mapping moet maken, kijk dan eens naar de veel-tot-veel sectie in mijn associatie mapping guide.
het meest efficiënte gegevenstype voor uw associatie
De meeste ontwikkelaars besteden niet veel aandacht aan het gegevenstype van een to-many associatie. Ze kiezen gewoon een java.util.Lijst omdat het eenvoudig is en geen controles uitvoert om duplicaten te voorkomen.
dat is OK, als je een Basic Java class implementeert of als je een één-op-veel/veel-op-één associatie modelleert. Maar je moet nooit een lijst gebruiken als je een veel-tot-veel Vereniging modelleert.
@Entitypublic class Book {// DON'T DO THIS!!!@ManyToMany@JoinTable(name = "book_author", joinColumns = { @JoinColumn(name = "fk_book") }, inverseJoinColumns = { @JoinColumn(name = "fk_author") })private List<Author> authors = new ArrayList<Author>();...}
Hibernate handvatten verwijderen operaties op veel-op-veel relaties die zijn toegewezen aan een java.util.Lijst zeer inefficiënt.
em = emf.createEntityManager();em.getTransaction().begin();// Get Book entity with 2 Authorsb = em.find(Book.class, 1L);// Remove one of the Authorb.getAuthors().remove(a);em.getTransaction().commit();em.close();
Het verwijdert eerst alle records uit de associatietabel voordat het alle resterende records invoegt.
09:54:28,876 DEBUG - update Book set title=?, version=? where id=? and version=?09:54:28,878 DEBUG - delete from book_author where fk_book=?09:54:28,882 DEBUG - insert into book_author (fk_book, fk_author) values (?, ?)
u moet in plaats daarvan een veel-tot-veel associatie modelleren als een java.util.Set.
@Entitypublic class Book {@ManyToMany@JoinTable(name = "book_author", joinColumns = { @JoinColumn(name = "fk_book") }, inverseJoinColumns = { @JoinColumn(name = "fk_author") })private Set<Author> authors = new HashSet<Author>();...}
Hibernate dan verwerkt verwijderen operaties op de associatie veel beter. Het verwijdert nu alleen de verwachte records uit de associatie en houdt de anderen onaangeroerd.
10:00:37,709 DEBUG - update Book set title=?, version=? where id=? and version=?10:00:37,711 DEBUG - delete from book_author where fk_book=? and fk_author=?
waarom u gebruiksmethoden nodig hebt om uw associatie te beheren
bidirectionele associaties worden toegewezen aan een entity attribuut aan beide uiteinden van de relaties. Dus, in het vorige voorbeeld, heb je een auteurs attribuut op de boekentiteit, en een Boeken attribuut op de Auteurentiteit. Dat maakt het implementeren van een Jpql of CriteriaQuery Zeer comfortabel omdat je deze attributen kunt gebruiken om een JOIN-clausule te definiëren.
maar het toevoegen of verwijderen van een associatie wordt ingewikkelder. U moet de wijziging altijd aan beide uiteinden van de associatie uitvoeren. Als u bijvoorbeeld een boek aan de auteur wilt toevoegen, moet u het toevoegen aan het attribuut boeken van de Auteurentiteit, en u moet ook de Auteur het attribuut auteurs toevoegen aan de Boekentiteit. Anders bevat uw huidige persistentie-context inconsistente gegevens die u zult gebruiken tot het einde van uw huidige transactie.
Book b = new Book();b.setTitle("Hibernate Tips - More than 70 solutions to common Hibernate problems");em.persist(b);Author a = em.find(Author.class, 1L);a.getBooks().add(b);b.getAuthors().add(a);
hulpprogramma methoden op uw auteur en boek entiteiten maken het bijwerken en verwijderen veel gemakkelijker. Binnen deze methoden voert u de vereiste bewerkingen uit op beide entiteiten.
@Entitypublic class Author {@ManyToMany(mappedBy = "authors")private Set<Book> books = new HashSet<Book>();...public void addBook(Book book) {this.books.add(book);book.getAuthors().add(this);}public void removeBook(Book book) {this.books.remove(book);book.getAuthors().remove(this);}}
het juiste FetchType voor een efficiënte toewijzing
Dit is een snelle. Je moet altijd FetchType gebruiken.Lui voor je vele-tot-vele verenigingen. Het vertelt uw persistence provider niet om de bijbehorende entiteiten uit de database te halen totdat u ze gebruikt. Dat is meestal het geval wanneer u de getter methode voor de eerste keer te bellen.
gelukkig is dat de standaard voor alle to-many associaties. Dus zorg ervoor dat je het niet verandert.
en als je meer wilt weten over JPA ‘ s verschillende FetchTypes, kijk dan eens naar mijn inleiding tot JPA FetchTypes.
wanneer en hoe query-specifieke fetching
te gebruiken als u FetchType gebruikt.Lui, je moet weten over query-specifieke ophalen. Anders zal uw toepassing erg traag zijn omdat u veel N+1 select-problemen hebt gemaakt.
wanneer u een entiteit laadt en query-specifieke fetching gebruikt, vertelt u Hibernate welke toegewezen associaties het moet initialiseren voor elke opgehaalde entiteit. Het breidt vervolgens de Select-clausule van uw query uit, zodat het de kolommen bevat die door deze andere entiteiten zijn toegewezen en de associaties initialiseert. En omdat de associaties al geïnitialiseerd zijn, hoeft Hibernate geen extra query uit te voeren wanneer u de getter-methode voor de eerste keer opent.
u kunt query-specifiek ophalen op verschillende manieren implementeren. De eenvoudigste is een JOIN FETCH-clausule, die ik je hier zal laten zien. Maar je kunt ook een @NamedEntityGraph of een EntityGraph gebruiken, die ik in eerdere artikelen heb uitgelegd.
de definitie van een JOIN FETCH-clausule is bijna identiek aan een eenvoudige JOIN-clausule in een jpql-query. U hoeft alleen maar het sleutelwoord FETCH toe te voegen.
Author a = em.createQuery("SELECT a FROM Author a JOIN FETCH a.books WHERE a.id = 1", Author.class).getSingleResult();
toch lijken een JOIN en een JOIN FETCH-clausule erg op elkaar, de JOIN FETCH-clausule heeft een veel groter effect op de gegenereerde SQL-query. Het wordt niet alleen vertaald in een SQL JOIN, zoals het geval is voor een jpql JOIN clausule, het dwingt ook uw persistence provider om de Select clausule uit te breiden met alle kolommen die zijn toegewezen door de geassocieerde entiteit.
16:21:03,046 DEBUG SQL:94 - select author0_.id as id1_0_0_, book2_.id as id1_1_1_, author0_.firstName as firstNam2_0_0_, author0_.lastName as lastName3_0_0_, author0_.version as version4_0_0_, book2_.format as format2_1_1_, book2_.publishingDate as publishi3_1_1_, book2_.title as title4_1_1_, book2_.version as version5_1_1_, books1_.author_id as author_i2_2_0__, books1_.book_id as book_id1_2_0__ from Author author0_ inner join book_author books1_ on author0_.id=books1_.author_id inner join Book book2_ on books1_.book_id=book2_.id where author0_.id=1
het CascadeType dat u ten koste van alles moet vermijden
Als u cascading op een associatie activeert, past uw persistence provider de bewerkingen die u uitvoert op de entiteit toe op alle geassocieerde entiteiten. Als het doet dat Voor alle bewerkingen of alleen voor een paar geselecteerde degenen hangt af van de geconfigureerde CascadeType.
Dat klinkt misschien als een geweldig idee dat de implementatie van uw bedrijfslogica veel gemakkelijker maakt. En dat is niet helemaal verkeerd.
maar vermijd de Cascadetypen verwijderen en alles, waaronder verwijderen, voor veel-tot-veel associaties. In het beste geval creëert het alleen prestatieproblemen, maar in het slechtste geval kan het ook meer records verwijderen dan u bedoeld had.
Ik heb beide valkuilen en hun oplossing in grote details uitgelegd in een vorig artikel. Of als je het eenvoudig wilt houden, activeer dan programmatisch de benodigde informatie over de bijbehorende entiteiten. Dit kan een paar regels code vereisen, maar het voorkomt onverwachte bijwerkingen.
conclusie
u kunt veel voorbeelden vinden voor veel-tot-veel associaties in de echte wereld, en u kunt ze eenvoudig toewijzen met JPA en Hibernate. Helaas verbergen deze eenvoudige toewijzingen een paar valkuilen die u kunt vermijden door het volgen van deze 5 best practices:
- Model associaties als een java.util.Set.
- geef hulpprogramma methoden om een entiteit toe te voegen of te verwijderen uit een associatie.
- Gebruik altijd FetchType.Lui, dat is de standaard, om prestatieproblemen te voorkomen.
- query-specifieke fetching toepassen om n+1 Select problemen te voorkomen.
- gebruik de CascadeTypes REMOVE en ALL niet.