Hibernate, @SequenceGenerator y deploymentSize

117

Todos conocemos el comportamiento predeterminado de Hibernate al usarlo @SequenceGenerator: aumenta la secuencia de la base de datos real en uno , multiplica este valor por 50 ( allocationSizevalor predeterminado ) y luego usa este valor como ID de entidad.

Este es un comportamiento incorrecto y entra en conflicto con la especificación que dice:

LocationSize - (Opcional) La cantidad a incrementar cuando se asignan números de secuencia de la secuencia.

Para ser claros: no me preocupo por las brechas entre los ID generados.

Me preocupan las identificaciones que no son consistentes con la secuencia de la base de datos subyacente. Por ejemplo: cualquier otra aplicación (que, por ejemplo, use JDBC simple) puede querer insertar nuevas filas bajo los ID obtenidos de la secuencia, ¡pero Hibernate ya puede usar todos esos valores! Locura.

¿Alguien conoce alguna solución a este problema (sin configurar allocationSize=1y, por lo tanto, degradar el rendimiento)?

EDITAR:
Para aclarar las cosas. Si el último registro insertado tenía ID = 1, entonces HB usa valores 51, 52, 53...para sus nuevas entidades PERO al mismo tiempo: el valor de la secuencia en la base de datos se establecerá en 2. Lo que puede conducir fácilmente a errores cuando otras aplicaciones utilizan esa secuencia.

Por otro lado: la especificación dice (según tengo entendido) que la secuencia de la base de datos debería haberse configurado 51y, mientras tanto, HB debería usar valores del rango 2, 3 ... 50


ACTUALIZACIÓN:
Como Steve Ebersole mencionó a continuación: el comportamiento descrito por mí (y también el más intuitivo para muchos) se puede habilitar configurando hibernate.id.new_generator_mappings=true.

Gracias a todos.

ACTUALIZACIÓN 2:
Para futuros lectores, a continuación puede encontrar un ejemplo práctico.

@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
fuente
2
"sin establecer la asignación de tamaño = 1 y, por lo tanto, degradar el rendimiento" ¿por qué degrada el rendimiento si lo establece en 1?
sheidaei
3
@sheidaei puede comentar a continuación :-) Esto se debe a que todos savenecesitan consultar la base de datos para el siguiente valor de la secuencia.
G. Demecki
Gracias estaba enfrentando el mismo problema. Al principio, estaba agregando allocationSize = 1 en cada @SequenceGenerator. Usar hibernate.id.new_generator_mappings = true evita eso. Aunque JPA todavía consulta la base de datos para obtener la identificación de cada inserción ...
TheBakker
1
Con SequenceGeneratorHibernate consultará la base de datos solo cuando se allocationsizeagote la cantidad de ID especificados por . Si lo configura, allocationSize = 1entonces es la razón por la que Hibernate consulta la base de datos para cada inserción. Cambie este valor y listo.
G. Demecki
1
¡Gracias! el hibernate.id.new_generator_mappingsentorno es realmente importante. Espero que sea la configuración predeterminada que no tenga que dedicar tanto tiempo a investigar por qué el número de identificación se vuelve loco.
LeOn - Han Li

Respuestas:

43

Para ser absolutamente claro ... lo que describe no entra en conflicto con la especificación de ninguna manera. La especificación habla de los valores que Hibernate asigna a sus entidades, no de los valores realmente almacenados en la secuencia de la base de datos.

Sin embargo, existe la opción de obtener el comportamiento que busca. Primero vea mi respuesta en ¿Hay alguna manera de elegir dinámicamente una estrategia @GeneratedValue usando anotaciones JPA e Hibernate? Eso te dará lo básico. Siempre que esté configurado para usar ese SequenceStyleGenerator, Hibernate interpretará allocationSizeusando el "optimizador agrupado" en SequenceStyleGenerator. El "optimizador agrupado" se utiliza con bases de datos que permiten una opción de "incremento" en la creación de secuencias (no todas las bases de datos que admiten secuencias admiten un incremento). De todos modos, lea sobre las diversas estrategias de optimización que existen.

