Hibernate, @SequenceGenerator e allocationSize


117

Conosciamo tutti il ​​comportamento predefinito di Hibernate durante l'uso @SequenceGenerator: aumenta la sequenza del database reale di uno , moltiplica questo valore per 50 ( allocationSizevalore predefinito ) e quindi utilizza questo valore come ID entità.

Questo è un comportamento errato e è in conflitto con le specifiche che dicono:

AllocationSize - (Facoltativo) L'importo di cui incrementare quando si allocano i numeri di sequenza dalla sequenza.

Per essere chiari: non mi preoccupo degli spazi tra gli ID generati.

Mi interessano gli ID che non sono coerenti con la sequenza del database sottostante. Ad esempio: qualsiasi altra applicazione (che ad esempio utilizza JDBC semplice) potrebbe voler inserire nuove righe sotto gli ID ottenuti dalla sequenza, ma tutti questi valori potrebbero essere già utilizzati da Hibernate! Follia.

Qualcuno conosce una soluzione a questo problema (senza impostare allocationSize=1e quindi degradare le prestazioni)?

EDIT:
per rendere le cose chiare. Se l'ultimo record inserito aveva ID = 1, allora HB usa i valori 51, 52, 53...per le sue nuove entità MA allo stesso tempo: il valore della sequenza nel database sarà impostato su 2. Il che può facilmente portare a errori quando altre applicazioni utilizzano quella sequenza.

D'altra parte: la specifica dice (nella mia comprensione) che la sequenza del database avrebbe dovuto essere impostata 51e nel frattempo HB dovrebbe usare i valori dell'intervallo 2, 3 ... 50


AGGIORNAMENTO:
Come Steve Ebersole ha menzionato di seguito: il comportamento da me descritto (e anche il più intuitivo per molti) può essere abilitato impostando hibernate.id.new_generator_mappings=true.

Grazie a tutti voi.

AGGIORNAMENTO 2:
Per i futuri lettori, di seguito puoi trovare un esempio funzionante.

@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>

2
"senza impostare allocationSize = 1 e quindi degradare le prestazioni" perché peggiora le prestazioni è stato impostato su 1?
Sheidaei

3
@sheidaei vede potrebbe commentare qui sotto :-) Questo perché tutti savedevono interrogare il database per il valore successivo della sequenza.
G. Demecki

Grazie stava affrontando lo stesso problema. All'inizio stavo aggiungendo allocationSize = 1 a ogni @SequenceGenerator. L'uso di hibernate.id.new_generator_mappings = true evita che. Sebbene JPA continui a interrogare il database per ottenere l'ID per ogni inserimento ...
TheBakker

1
Con SequenceGeneratorHibernate interrogherà il database solo quando la quantità di ID specificata da allocationsizesi esaurisce. Se imposti, allocationSize = 1è il motivo per cui Hibernate interroga il DB per ogni inserimento. Cambia questo valore e hai finito.
G. Demecki

1
Grazie! l' hibernate.id.new_generator_mappingsambientazione è davvero importante. Spero che sia l'impostazione predefinita che non devo dedicare così tanto tempo alla ricerca del motivo per cui il numero ID diventa selvaggio.
LeOn - Han Li

Risposte:


43

Per essere assolutamente chiari ... ciò che descrivi non è in alcun modo in conflitto con le specifiche. La specifica parla dei valori che Hibernate assegna alle tue entità, non dei valori effettivamente memorizzati nella sequenza del database.

Tuttavia, c'è la possibilità di ottenere il comportamento che stai cercando. Per prima cosa guarda la mia risposta su Esiste un modo per scegliere dinamicamente una strategia @GeneratedValue utilizzando le annotazioni JPA e Hibernate? Questo ti darà le basi. Finché sei impostato per utilizzare quel SequenceStyleGenerator, Hibernate interpreterà allocationSizeutilizzando l '"ottimizzatore in pool" nel SequenceStyleGenerator. L '"ottimizzatore in pool" è da utilizzare con i database che consentono un'opzione di "incremento" sulla creazione di sequenze (non tutti i database che supportano le sequenze supportano un incremento). Ad ogni modo, leggi le varie strategie di ottimizzazione presenti.


Grazie Steve! La migliore risposta. Anche l'altro tuo post è stato utile.
G. Demecki

4
Ho anche notato che sei coautore di org.hibernate.id.enhanced.SequenceStyleGenerator. Mi hai sorpreso.
G. Demecki

22
Ti ha sorpreso come? Sono lo sviluppatore principale di Hibernate. Ho scritto / co-scritto molte classi di Hibernate;)
Steve Ebersole

Solo per la cronaca. L'incremento della sequenza DB dovrebbe essere evitato per evitare grandi lacune. Sequenza di DB viene moltiplicato con allocationSize quando ID piste di cache out.More dettagli stackoverflow.com/questions/5346147/...
Olcay Tarazan

