Hibernate, @SequenceGenerator et allocationSize

117

Nous connaissons tous le comportement par défaut de Hibernate lors de l'utilisation @SequenceGenerator- il augmente la séquence de base de données réelle de un , multiplie cette valeur par 50 (valeur par défaut allocationSize) - puis utilise cette valeur comme ID d'entité.

C'est un comportement incorrect et entre en conflit avec la spécification qui dit:

allocationSize - (Facultatif) Le montant à incrémenter lors de l'allocation des numéros de séquence à partir de la séquence.

Pour être clair: je ne me soucie pas des écarts entre les identifiants générés.

Je me soucie des ID qui ne sont pas cohérents avec la séquence de base de données sous-jacente. Par exemple: toute autre application (qui utilise par exemple JDBC brut) peut vouloir insérer de nouvelles lignes sous les ID obtenus à partir de la séquence - mais toutes ces valeurs peuvent déjà être utilisées par Hibernate! La démence.

Quelqu'un connaît-il une solution à ce problème (sans réglage allocationSize=1et donc dégradation des performances)?

EDIT:
Pour clarifier les choses. Si le dernier enregistrement inséré avait ID = 1, alors HB utilise des valeurs 51, 52, 53...pour ses nouvelles entités MAIS en même temps: la valeur de la séquence dans la base de données sera définie sur 2. Ce qui peut facilement conduire à des erreurs lorsque d'autres applications utilisent cette séquence.

D'un autre côté: la spécification dit (à ma connaissance) que la séquence de la base de données aurait dû être définie sur 51et, entre-temps, HB devrait utiliser des valeurs de range 2, 3 ... 50


MISE
À JOUR: Comme Steve Ebersole l'a mentionné ci-dessous: le comportement décrit par moi (et aussi le plus intuitif pour beaucoup) peut être activé en définissant hibernate.id.new_generator_mappings=true.

Merci à vous tous.

MISE À JOUR 2:
Pour les futurs lecteurs, vous trouverez ci-dessous un exemple fonctionnel.

@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
la source
2
"sans définir allocationSize = 1 et donc dégrader les performances" pourquoi cela dégrade les performances le définissez-vous sur 1?
sheidaei
3
@sheidaei voir peut commenter ci-dessous :-) C'est parce que tout le monde savedoit interroger la base de données pour la valeur suivante de la séquence.
G.Demecki
Merci faisait face au même problème. Au début, j'ajoutais allocationSize = 1 à chaque @SequenceGenerator. L'utilisation de hibernate.id.new_generator_mappings = true empêche cela. Bien que JPA interroge toujours la base de données pour obtenir l'ID de chaque insertion ...
TheBakker
1
Avec SequenceGeneratorHibernate interrogera la base de données uniquement lorsque le nombre d'ID spécifiés par allocationsizes'épuise. Si vous configurez, allocationSize = 1c'est la raison pour laquelle Hibernate interroge la base de données pour chaque insertion. Modifiez cette valeur et vous avez terminé.
G.Demecki
1
Merci! le hibernate.id.new_generator_mappingscadre est vraiment important. J'espère que c'est le paramètre par défaut que je n'ai pas à passer autant de temps à rechercher pourquoi le numéro d'identification devient sauvage.
LeOn - Han Li

Réponses:

43

Pour être absolument clair ... ce que vous décrivez n'est en aucun cas en conflit avec la spécification. La spécification parle des valeurs qu'Hibernate attribue à vos entités, et non des valeurs réellement stockées dans la séquence de base de données.

Cependant, il existe la possibilité d'obtenir le comportement que vous recherchez. Voir d'abord ma réponse sur Existe-t-il un moyen de choisir dynamiquement une stratégie @GeneratedValue en utilisant les annotations JPA et Hibernate? Cela vous donnera les bases. Tant que vous êtes configuré pour utiliser ce SequenceStyleGenerator, Hibernate interprétera en allocationSizeutilisant «l'optimiseur groupé» dans SequenceStyleGenerator. L '"optimiseur groupé" est destiné à être utilisé avec des bases de données qui permettent une option "incrémenter" sur la création de séquences (toutes les bases de données prenant en charge les séquences ne prennent pas en charge un incrément). Quoi qu'il en soit, renseignez-vous sur les différentes stratégies d'optimisation.

Steve Ebersole
la source
Merci Steve! La meilleure réponse. Votre autre message a également été utile.
G.Demecki
4
J'ai également remarqué que vous êtes co-auteur de org.hibernate.id.enhanced.SequenceStyleGenerator. Vous m'avez surpris.
G.Demecki
22
Vous avez surpris comment? Je suis le développeur principal d'Hibernate. J'ai écrit / co-écrit de nombreux cours Hibernate;)
Steve Ebersole
Juste pour info. L'incrément de séquence DB doit être évité pour éviter de grands écarts. La séquence de base de données est multipliée par l'allocationSize lorsque le cache d'ID est épuisé.Plus de détails stackoverflow.com/questions/5346147/...
Olcay Tarazan
1
Une façon de changer l '"optimiseur" utilisé globalement est d'ajouter quelque chose comme ceci à vos options de mise en veille prolongée: serviceBuilder.applySetting ("hibernate.id.optimizer.pooled.preferred", LegacyHiLoAlgorithmOptimizer.class.getName ()); Au lieu de LegacyHiLoAlgorithOptimizer, vous pouvez choisir n'importe quelle classe d'optimisation, et elle deviendra par défaut. Cela devrait faciliter le maintien du comportement souhaité par défaut sans modifier toutes les annotations. De plus, méfiez-vous des optimiseurs "pooled" et "hilo": ceux-ci donnent des résultats bizarres lorsque la valeur de votre séquence commence à 0 provoquant des ID négatifs.
fjalvingh
17