Steve Ebersole
fuente
¡Gracias Steve! La mejor respuesta. También su otra publicación fue útil.
G. Demecki
4
También noté que eres coautor de org.hibernate.id.enhanced.SequenceStyleGenerator. Me sorprendiste.
G. Demecki
22
¿Te sorprendió cómo? Soy el desarrollador líder de Hibernate. He escrito / coescrito muchas clases de Hibernate;)
Steve Ebersole
Para que conste. Se debe evitar el incremento de secuencia de DB para evitar grandes espacios. La secuencia de la base de datos se multiplica con el tamaño de la asignación cuando se agota la caché de ID. Más detalles stackoverflow.com/questions/5346147/…
Olcay Tarazan
1
Una forma de cambiar el "optimizador" usado globalmente es agregar algo como esto a sus opciones de hibernación: serviceBuilder.applySetting ("hibernate.id.optimizer.pooled.preferred", LegacyHiLoAlgorithmOptimizer.class.getName ()); En lugar de LegacyHiLoAlgorithOptimizer, puede elegir cualquier clase de optimizador y se convertirá en la predeterminada. Esto debería facilitar el mantenimiento del comportamiento que desea como predeterminado sin cambiar todas las anotaciones. Además, tenga cuidado con los optimizadores "agrupados" e "hilo": estos dan resultados extraños cuando el valor de su secuencia comienza en 0, lo que provoca ID negativos.
fjalvingh
17

allocationSize=1Es una micro optimización antes de obtener la consulta. Hibernate intenta asignar un valor en el rango de tamaño de asignación y, por lo tanto, trata de evitar consultar la base de datos para la secuencia. Pero esta consulta se ejecutará cada vez que la establezca en 1. Esto apenas hace ninguna diferencia, ya que si alguna otra aplicación accede a su base de datos, se generarán problemas si, mientras tanto, otra aplicación utiliza la misma identificación.

La próxima generación de Sequence Id se basa en deploymentSize.

Por defualt se mantiene como lo 50que es demasiado. También será útil si va a tener cerca de 50registros en una sesión que no se conservan y que se conservarán utilizando esta sesión y transacción en particular.

Por lo tanto, siempre debe usar allocationSize=1mientras usa SequenceGenerator. Como ocurre con la mayoría de las bases de datos subyacentes, la secuencia siempre se incrementa en 1.

Amit Deshpande
fuente
12
¿Nada que ver con el rendimiento? ¿Estás realmente seguro? Me han enseñado que con allocationSize=1Hibernate en cada saveoperación es necesario realizar el viaje a la base de datos para obtener un nuevo valor de identificación.
G. Demecki
2
Es una micro optimización antes de obtener la consulta. Hibernate intenta asignar un valor en el rango de allocationSizey, por lo tanto, trata de evitar consultar la base de datos por secuencia. Pero esta consulta se ejecutará cada vez que la establezca en 1. Esto apenas hace ninguna diferencia, ya que si alguna otra aplicación accede a su base de datos, se generarán problemas si otra aplicación utiliza la misma identificación mientras tanto
Amit Deshpande
Y sí, es completamente específico de la aplicación si un tamaño de asignación de 1 tiene algún impacto real en el rendimiento. En un micro benchmark, por supuesto, siempre se mostrará como un gran impacto; ese es el problema con la mayoría de los puntos de referencia (micro o de otro tipo), simplemente no son realistas. E incluso si son lo suficientemente complejos como para ser algo realistas, aún debe observar qué tan cerca está el punto de referencia de su aplicación real para comprender qué tan aplicables son los resultados del punto de referencia a los resultados que vería en su aplicación. En pocas palabras ... pruébelo usted mismo
Steve Ebersole
2
OKAY. Todo es específico de la aplicación, ¿no? En caso de que su aplicación sea de solo lectura, el impacto de usar el tamaño de asignación 1000 o 1 es absolutamente 0. Por otro lado, cosas como estas son las mejores prácticas. Si no respeta las mejores prácticas que recopilan, el impacto combinado será que su aplicación se vuelva lenta. Otro ejemplo sería iniciar una transacción cuando absolutamente no la necesita.
Hasan Ceylan
1

Steve Ebersole y otros miembros,
¿podrían explicar el motivo de una identificación con una brecha más grande (por defecto 50)? Estoy usando Hibernate 4.2.15 y encontré el siguiente código en 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++ );

Siempre que golpea el interior de la declaración if, su valor aumenta mucho. Entonces, mi identificación durante la prueba con el reinicio frecuente del servidor genera los siguientes identificadores de secuencia:
1, 2, 3, 4, 19, 250, 251, 252, 400, 550, 750, 751, 752, 850, 1100, 1150.

Sé que ya dijo que no entraba en conflicto con la especificación, pero creo que esta será una situación muy inesperada para la mayoría de los desarrolladores.

La aportación de cualquiera será de mucha ayuda.

Jihwan

ACTUALIZACIÓN: ne1410s: Gracias por la edición.
cfrick: Está bien. Lo haré. Fue mi primera publicación aquí y no estaba seguro de cómo usarla.

Ahora, entendí mejor por qué maxLo se usó para dos propósitos: dado que la hibernación llama a la secuencia de la base de datos una vez, sigue aumentando la identificación en el nivel de Java y la guarda en la base de datos, el valor de la identificación del nivel de Java debe considerar cuánto se cambió sin llamar la secuencia de base de datos cuando llama a la secuencia la próxima vez.

