Hibernate, @SequenceGenerator iocationSize

117

Wszyscy znamy domyślne zachowanie Hibernate'a podczas używania @SequenceGenerator- zwiększa rzeczywistą sekwencję bazy danych o jeden , pomnóż tę wartość o 50 ( allocationSizewartość domyślna ) - a następnie używa tej wartości jako identyfikatora jednostki.

Jest to nieprawidłowe zachowanie i sprzeczne ze specyfikacją, która mówi:

ocationSize - (opcjonalne) kwota do zwiększenia podczas przydzielania numerów sekwencyjnych z sekwencji.

Żeby było jasne: nie przejmuję się lukami między wygenerowanymi identyfikatorami.

Zależy mi na identyfikatorach, które niezgodne z sekwencją bazową bazy danych. Na przykład: każda inna aplikacja (która np. Używa zwykłego JDBC) może chcieć wstawić nowe wiersze pod identyfikatorami uzyskanymi z sekwencji - ale wszystkie te wartości mogą być już wykorzystane przez Hibernate! Szaleństwo.

Czy ktoś zna jakieś rozwiązanie tego problemu (bez ustawiania, allocationSize=1a tym samym obniżania wydajności)?

EDYCJA:
Aby wszystko było jasne. Jeśli ostatni wstawiony rekord miał ID = 1, to HB użyje wartości 51, 52, 53...dla swoich nowych jednostek, ALE w tym samym czasie: wartość sekwencji w bazie danych zostanie ustawiona na 2. Co może łatwo prowadzić do błędów, gdy inne aplikacje używają tej sekwencji.

Z drugiej strony: specyfikacja mówi (w moim rozumieniu), że sekwencja bazy danych powinna była być ustawiona na, 51a tymczasem HB powinien używać wartości z zakresu 2, 3 ... 50


AKTUALIZACJA:
Jak Steve Ebersole wspomniał poniżej: zachowanie opisane przeze mnie (a także najbardziej intuicyjne dla wielu) można włączyć, ustawiając hibernate.id.new_generator_mappings=true.

Dziękuję wam wszystkim.

UPDATE 2:
Dla przyszłych czytelników poniżej znajduje się działający przykład.

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USERS_SEQ")
    @SequenceGenerator(name = "USERS_SEQ", sequenceName = "SEQUENCE_USERS")
    private Long id;
}

persistence.xml

<persistence-unit name="testPU">
  <properties>
    <property name="hibernate.id.new_generator_mappings" value="true" />
  </properties>
</persistence-unit>
G. Demecki
źródło
2
„bez ustawiania parametruocationSize = 1 i tym samym obniżających wydajność” dlaczego to obniża wydajność, czy ustawisz go na 1?
sheidaei
3
@sheidaei zobacz może komentarz poniżej :-) Dzieje się tak, ponieważ każdy savemusi zapytać bazę danych o następną wartość sekwencji.
G. Demecki
Dziękuję, że masz ten sam problem. Na początku dodawałem AvenueSize = 1 na każdym @SequenceGenerator. Użycie hibernate.id.new_generator_mappings = true zapobiega temu. Chociaż JPA nadal
wysyła
1
Z SequenceGeneratorHibernate będzie wysyłać zapytania do bazy danych tylko wtedy, gdy allocationsizeskończy się ilość identyfikatorów określonych przez . Jeśli to skonfigurujesz, allocationSize = 1to jest to powód, dla którego Hibernacja wysyła zapytanie do DB dla każdej wstawki. Zmień tę wartość i gotowe.
G. Demecki
1
Dzięki! hibernate.id.new_generator_mappingsustawienie jest naprawdę ważne. Mam nadzieję, że jest to ustawienie domyślne, że nie muszę spędzać tyle czasu na badaniu, dlaczego numer id zwariował.
LeOn - Han Li

Odpowiedzi:

43

Żeby było absolutnie jasne ... to, co opisujesz, w żaden sposób nie koliduje ze specyfikacją. Specyfikacja mówi o wartościach przypisywanych przez Hibernate do twoich encji, a nie o wartościach faktycznie przechowywanych w sekwencji bazy danych.