allocationSize=1Il s'agit d'une micro-optimisation avant d'obtenir la requête Hibernate essaie d'attribuer une valeur dans la plage d'allocationSize et essaie donc d'éviter d'interroger la base de données pour la séquence. Mais cette requête sera exécutée à chaque fois si vous la définissez sur 1. Cela ne fait pratiquement aucune différence puisque si votre base de données est accédée par une autre application, cela créera des problèmes si le même identifiant est utilisé par une autre application entre-temps.

La prochaine génération d'ID de séquence est basée sur allocationSize.

Par défaut, il est conservé comme 50ce qui est trop. Cela ne vous aidera également que si vous allez avoir près d' 50enregistrements dans une session qui ne sont pas persistants et qui seront conservés en utilisant cette session et cette transition particulières.

Vous devez donc toujours utiliser allocationSize=1lors de l'utilisation SequenceGenerator. Comme pour la plupart des bases de données sous-jacentes, la séquence est toujours incrémentée de 1.

Amit Deshpande
la source
12
Rien à voir avec la performance? Etes-vous vraiment sûr? On m'a appris qu'avec allocationSize=1Hibernate, chaque saveopération doit faire le voyage vers la base de données afin d'obtenir une nouvelle valeur d'identification.
G.Demecki
2
Il s'agit d'une micro-optimisation avant d'obtenir la requête Hibernate essaie d'attribuer une valeur dans la plage de allocationSizeet donc d'éviter d'interroger la base de données pour la séquence. Mais cette requête sera exécutée à chaque fois si vous la définissez sur 1. Cela ne fait pratiquement aucune différence puisque si votre base de données est accédée par une autre application, cela créera des problèmes si le même identifiant est utilisé par une autre application entre
Amit Deshpande
Et oui, il est complètement spécifique à l'application qu'une taille d'allocation de 1 ait un impact réel sur les performances. Dans un micro-benchmark, bien sûr, cela se manifestera toujours comme un impact énorme; c'est le problème avec la plupart des benchmarks (micro ou autre), ils ne sont tout simplement pas réalistes. Et même s'ils sont suffisamment complexes pour être quelque peu réalistes, vous devez toujours regarder à quel point le benchmark est proche de votre application réelle pour comprendre dans quelle mesure les résultats du benchmark sont applicables aux résultats que vous verriez dans votre application. En bref ... testez-le par vous
Steve Ebersole
2
D'ACCORD. Tout est spécifique à l'application, n'est-ce pas! Si votre application est une application en lecture seule, l’impact de l’utilisation de la taille d’allocation 1 000 ou 1 est absolument égal à 0. En revanche, ce sont les meilleures pratiques. Si vous ne respectez pas les meilleures pratiques qu'ils rassemblent et l'impact combiné sera votre application devient lente. Un autre exemple serait de démarrer une transaction alors que vous n'en avez absolument pas besoin.
Hasan Ceylan
1

Steve Ebersole et autres membres,
pourriez-vous expliquer la raison d'un identifiant avec un écart plus grand (par défaut 50)? J'utilise Hibernate 4.2.15 et j'ai trouvé le code suivant dans 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++ );

Chaque fois qu'il atteint l'intérieur de l'instruction if, la valeur hi devient beaucoup plus grande. Ainsi, mon identifiant lors des tests avec le redémarrage fréquent du serveur génère les identifiants de séquence suivants:
1, 2, 3, 4, 19, 250, 251, 252, 400, 550, 750, 751, 752, 850, 1100, 1150.

Je sais que vous avez déjà dit que cela n'était pas en conflit avec les spécifications, mais je pense que ce sera une situation très inattendue pour la plupart des développeurs.

La contribution de n'importe qui sera très utile.

Jihwan

MISE À JOUR: ne1410s: Merci pour la modification.
cfrick: OK. Je le ferai. C'était mon premier message ici et je ne savais pas comment l'utiliser.

Maintenant, j'ai mieux compris pourquoi maxLo a été utilisé à deux fins: comme la mise en veille prolongée appelle la séquence DB une fois, continuez d'augmenter l'id au niveau Java et l'enregistre dans la base de données, la valeur de l'id de niveau Java devrait considérer combien a été changé sans appeler la séquence DB lors de l'appel de la séquence la prochaine fois.

