sql >> Base de Datos >  >> RDS >> PostgreSQL

Anotación de Hibernate para el tipo de serie de PostgreSQL

Peligro: Su pregunta implica que puede estar cometiendo un error de diseño:está tratando de usar una secuencia de base de datos para un valor "comercial" que se presenta a los usuarios, en este caso, números de factura.

No use una secuencia si necesita algo más que probar el valor para la igualdad. No tiene orden. No tiene "distancia" de otro valor. Es simplemente igual o no igual.

Restaurar: Las secuencias generalmente no son apropiadas para tales usos porque los cambios en las secuencias no se revierten con la transacción ROLLBACK . Ver los pies de página en functions-sequence y CREATE SEQUENCE .

Los retrocesos son esperados y normales. Ocurren debido a:

  • puntos muertos causados ​​por una orden de actualización en conflicto u otros bloqueos entre dos transacciones;
  • reversiones de bloqueo optimistas en Hibernate;
  • errores transitorios del cliente;
  • mantenimiento del servidor por parte del DBA;
  • conflictos de serialización en SERIALIZABLE o instantáneas de transacciones de aislamiento

... y más.

Su aplicación tendrá "agujeros" en la numeración de la factura donde ocurren esos retrocesos. Además, no hay garantía de pedido, por lo que es muy posible que una transacción con un número de secuencia posterior se confirme antes (a veces mucho antes) que uno con un número posterior.

fragmentación:

También es normal que algunas aplicaciones, incluida Hibernate, obtengan más de un valor de una secuencia a la vez y los entreguen a las transacciones internamente. Eso está permitido porque se supone que no debe esperar que los valores generados por secuencias tengan un orden significativo o que sean comparables de ninguna manera, excepto por la igualdad. Para la numeración de facturas, también desea ordenar, por lo que no estará en absoluto feliz si Hibernate toma los valores 5900-5999 y comienza a repartirlos desde 5999 contando hacia atrás o alternativamente hacia arriba y hacia abajo, de modo que los números de su factura sean:n, n+1, n+49, n+2, n+48, ... n+50, n+99, n+51, n+98, [n+52 perdidos por reversión], n+97, ... . Sí, el asignador de alto luego bajo existe en Hibernate.

Eso no ayuda a menos que defina @SequenceGenerator individual s en sus asignaciones, a Hibernate le gusta compartir una sola secuencia para cada identificación generada, también. Feo.

Uso correcto:

Una secuencia solo es apropiada si solo requieren que la numeración sea única. Si también necesita que sea monótono y ordinal, debería pensar en usar una tabla ordinaria con un campo de contador a través de UPDATE ... RETURNING o SELECT ... FOR UPDATE ("bloqueo pesimista" en Hibernate) o a través del bloqueo optimista de Hibernate. De esa manera, puede garantizar incrementos sin espacios sin agujeros ni entradas fuera de orden.

Qué hacer en su lugar:

Crea una mesa solo para un mostrador. Tenga una sola fila y actualícela a medida que la lea. Eso lo bloqueará, evitando que otras transacciones obtengan una identificación hasta que la suya se confirme.

Debido a que obliga a que todas sus transacciones operen en serie, intente que las transacciones que generan ID de factura sean breves y evite hacer más trabajo del necesario.

CREATE TABLE invoice_number (
    last_invoice_number integer primary key
);

-- PostgreSQL specific hack you can use to make
-- really sure only one row ever exists
CREATE UNIQUE INDEX there_can_be_only_one 
ON invoice_number( (1) );

-- Start the sequence so the first returned value is 1
INSERT INTO invoice_number(last_invoice_number) VALUES (0);

-- To get a number; PostgreSQL specific but cleaner.
-- Use as a native query from Hibernate.
UPDATE invoice_number
SET last_invoice_number = last_invoice_number + 1
RETURNING last_invoice_number;

Como alternativa, puede:

  • Defina una entidad para número_de_factura, agregue un @Version columna, y dejar que el bloqueo optimista se ocupe de los conflictos;
  • Defina una entidad para número_de_factura y use bloqueo pesimista explícito en Hibernate para hacer una selección... para actualizar y luego una actualización.

Todas estas opciones serializarán sus transacciones, ya sea revirtiendo los conflictos con @Version o bloqueándolos (bloqueándolos) hasta que el titular del bloqueo se comprometa. De cualquier manera, las secuencias sin pausas realmente Reduzca la velocidad de esa área de su aplicación, así que solo use secuencias sin pausas cuando sea necesario.

@GenerationType.TABLE :Es tentador usar @GenerationType.TABLE con un @TableGenerator(initialValue=1, ...) . Desafortunadamente, aunque GenerationType.TABLE le permite especificar un tamaño de asignación a través de @TableGenerator, no ofrece ninguna garantía sobre el comportamiento de pedidos o reversión. Consulte la especificación JPA 2.0, sección 11.1.46 y 11.1.17. En particular "Esta especificación no define el comportamiento exacto de estas estrategias. y nota al pie 102 "Las aplicaciones portátiles no deben usar la anotación GeneratedValue en otros campos o propiedades persistentes [que no sean @Id claves primarias]" . Por lo tanto, no es seguro usar @GenerationType.TABLE para la numeración que necesita que no tenga espacios o la numeración que no está en una propiedad de clave principal, a menos que su proveedor de JPA ofrezca más garantías que el estándar.

Si te quedas atascado con una secuencia :

El cartel señala que tienen aplicaciones existentes que usan la base de datos que ya usan una secuencia, por lo que están atascados con ella.

El estándar JPA no garantiza que pueda usar columnas generadas excepto en @Id, puede (a) ignorar eso y continuar siempre que su proveedor lo permita, o (b) hacer la inserción con un valor predeterminado y volver -leer de la base de datos. Este último es más seguro:

    @Column(name = "inv_seq", insertable=false, updatable=false)
    public Integer getInvoiceSeq() {
        return invoiceSeq;
    }

Debido a insertable=false el proveedor no intentará especificar un valor para la columna. Ahora puede establecer un DEFAULT adecuado en la base de datos, como nextval('some_sequence') y será honrado. Es posible que deba volver a leer la entidad de la base de datos con EntityManager.refresh() después de persistirlo; no estoy seguro de si el proveedor de persistencia lo hará por usted y no he verificado las especificaciones ni he escrito un programa de demostración.

El único inconveniente es que parece que la columna no se puede hacer @ NotNull o nullable=false , ya que el proveedor no comprende que la base de datos tiene un valor predeterminado para la columna. Todavía puede ser NOT NULL en la base de datos.

Si tiene suerte, sus otras aplicaciones también utilizarán el enfoque estándar de omitir la columna de secuencia de INSERT la lista de columnas de o especificando explícitamente la palabra clave DEFAULT como el valor, en lugar de llamar a nextval . No será difícil averiguarlo habilitando log_statement = 'all' en postgresql.conf y buscando en los registros. Si lo hacen, entonces puede cambiar todo a sin espacios si decide que lo necesita reemplazando su DEFAULT con BEFORE INSERT ... FOR EACH ROW función de activación que establece NEW.invoice_number de la mesa del mostrador.