Najlepsze praktyki dla wielu-do-wielu stowarzyszeń z Hibernate i JPA

wiele-do-wielu stowarzyszeń są jednym z najczęściej używanych skojarzeń z JPA i Hibernate. Możesz znaleźć dla nich wiele przykładów w prawdziwym świecie i możesz je mapować za pomocą JPA i hibernować jako jedno – lub dwukierunkowe skojarzenie w modelu domeny.

ale pewnie wiesz też, że te mapowania zawierają kilka pułapek. W tym artykule pokażę Ci 5 najlepszych praktyk, które pomogą Ci uniknąć tych pułapek i wdrożyć skuteczne mapowania. Nauczysz się:

  1. najskuteczniejszy typ danych dla Twojego asocjacji
  2. dlaczego potrzebujesz metod użytkowych do zarządzania asocjacją
  3. odpowiedni FetchType do wydajnego mapowania
  4. kiedy i jak używać specyficznego dla zapytania pobierania
  5. CascadeType powinieneś unikać za wszelką cenę

Nie będę zanurkować w szczegóły podstawowego mapowania wielu do wielu. Jeśli nie jesteś do końca pewien, jak utworzyć takie mapowanie, proszę spojrzeć na sekcję wiele do wielu w moim przewodniku mapowania asocjacji.

najbardziej efektywny typ danych dla Twojego Stowarzyszenia

wideo z YouTube

większość programistów nie myśli zbyt wiele o typie danych Stowarzyszenia to-many. Po prostu wybierają Javę.util.Lista, ponieważ jest prosta i nie wykonuje żadnych kontroli, aby uniknąć duplikatów.

To jest OK, jeśli zaimplementujesz podstawową klasę Java lub jeśli modelujesz asocjację jeden-do-wielu/wiele-do-jednego. Ale nigdy nie powinieneś używać listy, jeśli modelujesz wiele do wielu skojarzeń.

@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 obsługuje operacje usuwania na wielu relacjach, które są mapowane do środowiska java.util.Lista bardzo nieefektywnie.

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();

najpierw usuwa wszystkie rekordy z tabeli asocjacji, zanim wstawia wszystkie pozostałe.

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 (?, ?)

zamiast tego powinieneś modelować wiele do wielu asocjacji jako java.util.Gotowi.

@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 znacznie lepiej obsługuje operacje usuwania w asocjacji. Teraz usuwa tylko oczekiwane rekordy ze Stowarzyszenia i utrzymuje Pozostałe nietknięte.

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=?

dlaczego potrzebujesz metod narzędzi do zarządzania powiązaniami

dwukierunkowe skojarzenia są mapowane do atrybutu encji na obu końcach relacji. Tak więc w poprzednim przykładzie atrybut authors znajduje się na encji książki, a atrybut books na encji autora. To sprawia, że implementacja JPQL lub kryteriów jest bardzo wygodna, ponieważ możesz użyć tych atrybutów do zdefiniowania klauzuli JOIN.

ale dodawanie lub usuwanie asocjacji staje się bardziej skomplikowane. Zawsze musisz dokonać zmiany na obu końcach Stowarzyszenia. Na przykład, jeśli chcesz dodać książkę do autora, musisz dodać ją do atrybutu książki encji autora, a także dodać atrybut Autor autorzy na encji Książki. W przeciwnym razie bieżący kontekst trwałości zawiera niespójne dane, których będziesz używać do końca bieżącej transakcji.

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);

metody użytkowe na encjach autora i książki znacznie ułatwiają aktualizację i usuwanie. W ramach tych metod wykonujesz wymagane operacje na obu podmiotach.

@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);}}

właściwy FetchType do wydajnego mapowania

wideo z YouTube

jest to szybkie. Zawsze powinieneś używać FetchType.Leniwy dla wielu-do-wielu skojarzeń. Informuje dostawcę programu persistence, aby nie pobierał powiązanych jednostek z bazy danych, dopóki ich nie użyjesz. Zwykle tak jest, gdy po raz pierwszy wywołujesz metodę getter.

na szczęście jest to domyślne dla wszystkich do-wielu skojarzeń. Więc upewnij się, że tego nie zmienisz.