Istnieje jednak opcja uzyskania zachowania, którego szukasz. Najpierw zobacz moją odpowiedź na temat Czy istnieje sposób dynamicznego wybierania strategii @GeneratedValue za pomocą adnotacji JPA i Hibernacji? To da ci podstawy. Dopóki jesteś skonfigurowany do używania tego SequenceStyleGeneratora, Hibernate będzie interpretować allocationSizeprzy użyciu „optymalizatora puli” w SequenceStyleGenerator. „Optymalizator puli” jest przeznaczony do użytku z bazami danych, które umożliwiają opcję „inkrementacji” tworzenia sekwencji (nie wszystkie bazy danych obsługujące sekwencje obsługują przyrost). W każdym razie przeczytaj o różnych strategiach optymalizacji.

Steve Ebersole
źródło
Dzięki Steve! Najlepsza odpowiedź. Pomocny był również twój drugi post .
G. Demecki
4
Zauważyłem też, że jesteś współautorem org.hibernate.id.enhanced.SequenceStyleGenerator. Zaskoczyłeś mnie.
G. Demecki
22
Zdziwiony, jak? Jestem głównym programistą Hibernate. Napisałem / współautorem wielu klas Hibernate;)
Steve Ebersole
Tak dla porządku. Należy unikać przyrostu sekwencji DB, aby zapobiec dużym przerwom. Sekwencja bazy danych jest mnożona przez alokację
Rozmiar po wyczerpaniu
1
Jednym ze sposobów zmiany "optymalizatora" używanego globalnie jest dodanie czegoś takiego do opcji hibernacji: serviceBuilder.applySetting ("hibernate.id.optimizer.pooled.preferred", LegacyHiLoAlgorithmOptimizer.class.getName ()); Zamiast LegacyHiLoAlgorithOptimizer możesz wybrać dowolną klasę optymalizatora i stanie się ona domyślna. Powinno to ułatwić zachowanie domyślnego zachowania bez zmiany wszystkich adnotacji. Ponadto uważaj na optymalizatory „puli” i „hilo”: dają one dziwne wyniki, gdy wartość sekwencji zaczyna się od 0, powodując ujemne identyfikatory.
fjalvingh
17

allocationSize=1Jest to mikro optymalizacja przed otrzymaniem zapytania Hibernate stara się przypisać wartość z zakresu przydzielenia rozmiaru, a więc stara się unikać zapytania bazy danych o sekwencję. Ale to zapytanie będzie wykonywane za każdym razem, jeśli ustawisz je na 1. Nie robi to żadnej różnicy, ponieważ jeśli do Twojej bazy danych ma dostęp inna aplikacja, spowoduje to problemy, jeśli ten sam identyfikator będzie używany w międzyczasie przez inną aplikację.

Następna generacja Id sekwencji jest oparta na rozmiarze przydzielenia.

Domyślnie jest 50to za dużo. Pomoże to również tylko wtedy, gdy będziesz mieć blisko50 rekordów w jednej sesji, które nie są utrwalone i które zostaną utrwalone przy użyciu tej konkretnej sesji i transakcji.

Dlatego zawsze powinieneś używać allocationSize=1podczas używania SequenceGenerator. Podobnie jak w przypadku większości bazowych baz danych, sekwencja jest zawsze zwiększana o 1.

Amit Deshpande
źródło
12
Nie masz nic wspólnego z wydajnością? Czy jesteś naprawdę pewien? Nauczono mnie, że z allocationSize=1Hibernate podczas każdej saveoperacji trzeba zrobić podróż do bazy danych w celu uzyskania nowej wartości ID.
G. Demecki
2
Jest to mikro optymalizacja przed otrzymaniem zapytania Hibernate próbuje przypisać wartość z zakresu, allocationSizewięc stara się unikać zapytania bazy danych o sekwencję. Ale to zapytanie będzie wykonywane za każdym razem, jeśli ustawisz je na 1. Nie robi to żadnej różnicy, ponieważ jeśli twoja baza danych jest używana przez jakąś inną aplikację, spowoduje to problemy, jeśli ten sam identyfikator będzie w międzyczasie używany przez inną aplikację
Amit Deshpande
I tak, to jest całkowicie zależne od aplikacji, czy rozmiar alokacji 1 ma jakikolwiek rzeczywisty wpływ na wydajność. Oczywiście w mikro benchmarku zawsze będzie to miało ogromny wpływ; na tym polega problem z większością wskaźników (mikro lub innych), po prostu nie są one realistyczne. Nawet jeśli są one na tyle złożone, że są nieco realistyczne, nadal musisz przyjrzeć się, jak blisko jest punkt odniesienia w stosunku do rzeczywistej aplikacji, aby zrozumieć, jak odpowiednie są wyniki testu porównawczego do wyników, które zobaczysz w swojej aplikacji. Krótko mówiąc… przetestuj to sam
Steve Ebersole
2
DOBRZE. Wszystko zależy od aplikacji, prawda! W przypadku, gdy aplikacja jest aplikacją tylko do odczytu, wpływ wykorzystania rozmiaru alokacji 1000 lub 1 wynosi absolutnie 0. Z drugiej strony takie rzeczy są najlepszymi praktykami. Jeśli nie będziesz przestrzegać najlepszych praktyk, które gromadzą, a połączony wpływ będzie spowolniony. Innym przykładem może być rozpoczęcie transakcji, gdy absolutnie jej nie potrzebujesz.
Hasan Ceylan
1