Por ejemplo, la identificación de la secuencia fue 1 en un punto y la hibernación ingresó 5, 6, 7, 8, 9 (con tamaño de asignación = 5). La próxima vez, cuando obtengamos el siguiente número de secuencia, DB devuelve 2, pero hibernación necesita usar 10, 11, 12 ... Entonces, es por eso que "hi = lastSourceValue.copy (). MultiplyBy (maxLo + 1)" es utilizado para obtener un siguiente id 10 de los 2 devueltos de la secuencia DB. Parece que lo único molesto fue durante el reinicio frecuente del servidor y este fue mi problema con la brecha más grande.

Entonces, cuando usamos el ID DE SECUENCIA, el ID insertado en la tabla no coincidirá con el número de SECUENCIA en DB.

Jihwan
fuente
1

Después de profundizar en el código fuente de hibernación y la configuración inferior va a la base de datos de Oracle para obtener el siguiente valor después de 50 inserciones. Así que haga que su INST_PK_SEQ incremente 50 cada vez que se llame.

Hibernate 5 se usa para la siguiente estrategia

Consulte también a continuación 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
fuente
3
Lo siento, pero esta es una forma extremadamente detallada de configurar algo, que se puede expresar fácilmente con dos parámetros para un Hibernate completo y, por lo tanto, para todas las entidades.
G. Demecki
es cierto, pero cuando intento con otras formas, ninguna de ellas funcionó, si lo tienes funcionando, puedes enviarme cómo lo has configurado
fatih tekin
Actualicé mi respuesta, ahora también incluye un ejemplo funcional. Aunque mi comentario anterior es parcialmente incorrecto: desafortunadamente, no puede configurar allocationSizeni initialValueglobalmente para todas las entidades (a menos que use solo un generador, pero en mi humilde opinión no es muy legible).
G. Demecki
1
Gracias por la explicación, pero lo que escribiste anteriormente, lo he intentado y no funcionó con hibernate 5.0.7.La versión final, luego he profundizado en el código fuente para poder lograr este objetivo y esa es la implementación que pude encontrar. en código fuente de hibernación. La configuración puede verse mal, pero desafortunadamente eso es api de hibernación y estoy usando la implementación estándar de EntityManager de hibernate
fatih tekin
1

Yo también enfrenté este problema en Hibernate 5:

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

Recibí una advertencia como esta a continuación:

Se encontró el uso del generador de identificaciones basado en secuencias [org.hibernate.id.SequenceHiLoGenerator] obsoleto; use org.hibernate.id.enhanced.SequenceStyleGenerator en su lugar. Consulte la Guía de mapeo del modelo de dominio de Hibernate para obtener más detalles.

Luego cambié mi código a 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;

Esto resolvió mis dos problemas:

  1. Se corrigió la advertencia obsoleta
  2. Ahora, la identificación se genera según la secuencia de Oracle.
Mohamed Afzal
fuente
0

Verificaría el DDL para la secuencia en el esquema. La implementación de JPA es responsable solo de la creación de la secuencia con el tamaño de asignación correcto. Por lo tanto, si el tamaño de la asignación es 50, su secuencia debe tener el incremento de 50 en su DDL.

Este caso puede ocurrir típicamente con la creación de una secuencia con un tamaño de asignación 1 y luego configurada con un tamaño de asignación 50 (o predeterminado) pero la secuencia DDL no se actualiza.

Hasan Ceylan
fuente
Estás malinterpretando mi punto. ALTER SEQUENCE ... INCREMENTY BY 50;no resolverá nada, porque el problema sigue siendo el mismo. El valor de la secuencia aún no refleja los ID de entidades reales.
G. Demecki
Comparta un caso de prueba para que podamos comprender mejor el problema aquí.
Hasan Ceylan
1
¿Caso de prueba? ¿Por qué? La pregunta publicada por mí no fue tan complicada y ya ha sido respondida. Parece que no sabes cómo funciona el generador HiLo. De todos modos: gracias por sacrificar su tiempo y esfuerzo.
G. Demecki
1
Gregory, en realidad sé de lo que estoy hablando, he escrito Batoo JPA que es la implementación de% 100 JPA que se encuentra actualmente en su incubación y supera a Hibernate en términos de velocidad, 15 veces más rápido. Por otro lado, podría haber entendido mal su pregunta y no pensé que usar Hibernate con secuencias debería crear ningún problema, ya que he usado Hibernate desde 2003 en muchos proyectos en muchas bases de datos. Lo importante es que tiene una solución a la pregunta, lo siento, me perdí la respuesta marcada como correcta ...
Hasan Ceylan
Lo siento, no quise ofenderte. Gracias nuevamente por su ayuda, la pregunta está respondida.
G. Demecki