1
Un modo per modificare l '"ottimizzatore" utilizzato a livello globale è aggiungere qualcosa di simile alle opzioni di ibernazione: serviceBuilder.applySetting ("hibernate.id.optimizer.pooled.preferred", LegacyHiLoAlgorithmOptimizer.class.getName ()); Invece di LegacyHiLoAlgorithOptimizer puoi scegliere qualsiasi classe di ottimizzatore e diventerà predefinita. Ciò dovrebbe rendere più facile mantenere il comportamento che desideri come predefinito senza modificare tutte le annotazioni. Inoltre, fai attenzione agli ottimizzatori "pooled" e "hilo": questi danno risultati strani quando il valore della sequenza inizia da 0 causando ID negativi.
fjalvingh

17

allocationSize=1È una micro ottimizzazione prima di ottenere la query Hibernate tenta di assegnare un valore nell'intervallo di allocationSize e quindi cerca di evitare di interrogare il database per la sequenza. Ma questa query verrà eseguita ogni volta se la imposti su 1. Questo non fa quasi alcuna differenza poiché se il tuo database è accessibile da qualche altra applicazione, allora creerà problemi se lo stesso ID viene utilizzato da un'altra applicazione nel frattempo.

La prossima generazione di Sequence Id si basa su allocationSize.

Di default viene mantenuto come se 50fosse troppo. Sarà anche utile solo se 50in una sessione avrai vicino dei record che non sono persistenti e che verranno mantenuti usando questa particolare sessione e transizione.

Quindi dovresti sempre usare allocationSize=1durante l'utilizzo SequenceGenerator. Come per la maggior parte dei database sottostanti, la sequenza è sempre incrementata di 1.


12
Niente a che vedere con le prestazioni? Ne sei davvero sicuro? Mi è stato insegnato che con allocationSize=1Hibernate su ogni saveoperazione è necessario fare il viaggio al database per poter ottenere un nuovo valore ID.
G. Demecki

2
Si tratta di una micro ottimizzazione prima di ottenere la query Hibernate cerca di assegnare un valore nell'intervallo di allocationSizee quindi cerca di evitare di interrogare il database per la sequenza. Ma questa query verrà eseguita ogni volta se la imposti su 1. Questo non fa quasi alcuna differenza poiché se il tuo database è accessibile da qualche altra applicazione, allora creerà problemi se lo stesso ID viene utilizzato da un'altra applicazione nel frattempo
Amit Deshpande

E sì, è completamente specifico dell'applicazione se una dimensione di allocazione di 1 ha un impatto reale sulle prestazioni. In un micro benchmark, ovviamente, si presenterà sempre come un enorme impatto; questo è il problema con la maggior parte dei benchmark (micro o altro), semplicemente non sono realistici. E anche se sono abbastanza complessi da essere in qualche modo realistici, devi comunque guardare quanto è vicino il benchmark alla tua applicazione effettiva per capire quanto siano applicabili i risultati del benchmark ai risultati che vedresti nella tua app. Per farla breve .. prova tu stesso
Steve Ebersole il

2
OK. Tutto è specifico dell'applicazione, non è vero? Nel caso in cui l'applicazione sia di sola lettura, l'impatto dell'utilizzo della dimensione di allocazione 1000 o 1 è assolutamente 0. D'altra parte, cose come queste sono best practice. Se non rispetti le migliori pratiche raccolte e l'impatto combinato sarà che la tua applicazione diventerà lenta. Un altro esempio potrebbe essere l'avvio di una transazione quando non ne hai assolutamente bisogno.
Hasan Ceylan

1

Steve Ebersole e altri membri,
spiegheresti gentilmente il motivo di un ID con un divario maggiore (per impostazione predefinita 50)? Sto usando Hibernate 4.2.15 e ho trovato il seguente codice in org.hibernate.id.enhanced.OptimizerFactory cass.

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

Ogni volta che colpisce l'interno dell'istruzione if, il valore hi diventa molto più grande. Quindi, il mio ID durante il test con il frequente riavvio del server genera i seguenti ID di sequenza:
1, 2, 3, 4, 19, 250, 251, 252, 400, 550, 750, 751, 752, 850, 1100, 1150.

So che hai già detto che non è in conflitto con le specifiche, ma credo che questa sarà una situazione molto inaspettata per la maggior parte degli sviluppatori.

Il contributo di chiunque sarà molto utile.

Jihwan

AGGIORNAMENTO: ne1410s: Grazie per la modifica.
cfrick: OK. Lo farò. Era il mio primo post qui e non sapevo come usarlo.

Ora, ho capito meglio perché maxLo è stato utilizzato per due scopi: poiché l'ibernazione chiama la sequenza DB una volta, continua ad aumentare l'id nel livello Java e lo salva nel DB, il valore dell'id del livello Java dovrebbe considerare quanto è stato modificato senza chiamare la sequenza DB quando chiama la sequenza la volta successiva.

