JPA: jaki jest właściwy wzorzec do iteracji po dużych zestawach wyników?

114

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.

George Armhold
źródło
Jakiej bazy danych i sterownika JDBC używasz?

Odpowiedzi:

55

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/ setMaxResultsi ręczna iteracja są naprawdę konieczne. Oto moje rozwiązanie wykorzystujące JPA:

private List<Model> getAllModelsIterable(int offset, int max)
{
    return entityManager.createQuery("from Model m", Model.class).setFirstResult(offset).setMaxResults(max).getResultList();
}

następnie użyj tego w ten sposób:

private void iterateAll()
{
    int offset = 0;

    List<Model> models;
    while ((models = Model.getAllModelsIterable(offset, 100)).size() > 0)
    {
        entityManager.getTransaction().begin();
        for (Model model : models)
        {
            log.info("do something with model: " + model.getId());
        }

        entityManager.flush();
        entityManager.clear();
        em.getTransaction().commit();
        offset += models.size();
    }
}
George Armhold
źródło
33
Myślę, że przykład nie jest bezpieczny, jeśli podczas procesu wsadowego pojawiają się nowe wkładki. Użytkownik musi uporządkować dane na podstawie kolumny, w której ma pewność, że nowo wstawione dane znajdą się na końcu listy wyników.
Balazs Zsoldos
gdy bieżąca strona jest ostatnią stroną i ma mniej niż 100 elementów, sprawdzenie size() == 100zamiast tego pominie jedno dodatkowe zapytanie, które zwróci pustą listę
cdalxndr
38

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:

        StatelessSession session = ((Session) entityManager.getDelegate()).getSessionFactory().openStatelessSession();

        Query query = session
                .createQuery("SELECT a FROM Address a WHERE .... ORDER BY a.id");
        query.setFetchSize(Integer.valueOf(1000));
        query.setReadOnly(true);
        query.setLockMode("a", LockMode.NONE);
        ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
        while (results.next()) {
            Address addr = (Address) results.get(0);
            // Do stuff
        }
        results.close();
        session.close();

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.

Zds
źródło
2
Cześć Zds, Twój przypadek użycia skanowania milionów wierszy jest z pewnością dla mnie typowy i DZIĘKUJĘ za wysłanie ostatecznego kodu. W moim przypadku wpycham rekordy do Solr, aby je zindeksować pod kątem wyszukiwania pełnotekstowego. I ze względu na zasady biznesowe, w które nie będę wchodził, muszę przejść przez Hibernację, zamiast po prostu używać JDBC lub wbudowanych modułów Solr.
Mark Bennett
Chętnie pomoże :-). Mamy też do czynienia z dużymi zbiorami danych, w tym przypadku umożliwiającymi użytkownikowi odpytywanie wszystkich nazw ulic w obrębie tego samego miasta / powiatu, a czasem nawet stanu, więc tworzenie indeksów wymaga odczytania dużej ilości danych.
Zds
Pojawia się z MySQL, naprawdę musisz przejść przez wszystkie te obręcze: stackoverflow.com/a/20900045/32453 (inne DB mogą być mniej rygorystyczne, wyobrażam sobie ...)
rogerdpack
32

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

Cyberax
źródło
17
Dzięki. Dobrze wiedzieć, że ktoś wykonuje miliardy wierszy przez Hibernate. Niektórzy tutaj twierdzą, że to niemożliwe. :-)
George Armhold
2
Czy można tu dodać przykład? Zakładam, że jest podobny do przykładu Zdsa?
rogerdpack
19

