Powiedzmy, że mam tabelę z milionami wierszy. Używając JPA, jaki jest właściwy sposób iteracji zapytania względem tej tabeli, tak że nie mam całej listy w pamięci z milionami obiektów?
Na przykład podejrzewam, że następujące elementy wybuchną, jeśli stół jest duży:
List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();
for (Model model : models)
{
System.out.println(model.getId());
}
Czy paginacja (zapętlanie i ręczna aktualizacja setFirstResult()
/ setMaxResult()
) to naprawdę najlepsze rozwiązanie?
Edycja : główny przypadek użycia, na który kieruję, to rodzaj pracy wsadowej. W porządku, jeśli bieganie zajmuje dużo czasu. Nie jest zaangażowany żaden klient sieciowy; Muszę tylko „zrobić coś” dla każdego wiersza, po jednym (lub kilku małych N) na raz. Po prostu staram się nie mieć ich wszystkich w pamięci w tym samym czasie.
Odpowiedzi:
Strona 537 Java Persistence with Hibernate daje rozwiązanie przy użyciu
ScrollableResults
, ale niestety jest to tylko dla Hibernate.Wydaje się więc, że użycie
setFirstResult
/setMaxResults
i ręczna iteracja są naprawdę konieczne. Oto moje rozwiązanie wykorzystujące JPA:następnie użyj tego w ten sposób:
źródło
size() == 100
zamiast tego pominie jedno dodatkowe zapytanie, które zwróci pustą listęWypróbowałem odpowiedzi tutaj przedstawione, ale JBoss 5.1 + MySQL Connector / J 5.1.15 + Hibernate 3.3.2 nie działały z nimi. Właśnie przeprowadziliśmy migrację z JBoss 4.x do JBoss 5.1, więc na razie się z nim utknęliśmy, a zatem najnowsza wersja Hibernate, której możemy użyć, to 3.3.2.
Dodanie kilku dodatkowych parametrów wykonało zadanie, a kod taki jak ten działa bez OOME:
Kluczowe wiersze to parametry zapytania między createQuery i scroll. Bez nich wywołanie "scroll" próbuje załadować wszystko do pamięci i albo nigdy nie kończy się, albo działa do OutOfMemoryError.
źródło
Tak naprawdę nie można tego zrobić w prostym JPA, jednak Hibernate obsługuje sesje bezstanowe i przewijalne zestawy wyników.
Z jego pomocą rutynowo przetwarzamy miliardy wierszy.
Oto link do dokumentacji: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession
źródło
Szczerze mówiąc, sugerowałbym opuszczenie JPA i pozostanie przy JDBC (ale z pewnością przy użyciu
JdbcTemplate
klasy wsparcia itp.). JPA (i inni dostawcy / specyfikacje ORM) nie są przeznaczone do działania na wielu obiektach w ramach jednej transakcji, ponieważ zakładają, że wszystko, co załadowane, powinno pozostać w pamięci podręcznej pierwszego poziomu (stąd potrzebaclear()
w JPA).Polecam również bardziej niskopoziomowe rozwiązanie, ponieważ narzut ORM (odbicie jest tylko wierzchołkiem góry lodowej) może być tak znaczący, że iteracja po gładkiej powierzchni
ResultSet
, nawet przy użyciu lekkiej podpory, jak wspomniano,JdbcTemplate
będzie znacznie szybsza.JPA po prostu nie jest przeznaczony do wykonywania operacji na dużej liczbie podmiotów. Możesz grać z
flush()
/,clear()
aby uniknąćOutOfMemoryError
, ale rozważ to jeszcze raz. Zyskujesz bardzo mało płacąc cenę ogromnego zużycia zasobów.źródło
flush()
/clear()
. Pierwszym z nich jest IMHO nieprzeznaczone do przetwarzania wsadowego, podczas gdy sekwencja flush () / clear () pachnie jak nieszczelna abstrakcja .Jeśli używasz EclipseLink I ', użyj tej metody, aby uzyskać wynik jako iterowalny
zamknij Metoda
źródło
To zależy od rodzaju operacji, którą musisz wykonać. Dlaczego zapętlasz ponad milion wierszy? Aktualizujesz coś w trybie wsadowym? Czy zamierzasz wyświetlić wszystkie rekordy klientowi? Czy obliczasz jakieś statystyki dotyczące pobranych jednostek?
Jeśli zamierzasz wyświetlić klientowi milion rekordów, rozważ ponownie swój interfejs użytkownika. W takim przypadku odpowiednim rozwiązaniem jest podzielenie wyników na strony i użycie
setFirstResult()
isetMaxResult()
.Jeśli uruchomiłeś aktualizację dużej liczby rekordów, lepiej zachowaj prostotę i obsługę aktualizacji
Query.executeUpdate()
. Opcjonalnie można przeprowadzić aktualizację w trybie asynchronicznym za pomocą komponentu Bean sterowanego komunikatami lub Work Manager.Jeśli obliczasz statystyki pobranych jednostek, możesz skorzystać z funkcji grupowania zdefiniowanych w specyfikacji JPA.
W każdym innym przypadku podaj bardziej szczegółowe informacje :)
źródło
SELECT m.id FROM Model m
a następnie iteracji po liście <Integer>.Nie ma „właściwego” tego, co należy zrobić, nie to jest przeznaczone do JPA, JDO lub innego ORM-u. Prosty JDBC będzie najlepszą alternatywą, ponieważ można go skonfigurować tak, aby przywrócić niewielką liczbę wierszy w czas i opróżnij je, gdy są używane, dlatego istnieją kursory po stronie serwera.
Narzędzia ORM nie są przeznaczone do przetwarzania zbiorczego, są zaprojektowane tak, aby umożliwić manipulowanie obiektami i próbować uczynić RDBMS, w którym przechowywane są dane, możliwie jak najbardziej przejrzystym, a większość z nich przynajmniej do pewnego stopnia zawodzi w części przezroczystej. W tej skali nie ma sposobu na przetworzenie setek tysięcy wierszy (obiektów), a tym bardziej milionów za pomocą dowolnego ORM i wykonanie go w rozsądnym czasie ze względu na obciążenie związane z tworzeniem obiektów, proste i proste.
Użyj odpowiedniego narzędzia. Prosty JDBC i procedury składowane zdecydowanie mają swoje miejsce w 2011 roku, zwłaszcza w tym, co robią lepiej niż te ramy ORM.
Wyciągnięcie miliona czegokolwiek, nawet do prostego,
List<Integer>
nie będzie zbyt wydajne, niezależnie od tego, jak to zrobisz. Prawidłowy sposób na zrobienie tego, o co prosisz, to prosteSELECT id FROM table
ustawienieSERVER SIDE
(zależne od dostawcy) i kursor naFORWARD_ONLY READ-ONLY
i iteracja po tym.Jeśli naprawdę pobierasz miliony identyfikatorów do przetworzenia, wywołując z każdym serwerem WWW, będziesz musiał również wykonać równoległe przetwarzanie, aby to działało w rozsądnym czasie. Przeciąganie za pomocą kursora JDBC i umieszczanie kilku z nich naraz w ConcurrentLinkedQueue oraz posiadanie małej puli wątków (liczba procesorów / rdzeni + 1) ściąganie i przetwarzanie ich to jedyny sposób na wykonanie zadania na maszynie z dowolnym " normalna ilość pamięci RAM, biorąc pod uwagę, że już zaczyna brakować pamięci.
Zobacz również tę odpowiedź .
źródło
Możesz użyć innej „sztuczki”. Załaduj tylko zbiór identyfikatorów podmiotów, którymi jesteś zainteresowany. Powiedz, że identyfikator jest typu long = 8 bajtów, a następnie 10 ^ 6 lista takich identyfikatorów to około 8 MB. Jeśli jest to proces wsadowy (jedna instancja na raz), to można to znieść. Następnie po prostu wykonaj iterację i wykonaj zadanie.
Jeszcze jedna uwaga - i tak powinieneś to robić fragmentami - zwłaszcza jeśli modyfikujesz rekordy, w przeciwnym razie segment wycofywania w bazie danych będzie rósł.
Jeśli chodzi o ustawienie strategii firstResult / maxRows - będzie to BARDZO BARDZO powolne dla wyników daleko od góry.
Weź również pod uwagę, że baza danych prawdopodobnie działa w izolacji zatwierdzonej do odczytu , więc aby uniknąć odczytów fantomowych, odczytuje identyfikatory ładowania, a następnie ładuje jednostki jeden po drugim (lub 10 na 10 lub cokolwiek).
źródło
Zaskoczyło mnie, że użycie procedur składowanych nie było bardziej widoczne w odpowiedziach tutaj. W przeszłości, gdy musiałem coś takiego zrobić, tworzę procedurę składowaną, która przetwarza dane w małych kawałkach, potem przez chwilę usypia, a potem kontynuuje. Powodem uśpienia jest to, aby nie przeciążać bazy danych, która prawdopodobnie jest również wykorzystywana do wykonywania zapytań w czasie rzeczywistym, takich jak połączenie z witryną internetową. Jeśli nikt inny nie korzysta z bazy danych, możesz pominąć sen. Jeśli chcesz mieć pewność, że każdy rekord jest przetwarzany raz i tylko raz, musisz utworzyć dodatkową tabelę (lub pole) do przechowywania przetworzonych rekordów, aby zapewnić odporność na ponowne uruchomienie.
Oszczędności wydajności są tutaj znaczące, prawdopodobnie o rząd wielkości szybciej niż cokolwiek, co można zrobić w środowisku JPA / Hibernate / AppServer, a serwer bazy danych najprawdopodobniej będzie miał własny mechanizm typu kursora po stronie serwera do wydajnego przetwarzania dużych zestawów wyników. Oszczędność wydajności wynika z braku konieczności wysyłania danych z serwera bazy danych do serwera aplikacji, gdzie przetwarzane są dane, a następnie wysyłane z powrotem.
Istnieją poważne wady korzystania z procedur składowanych, które mogą całkowicie wykluczyć to dla Ciebie, ale jeśli masz tę umiejętność w swoim osobistym zestawie narzędzi i możesz jej użyć w takiej sytuacji, możesz dość szybko wyeliminować tego typu rzeczy .
źródło
Aby rozwinąć odpowiedź @Tomasz Nurkiewicz. Masz dostęp do tego,
DataSource
który z kolei może zapewnić ci połączenieW swoim kodzie masz
Umożliwi to ominięcie JPA w przypadku niektórych określonych dużych operacji wsadowych, takich jak import / eksport, jednak nadal masz dostęp do menedżera encji dla innych operacji JPA, jeśli go potrzebujesz.
źródło
Użyj
Pagination
Concept do pobierania wynikówźródło
Sam się nad tym zastanawiałem. Wydaje się to mieć znaczenie:
Napisałem Iterator, aby ułatwić zamianę obu podejść (findAll vs findEntries).
Polecam spróbować obu.
Skończyło się na tym, że nie korzystałem z mojego iteratora fragmentów (więc może to nie być tak przetestowane). Nawiasem mówiąc, będziesz potrzebować kolekcji Google, jeśli chcesz z niej korzystać.
źródło
Dzięki hibernacji istnieją 4 różne sposoby osiągnięcia tego, co chcesz. Każdy ma projektowe kompromisy, ograniczenia i konsekwencje. Proponuję zbadać każdy z nich i zdecydować, który jest odpowiedni dla Twojej sytuacji.
źródło
Oto prosty, prosty przykład JPA (w Kotlinie), który pokazuje, jak można podzielić na strony dowolnie duży zestaw wyników, odczytując fragmenty po 100 pozycji na raz, bez użycia kursora (każdy kursor zużywa zasoby w bazie danych). Używa paginacji zestawu kluczy.
Zobacz https://use-the-index-luke.com/no-offset, aby zapoznać się z koncepcją paginacji zestawu kluczy i https://www.citusdata.com/blog/2016/03/30/five-ways-to- paginate / w celu porównania różnych sposobów podziału na strony wraz z ich wadami.
źródło
Przykład z JPA i NativeQuery pobierającym za każdym razem rozmiar elementów przy użyciu przesunięć
źródło