Par exemple, l'ID de séquence était 1 à un point et la mise en veille prolongée a entré 5, 6, 7, 8, 9 (avec allocationSize = 5). La prochaine fois, lorsque nous obtenons le numéro de séquence suivant, DB renvoie 2, mais hibernate doit utiliser 10, 11, 12 ... C'est pourquoi "hi = lastSourceValue.copy (). MultiplyBy (maxLo + 1)" est utilisé pour obtenir un prochain identifiant 10 à partir des 2 renvoyés par la séquence DB. Il semble que la seule chose gênante ait été lors du redémarrage fréquent du serveur et c'était mon problème avec le plus grand écart.

Ainsi, lorsque nous utilisons l'ID de SEQUENCE, l'ID inséré dans la table ne correspondra pas au numéro de SEQUENCE dans DB.

Jihwan
la source
1

Après avoir fouillé dans le code source de mise en veille prolongée et la configuration ci-dessous, accédez à Oracle db pour la valeur suivante après 50 insertions. Faites donc votre INST_PK_SEQ incrémenter de 50 chaque fois qu'il est appelé.

Hibernate 5 est utilisé pour la stratégie ci-dessous

Vérifiez également ci-dessous 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
la source
3
Désolé, mais c'est une manière extrêmement verbeuse de mettre en place quelque chose, qui peut s'exprimer facilement avec deux paramètres pour un Hibernate entier et donc pour toutes les entités.
G.Demecki
vrai mais quand j'essaye avec d'autres moyens, aucun d'eux n'a fonctionné si vous le faites fonctionner peut m'envoyer comment vous avez configuré
fatih tekin
J'ai mis à jour ma réponse - maintenant elle comprend également un exemple fonctionnel. Bien que mon commentaire ci-dessus soit partiellement faux: malheureusement, vous ne pouvez pas définir ni allocationSizeni initialValueglobalement pour toutes les entités (à moins d'utiliser un seul générateur, mais à mon humble avis, ce n'est pas très lisible).
G.Demecki
1
Merci pour l'explication, mais ce que vous avez écrit ci-dessus, j'ai essayé et cela n'a pas fonctionné avec hibernate 5.0.7.Version finale alors j'ai creusé dans le code source pour pouvoir atteindre cet objectif et c'est l'implémentation que j'ai pu trouver dans le code source d'hibernation. La configuration peut sembler mauvaise, mais c'est malheureusement l'API hibernate et
j'utilise l'implémentation EntityManager standard d'Hibernate
1

J'ai également rencontré ce problème dans Hibernate 5:

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

Vous avez un avertissement comme celui-ci ci-dessous:

Utilisation trouvée du générateur d'ID basé sur une séquence [org.hibernate.id.SequenceHiLoGenerator] obsolète; utilisez plutôt org.hibernate.id.enhanced.SequenceStyleGenerator. Consultez le Guide de mappage du modèle de domaine Hibernate pour plus de détails.

Ensuite, j'ai changé mon code en 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;

Cela a résolu mes deux problèmes:

  1. L'avertissement obsolète est corrigé
  2. Maintenant, l'identifiant est généré selon la séquence oracle.
Mohamed Afzal
la source
0

Je vérifierais le DDL pour la séquence dans le schéma. L'implémentation JPA est responsable uniquement de la création de la séquence avec la taille d'allocation correcte. Par conséquent, si la taille d'allocation est de 50, votre séquence doit avoir l'incrément de 50 dans sa DDL.

Ce cas peut généralement se produire avec la création d'une séquence avec la taille d'allocation 1 puis configurée ultérieurement à la taille d'allocation 50 (ou par défaut) mais la séquence DDL n'est pas mise à jour.

Hasan Ceylan
la source
Vous ne comprenez pas mon point. ALTER SEQUENCE ... INCREMENTY BY 50;ne résoudra rien, car le problème reste le même. La valeur de séquence ne reflète toujours pas les ID d'entités réels.
G.Demecki
Veuillez partager un cas de test afin que nous puissions mieux comprendre le problème ici.
Hasan Ceylan
1
Cas de test? Pourquoi? La question posée par moi n'était pas si compliquée et a déjà reçu une réponse. Il semble que vous ne savez pas comment fonctionne le générateur HiLo. Quoi qu'il en soit: merci d'avoir sacrifié votre temps et vos efforts.
G.Demecki
1
Gregory, En fait, je sais de quoi je parle, j'ai écrit Batoo JPA qui est l'implémentation% 100 JPA qui est actuellement dans son incubation et bat Hibernate en termes de vitesse - 15 fois plus rapide. D'un autre côté, j'aurais peut-être mal compris votre question et je ne pensais pas que l'utilisation d'Hibernate avec des séquences devrait créer un problème car j'utilise Hibernate depuis 2003 dans de nombreux projets sur de nombreuses bases de données. L'important est que vous ayez une solution à la question, désolé j'ai raté la réponse marquée comme correcte ...
Hasan Ceylan
Désolé, je ne voulais pas vous offenser. Merci encore pour votre aide, la question est répondue.
G.Demecki