Szczerze mówiąc, sugerowałbym opuszczenie JPA i pozostanie przy JDBC (ale z pewnością przy użyciu JdbcTemplateklasy 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 potrzeba clear()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, JdbcTemplatebę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.

Tomasz Nurkiewicz
źródło
Zaletą JPA jest nie tylko brak agnostyki bazy danych, ale także możliwość nieużywania nawet tradycyjnej bazy danych (NoSQL). Od czasu do czasu nie jest trudno wykonać flush / clear i zwykle operacje wsadowe są wykonywane rzadko.
Adam Gent,
1
Cześć Thomasz. Mam wiele powodów do narzekania na JPA / Hibernate, ale z szacunkiem naprawdę wątpię, czy są one „przeznaczone do działania na wielu obiektach”. Podejrzewam, że po prostu muszę się nauczyć właściwego wzorca dla tego przypadku użycia.
George Armhold,
4
Cóż, przychodzą mi do głowy tylko dwa wzorce: paginacje (wspomniane kilkakrotnie) i flush()/ clear(). Pierwszym z nich jest IMHO nieprzeznaczone do przetwarzania wsadowego, podczas gdy sekwencja flush () / clear () pachnie jak nieszczelna abstrakcja .
Tomasz Nurkiewicz
Tak, było to połączenie paginacji i koloru / czystego, jak wspomniałeś. Dzięki!
George Armhold
7

Jeśli używasz EclipseLink I ', użyj tej metody, aby uzyskać wynik jako iterowalny

private static <T> Iterable<T> getResult(TypedQuery<T> query)
{
  //eclipseLink
  if(query instanceof JpaQuery) {
    JpaQuery<T> jQuery = (JpaQuery<T>) query;
    jQuery.setHint(QueryHints.RESULT_SET_TYPE, ResultSetType.ForwardOnly)
       .setHint(QueryHints.SCROLLABLE_CURSOR, true);

    final Cursor cursor = jQuery.getResultCursor();
    return new Iterable<T>()
    {     
      @SuppressWarnings("unchecked")
      @Override
      public Iterator<T> iterator()
      {
        return cursor;
      }
    }; 
   }
  return query.getResultList();  
}  

zamknij Metoda

static void closeCursor(Iterable<?> list)
{
  if (list.iterator() instanceof Cursor)
    {
      ((Cursor) list.iterator()).close();
    }
}
user2008477
źródło
6
Ładny obiekt jQuery
usr-local-ΕΨΗΕΛΩΝ
Wypróbowałem Twój kod, ale nadal otrzymuję OOM - wygląda na to, że wszystkie obiekty T (i wszystkie połączone obiekty tabeli, do których odwołuje się T) nigdy nie są GC. Profilowanie pokazuje, że istnieją odniesienia do nich z „tabeli” w org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork wraz z org.eclipse.persistence.internal.identitymaps.CacheKey. Zajrzałem do pamięci podręcznej i wszystkie moje ustawienia są domyślne (Wyłącz selektywne, słabe z miękką pamięcią podręczną, rozmiar pamięci podręcznej 100, upuść unieważnienie). Przyjrzę się wyłączaniu sesji i zobaczę, czy to pomaga. BTW po prostu iteruję po kursorze powrotu, używając "for (T o: results)".
Edi Bice
Badum tssssssss
dctremblay
5

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()i setMaxResult().

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

frm
źródło
Po prostu muszę zrobić coś „dla każdego” wiersza. Z pewnością jest to typowy przypadek użycia. W konkretnym przypadku, nad którym teraz pracuję, muszę wysłać zapytanie do zewnętrznej usługi sieciowej, która jest całkowicie poza moją bazą danych, używając identyfikatora (PK) z każdego wiersza. Wyniki nie są wyświetlane z powrotem w żadnej przeglądarce internetowej klienta, więc nie ma interfejsu użytkownika, o którym można by mówić. Innymi słowy, jest to praca wsadowa.
George Armhold,
Jeśli potrzebujesz "print id" dla każdego wiersza, nie ma innego sposobu, jak pobranie każdego wiersza, pobranie id i wydrukowanie. Najlepsze rozwiązanie zależy od tego, co musisz zrobić.
Dainius,
@Caffeine Coma, jeśli potrzebujesz tylko identyfikatora każdego wiersza, największa poprawa prawdopodobnie wynikałaby z pobrania tylko tej kolumny, SELECT m.id FROM Model ma następnie iteracji po liście <Integer>.
Jörn Horstmann,
1
@ Jörn Horstmann - jeśli są miliony wierszy, czy to naprawdę ma znaczenie? Chodzi mi o to, że ArrayList z milionami obiektów (choćby małych) nie będzie dobre dla stosu JVM.
George Armhold,
@Dainius: tak naprawdę moje pytanie brzmi: "jak mogę iterować po każdym wierszu bez posiadania całej tablicy ArrayList w pamięci?" Innymi słowy, chciałbym mieć interfejs do ciągnięcia N w czasie, w którym N jest znacznie mniejsze niż 1 milion. :-)
George Armhold
5

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 proste SELECT id FROM tableustawienie SERVER 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ź .