a jeśli chcesz dowiedzieć się więcej o różnych typach Fetchtypów JPA, spójrz na moje Wprowadzenie do JPA FetchTypes.

kiedy i jak używać pobierania specyficznego dla zapytania

, Jeśli używasz FetchType.Leniwy, musisz wiedzieć o pobieraniu specyficznym dla zapytania. W przeciwnym razie Twoja aplikacja będzie bardzo powolna, ponieważ utworzyłeś wiele spraw N + 1 select.

wideo z YouTube

Kiedy ładujesz encję i używasz pobierania specyficznego dla zapytania, mówisz Hibernate, które zmapowane skojarzenia ma zainicjować dla każdego pobranego encji. Następnie rozszerza klauzulę SELECT zapytania tak, że zawiera kolumny odwzorowane przez te inne elementy i inicjalizuje Asocjacje. A ponieważ powiązania są już zainicjowane, Hibernate nie musi wykonywać dodatkowego zapytania, gdy uzyskujesz dostęp do metody getter po raz pierwszy.

możesz zaimplementować pobieranie specyficzne dla zapytania na kilka różnych sposobów. Najprostszą z nich jest klauzula join FETCH, którą pokażę Ci tutaj. Ale możesz również użyć @ NamedEntityGraph lub EntityGraph, co wyjaśniłem w poprzednich artykułach.

definicja klauzuli join Fetch jest prawie identyczna z prostą klauzulą JOIN w zapytaniu JPQL. Wystarczy dodać słowo kluczowe FETCH.

Author a = em.createQuery("SELECT a FROM Author a JOIN FETCH a.books WHERE a.id = 1", Author.class).getSingleResult();

mimo to, klauzula JOIN I JOIN fetch wyglądają bardzo podobnie, klauzula JOIN fetch ma znacznie większy wpływ na generowane zapytanie SQL. Nie tylko zostanie przetłumaczona na SQL JOIN, jak ma to miejsce w przypadku klauzuli JPQL JOIN, ale także zmusi dostawcę trwałości do rozszerzenia klauzuli SELECT o wszystkie kolumny, które są mapowane przez powiązaną jednostkę.

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

Typ CascadeType, którego należy unikać za wszelką cenę

jeśli aktywujesz kaskadowanie w Stowarzyszeniu, Twój dostawca trwałości stosuje operacje wykonywane na jednostce do wszystkich powiązanych jednostek. Jeśli robi to dla wszystkich operacji lub tylko dla kilku wybranych zależy od skonfigurowanego typu CascadeType.

film z YouTube

To może brzmieć jak niesamowity pomysł, który znacznie ułatwia wdrożenie Twojej logiki biznesowej. I to nie jest do końca złe.

ale proszę unikać KASKADETYPÓW usunąć i wszystko, co obejmuje usunąć, dla wielu do wielu skojarzeń. W najlepszym przypadku powoduje to tylko problemy z wydajnością, ale w najgorszym przypadku może również usunąć więcej rekordów niż zamierzałeś.

w poprzednim artykule szczegółowo wyjaśniłem zarówno pułapki, jak i ich rozwiązanie. Jeśli chcesz zachować prostotę, Uruchom wymagane informacje programowo na powiązanych jednostkach. Może to wymagać jeszcze kilku linijek kodu, ale pozwala uniknąć nieoczekiwanych skutków ubocznych.

podsumowanie

Możesz znaleźć wiele przykładów wielu do wielu skojarzeń w prawdziwym świecie i łatwo możesz je mapować za pomocą JPA i Hibernate. Niestety, te proste mapowania ukrywają kilka pułapek, których można uniknąć, stosując się do tych 5 najlepszych praktyk:

  1. model associations as a java.util.Gotowi.
  2. dostarcza narzędzi do dodawania lub usuwania encji z asocjacji.
  3. zawsze używa FetchType.Leniwy, co jest domyślne, aby uniknąć problemów z wydajnością.
  4. Zastosuj pobieranie specyficzne dla zapytania, aby uniknąć N+1 problemów z wyborem.
  5. nie używaj KASKADETYPÓW Usuń i wszystko.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.