Steve Ebersole i inni członkowie,
Czy mógłbyś uprzejmie wyjaśnić powód dla identyfikatora z większą luką (domyślnie 50)? Używam Hibernate 4.2.15 i znalazłem następujący kod w kasecie org.hibernate.id.enhanced.OptimizerFactory.

if ( lo > maxLo ) {
   lastSourceValue = callback.getNextValue();
   lo = lastSourceValue.eq( 0 ) ? 1 : 0;
   hi = lastSourceValue.copy().multiplyBy( maxLo+1 ); 
}  
value = hi.copy().add( lo++ );

Za każdym razem, gdy trafia do wnętrza instrukcji if, wartość hi znacznie się zwiększa. Tak więc mój identyfikator podczas testowania z częstym restartem serwera generuje następujące identyfikatory sekwencji:
1, 2, 3, 4, 19, 250, 251, 252, 400, 550, 750, 751, 752, 850, 1100, 1150.

Wiem, że już powiedziałeś, że nie jest to sprzeczne ze specyfikacją, ale uważam, że będzie to bardzo nieoczekiwana sytuacja dla większości programistów.

Każdy wkład będzie bardzo pomocny.

Jihwan

UPDATE: ne1410s: Dzięki za edycję.
cfrick: OK. Zrobię to. To był mój pierwszy wpis tutaj i nie byłem pewien, jak go użyć.

Teraz lepiej zrozumiałem, dlaczego maxLo zostało użyte do dwóch celów: ponieważ hibernacja wywołuje sekwencję DB raz, zwiększaj identyfikator na poziomie Java i zapisuj go w bazie danych, wartość identyfikatora poziomu Java powinna uwzględniać, ile zostało zmienione bez wywoływania sekwencja DB, gdy wywoła sekwencję następnym razem.

Na przykład identyfikator sekwencji wynosił 1 w danym momencie, a hibernacja wprowadzono 5, 6, 7, 8, 9 (z przydziałem wielkości = 5). Następnym razem, gdy otrzymamy kolejny numer sekwencji, DB zwraca 2, ale hibernacja musi używać 10, 11, 12 ... Dlatego właśnie "hi = lastSourceValue.copy (). MultiplyBy (maxLo + 1)" jest używany do uzyskania następnego id 10 z 2 zwróconych z sekwencji DB. Wydaje się, że przeszkadzało tylko częste ponowne uruchamianie serwera i to był mój problem z większą luką.

Tak więc, kiedy używamy SEQUENCE ID, wstawiony identyfikator w tabeli nie będzie zgodny z numerem SEQUENCE w DB.

Jihwan
źródło
1

Po przekopaniu się do hibernacji kodu źródłowego i poniższej konfiguracji przechodzi do bazy danych Oracle po następną wartość po 50 wstawieniach. Zrób więc przyrost INST_PK_SEQ o 50 przy każdym wywołaniu.

Hibernate 5 jest używany do poniższej strategii

Sprawdź również poniżej http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#identifiers-generators-sequence