Społeczność
źródło
1
Więc mówisz, że żadna firma nie musi nigdy odwiedzać każdego wiersza tabeli użytkowników? Ich programiści po prostu wyrzucają Hibernację przez okno, kiedy przychodzi na to czas? „ nie ma sposobu na przetworzenie setek tysięcy wierszy ” - w swoim pytaniu wskazałem setFirstResult / setMaxResult, więc wyraźnie jest sposób. Pytam, czy jest lepszy.
George Armhold,
„Wciągnięcie miliona czegokolwiek, nawet do prostej listy <Integer>, nie będzie bardzo wydajne, niezależnie od tego, jak to zrobisz”. To jest dokładnie mój punkt widzenia. Pytam, jak nie tworzyć gigantycznej listy, ale raczej iterować po zestawie wyników.
George Armhold,
Użyj prostej prostej instrukcji JDBC select z FORWARD_ONLY READ_ONLY z kursorem SERVER_SIDE, jak zasugerowałem w mojej odpowiedzi. Sposób wykorzystania kursora SERVER_SIDE przez JDBC zależy od sterownika bazy danych.
1
W pełni zgadzam się z odpowiedzią. Najlepsze rozwiązanie zależy od problemu. Jeśli problemem jest łatwe ładowanie kilku jednostek, JPA jest dobre. Jeśli problemem jest efektywne wykorzystanie ogromnych ilości danych, bezpośrednie JDBC jest lepsze.
Extraneon
4
Przeszukiwanie milionów rekordów jest powszechne z wielu powodów, na przykład indeksowania ich w wyszukiwarce. I chociaż zgadzam się, że JDBC jest zwykle bardziej bezpośrednią drogą, czasami wchodzisz do projektu, który ma już bardzo złożoną logikę biznesową spakowaną w warstwę hibernacji. Jeśli go pominiesz i przejdziesz do JDBC, pominiesz logikę biznesową, której ponowne wdrożenie i utrzymanie czasami jest nietrywialne. Kiedy ludzie piszą pytania dotyczące nietypowych przypadków użycia, często wiedzą, że jest to trochę dziwne, ale mogą coś dziedziczyć zamiast budować od zera i być może nie mogą ujawnić szczegółów.
Mark Bennett
4

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

Marcin Cinik
źródło
Cześć @Marcin, czy Ty lub ktokolwiek inny może podać link do przykładowego kodu stosującego to fragmentaryczne i krokowe podejście typu id-first, najlepiej przy użyciu strumieni Java8?
krevelen
2

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 .

Zagrożenie
źródło
1
-2 głosy przeciwne - czy następny przeciwnik mógłby bronić Twojego głosu przeciw?
Niebezpieczeństwo
1
Myślałem to samo czytając te. Pytanie wskazuje na duże zadanie wsadowe bez interfejsu użytkownika. Zakładając, że nie potrzebujesz zasobów specyficznych dla serwera aplikacji, po co w ogóle używać serwera aplikacji? Procedura składowana byłaby znacznie wydajniejsza.
jdessey
@jdessey W zależności od sytuacji, powiedzmy, że mamy funkcję importu, która przy imporcie powinna zrobić coś z inną częścią systemu, np. dodać wiersze do innej tabeli w oparciu o pewne reguły biznesowe, które zostały już zakodowane jako EJB. Wtedy uruchomienie na serwerze aplikacji miałoby większy sens, chyba że można sprawić, aby komponent EJB działał w trybie osadzonym.
Archimedes Trajano
1

Aby rozwinąć odpowiedź @Tomasz Nurkiewicz. Masz dostęp do tego, DataSourcektóry z kolei może zapewnić ci połączenie

@Resource(name = "myDataSource",
    lookup = "java:comp/DefaultDataSource")
private DataSource myDataSource;

W swoim kodzie masz

try (Connection connection = myDataSource.getConnection()) {
    // raw jdbc operations
}

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.

Archimedes Trajano
źródło
0

Użyj PaginationConcept do pobierania wyników

Martwy programista
źródło
4
Paginacja jest bardzo dobra dla GUI. Ale do przetwarzania ogromnych ilości danych ScrollableResultSet został wynaleziony dawno temu. Po prostu nie ma tego w JPA.
extraneon
0