Ad esempio, l'ID della sequenza era 1 in un punto e ibernazione ha inserito 5, 6, 7, 8, 9 (con allocationSize = 5). La prossima volta, quando otteniamo il numero di sequenza successivo, DB restituisce 2, ma ibernazione deve usare 10, 11, 12 ... Quindi, ecco perché "hi = lastSourceValue.copy (). MultiplyBy (maxLo + 1)" è utilizzato per ottenere un successivo ID 10 dal 2 restituito dalla sequenza DB. Sembra che l'unica cosa fastidiosa fosse durante il frequente riavvio del server e questo era il mio problema con il divario maggiore.

Quindi, quando usiamo l'ID SEQUENZA, l'id inserito nella tabella non corrisponderà al numero SEQUENZA in DB.


1

Dopo aver scavato nel codice sorgente di ibernazione e la configurazione Below va a Oracle db per il valore successivo dopo 50 inserimenti. Quindi fai aumentare il tuo INST_PK_SEQ di 50 ogni volta che viene chiamato.

Hibernate 5 viene utilizzato per la strategia sottostante

Controlla anche di seguito 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;

3
Scusate, ma questo è un modo estremamente prolisso di impostare qualcosa, che può essere espresso facilmente con due parametri per un intero Hibernate e quindi per tutte le entità.
G. Demecki

vero ma quando provo con altri modi nessuno di loro ha funzionato se lo fai funzionare può inviarmi come hai configurato
fatih tekin

Ho aggiornato la mia risposta - ora include anche un esempio funzionante. Anche se il mio commento sopra è parzialmente sbagliato: sfortunatamente, non puoi impostare allocationSizeinitialValueglobalmente per tutte le entità (a meno che non usi un solo generatore, ma IMHO non è molto leggibile).
G. Demecki

1
Grazie per la spiegazione ma quello che hai scritto sopra ho provato e non ha funzionato con hibernate 5.0.7.Versione finale quindi ho scavato nel codice sorgente per essere in grado di raggiungere questo obiettivo e questa è l'implementazione che sono riuscito a trovare nel codice sorgente di ibernazione. La configurazione potrebbe sembrare brutta, ma sfortunatamente è in ibernazione api e sto usando l'implementazione EntityManager standard di hibernate
fatih tekin

1

Anch'io ho affrontato questo problema in Hibernate 5:

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

Ho ricevuto un avviso come questo di seguito:

Trovato uso del deprecato generatore di id basato su sequenza [org.hibernate.id.SequenceHiLoGenerator]; usa invece org.hibernate.id.enhanced.SequenceStyleGenerator. Vedere Hibernate Domain Model Mapping Guide per i dettagli.

Quindi ho cambiato il mio codice in 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;

Questo ha risolto i miei due problemi:

  1. L'avviso deprecato è stato risolto
  2. Ora l'id viene generato secondo la sequenza Oracle.

0

Vorrei controllare il DDL per la sequenza nello schema. L'implementazione JPA è responsabile solo della creazione della sequenza con la dimensione di allocazione corretta. Pertanto, se la dimensione dell'allocazione è 50, la sequenza deve avere l'incremento di 50 nel DDL.

Questo caso può tipicamente verificarsi con la creazione di una sequenza con dimensione di allocazione 1 e successivamente configurata sulla dimensione di allocazione 50 (o predefinita) ma la sequenza DDL non viene aggiornata.


Stai fraintendendo il mio punto. ALTER SEQUENCE ... INCREMENTY BY 50;non risolverà nulla, perché il problema rimane lo stesso. Il valore della sequenza continua a non riflettere gli ID delle entità reali.
G. Demecki

Si prega di condividere un caso di test in modo da poter comprendere meglio il problema qui.
Hasan Ceylan

1
Caso di prova? Perché? La domanda posta da me non è stata così complicata ed è già stata data una risposta. Sembra che tu non sappia come funziona il generatore HiLo. Comunque: grazie per aver sacrificato il tuo tempo e il tuo impegno.
G. Demecki

1
Gregory, In realtà so di cosa sto parlando, ho scritto Batoo JPA che è l'implementazione JPA% 100 che è attualmente in incubazione e batte Hibernate in termini di velocità - 15 volte più veloce. D'altra parte potrei aver frainteso la tua domanda e non ho pensato che l'uso di Hibernate con le sequenze dovrebbe creare alcun problema poiché ho usato Hibernate dal 2003 in molti progetti su molti database. L'importante è che tu abbia la soluzione alla domanda, scusa, mi sono perso la risposta contrassegnata come corretta ...
Hasan Ceylan

Scusa, non volevo offenderti. Grazie ancora per il tuo aiuto, la domanda è risolta.
G. Demecki
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.