@Id
@Column(name = "ID")
@GenericGenerator(name = "INST_PK_SEQ", 
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
        @org.hibernate.annotations.Parameter(
                name = "optimizer", value = "pooled-lo"),
        @org.hibernate.annotations.Parameter(
                name = "initial_value", value = "1"),
        @org.hibernate.annotations.Parameter(
                name = "increment_size", value = "50"),
        @org.hibernate.annotations.Parameter(
                name = SequenceStyleGenerator.SEQUENCE_PARAM, value = "INST_PK_SEQ"),
    }
)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "INST_PK_SEQ")
private Long id;
fatih tekin
źródło
3
Przepraszamy, ale jest to niezwykle rozwlekły sposób konfiguracji czegoś, co można łatwo wyrazić dwoma parametrami dla całej Hibernacji, a więc dla wszystkich podmiotów.
G. Demecki
prawda, ale kiedy spróbuję z innymi sposobami, żaden z nich nie działał, jeśli masz działa, możesz wysłać mi, jak skonfigurowałeś
fatih tekin
Zaktualizowałem moją odpowiedź - teraz zawiera również działający przykład. Chociaż mój komentarz powyżej jest częściowo błędny: niestety nie można ustawić ani allocationSizeani initialValueglobalnie dla wszystkich podmiotów (chyba że używasz tylko jednego generatora, ale IMHO nie jest bardzo czytelny).
G. Demecki
1
Dzięki za wyjaśnienie, ale to, co napisałeś powyżej, wypróbowałem i nie zadziałało z hibernacją 5.0.7. Wersja ostateczna, a następnie zagłębiłem się w kod źródłowy, aby móc osiągnąć ten cel i jest to implementacja, którą udało mi się znaleźć w kodzie źródłowym hibernacji. Konfiguracja może wyglądać źle, ale niestety hibernuje api i używam standardowej implementacji EntityManager Hibernate
fatih tekin
1

Ja też napotkałem ten problem w Hibernate 5:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
@SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE)
private Long titId;

Oto ostrzeżenie takie jak to:

Znaleziono użycie przestarzałego generatora identyfikatorów opartego na sekwencji [org.hibernate.id.SequenceHiLoGenerator]; zamiast tego użyj org.hibernate.id.enhanced.SequenceStyleGenerator. Szczegółowe informacje można znaleźć w Przewodniku mapowania modelu domeny Hibernate.

Następnie zmieniłem mój kod na SequenceStyleGenerator:

@Id
@GenericGenerator(name="cmrSeq", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
        parameters = {
                @Parameter(name = "sequence_name", value = "SEQUENCE")}
)
@GeneratedValue(generator = "sequence_name")
private Long titId;

To rozwiązało moje dwa problemy:

  1. Nieaktualne ostrzeżenie zostało naprawione
  2. Teraz identyfikator jest generowany zgodnie z sekwencją wyroczni.
Mohamed Afzal
źródło
0

Sprawdziłbym DDL pod kątem sekwencji w schemacie. Implementacja JPA odpowiada tylko za stworzenie sekwencji o odpowiednim rozmiarze alokacji. Dlatego jeśli rozmiar alokacji wynosi 50, sekwencja musi mieć przyrost o 50 w swoim DDL.

Ten przypadek może zwykle wystąpić podczas tworzenia sekwencji o rozmiarze alokacji 1, a następnie później konfigurowanej na rozmiar alokacji 50 (lub domyślny), ale sekwencja DDL nie jest aktualizowana.

Hasan Ceylan
źródło
Źle rozumiesz mój punkt widzenia. ALTER SEQUENCE ... INCREMENTY BY 50;niczego nie rozwiąże, ponieważ problem nadal pozostaje ten sam. Wartość sekwencji nadal nie odzwierciedla rzeczywistych identyfikatorów jednostek.
G. Demecki
Udostępnij przypadek testowy, abyśmy mogli lepiej zrozumieć problem.
Hasan Ceylan
1
Przypadek testowy? Czemu? Zadane przeze mnie pytanie nie było zbyt skomplikowane i już na nie odpowiedział. Wygląda na to, że nie wiesz, jak działa generator HiLo. W każdym razie: dziękuję za poświęcenie czasu i wysiłku.
G. Demecki
1
Gregory, Właściwie to wiem, o czym mówię, napisałem Batoo JPA czyli implementację% 100 JPA, która jest obecnie w fazie inkubacji i bije Hibernację pod względem szybkości - 15 razy szybciej. Z drugiej strony mogłem źle zrozumieć twoje pytanie i nie sądziłem, że używanie Hibernate z sekwencjami powinno w ogóle stwarzać jakiekolwiek problemy, ponieważ używam Hibernate od 2003 r. W wielu projektach na wielu bazach danych. Ważną rzeczą jest to, że masz rozwiązanie na pytanie, przepraszam, że przegapiłem odpowiedź oznaczoną jako poprawną ...
Hasan Ceylan
Przepraszam, nie chciałem cię urazić. Jeszcze raz dziękuję za pomoc, odpowiedź na pytanie.
G. Demecki