Sam się nad tym zastanawiałem. Wydaje się to mieć znaczenie:

  • jak duży jest Twój zbiór danych (wiersze)
  • jakiej implementacji JPA używasz
  • jakiego rodzaju przetwarzanie jest wykonywane dla każdego wiersza.

Napisałem Iterator, aby ułatwić zamianę obu podejść (findAll vs findEntries).

Polecam spróbować obu.

Long count = entityManager().createQuery("select count(o) from Model o", Long.class).getSingleResult();
ChunkIterator<Model> it1 = new ChunkIterator<Model>(count, 2) {

    @Override
    public Iterator<Model> getChunk(long index, long chunkSize) {
        //Do your setFirst and setMax here and return an iterator.
    }

};

Iterator<Model> it2 = List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList().iterator();


public static abstract class ChunkIterator<T> 
    extends AbstractIterator<T> implements Iterable<T>{
    private Iterator<T> chunk;
    private Long count;
    private long index = 0;
    private long chunkSize = 100;

    public ChunkIterator(Long count, long chunkSize) {
        super();
        this.count = count;
        this.chunkSize = chunkSize;
    }

    public abstract Iterator<T> getChunk(long index, long chunkSize);

    @Override
    public Iterator<T> iterator() {
        return this;
    }

    @Override
    protected T computeNext() {
        if (count == 0) return endOfData();
        if (chunk != null && chunk.hasNext() == false && index >= count) 
            return endOfData();
        if (chunk == null || chunk.hasNext() == false) {
            chunk = getChunk(index, chunkSize);
            index += chunkSize;
        }
        if (chunk == null || chunk.hasNext() == false) 
            return endOfData();
        return chunk.next();
    }

}

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ć.

Adam Gent
źródło
Odnośnie „jakiego rodzaju przetwarzania wykonujesz dla każdego wiersza” - jeśli liczba wierszy jest w milionach, podejrzewam, że nawet prosty obiekt z tylko kolumną id będzie powodować problemy. Ja również myślałem o napisaniu własnego Iteratora, który zawierał setFirstResult / setMaxResult, ale doszedłem do wniosku, że musi to być powszechny (i mam nadzieję, że rozwiązany!) Problem.
George Armhold,
@Caffeine Coma Opublikowałem mój Iterator, prawdopodobnie mógłbyś zrobić trochę więcej JPA, dostosowując się do niego. Powiedz mi, czy to pomaga. Skończyło się na tym, że nie korzystałem (zrobiłem findAll).
Adam Gent,
0

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.

  1. Użyj sesji bezstanowej z scroll ()
  2. Użyj session.clear () po każdej iteracji. Gdy trzeba dołączyć inne jednostki, załaduj je w osobnej sesji. w rzeczywistości pierwsza sesja emuluje sesję bezstanową, ale zachowuje wszystkie funkcje sesji stanowej, dopóki obiekty nie zostaną odłączone.
  3. Użyj iterate () lub list (), ale uzyskaj tylko identyfikatory w pierwszym zapytaniu, a następnie w oddzielnej sesji w każdej iteracji wykonaj session.load i zamknij sesję na koniec iteracji.
  4. Użyj Query.iterate () z EntityManager.detach () aka Session.evict ();
Larry Chu
źródło
0

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.

/*
create table my_table(
  id int primary key, -- index will be created
  my_column varchar
)
*/

fun keysetPaginationExample() {
    var lastId = Integer.MIN_VALUE
    do {

        val someItems =
        myRepository.findTop100ByMyTableIdAfterOrderByMyTableId(lastId)

        if (someItems.isEmpty()) break

        lastId = someItems.last().myTableId

        for (item in someItems) {
          process(item)
        }

    } while (true)
}
Elifarley
źródło
0

Przykład z JPA i NativeQuery pobierającym za każdym razem rozmiar elementów przy użyciu przesunięć

public List<X> getXByFetching(int fetchSize) {
        int totalX = getTotalRows(Entity);
        List<X> result = new ArrayList<>();
        for (int offset = 0; offset < totalX; offset = offset + fetchSize) {
            EntityManager entityManager = getEntityManager();
            String sql = getSqlSelect(Entity) + " OFFSET " + offset + " ROWS";
            Query query = entityManager.createNativeQuery(sql, X.class);
            query.setMaxResults(fetchSize);
            result.addAll(query.getResultList());
            entityManager.flush();
            entityManager.clear();
        return result;
    }
harryssuperman
źródło