PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Hibernate-Anmerkung für den seriellen PostgreSQL-Typ

Gefahr: Ihre Frage impliziert, dass Sie möglicherweise einen Designfehler machen – Sie versuchen, eine Datenbanksequenz für einen „Geschäfts“-Wert zu verwenden, der Benutzern angezeigt wird, in diesem Fall Rechnungsnummern.

Verwenden Sie keine Sequenz, wenn Sie mehr als den Wert auf Gleichheit testen müssen. Es hat keine Ordnung. Es hat keinen "Abstand" zu einem anderen Wert. Es ist einfach gleich oder nicht gleich.

Zurücksetzen: Sequenzen sind für solche Verwendungen im Allgemeinen nicht geeignet, da Änderungen an Sequenzen nicht mit der Transaktion ROLLBACK rückgängig gemacht werden . Siehe Fußzeilen zu functions-sequence und CREATE SEQUENCE .

Rollbacks werden erwartet und sind normal. Sie treten auf wegen:

  • Deadlocks, die durch widersprüchliche Update-Reihenfolge oder andere Sperren zwischen zwei Transaktionen verursacht werden;
  • optimistische Sperr-Rollbacks in Hibernate;
  • vorübergehende Clientfehler;
  • Serverwartung durch den DBA;
  • Serialisierungskonflikte in SERIALIZABLE oder Snapshot-Isolationstransaktionen

... und mehr.

Ihre Anwendung wird „Löcher“ in der Rechnungsnummerierung haben, wo diese Rollbacks auftreten. Darüber hinaus gibt es keine Bestellgarantie, daher ist es durchaus möglich, dass eine Transaktion mit einer späteren Sequenznummer früher festgeschrieben wird (manchmal viel früher) als eine mit einer späteren Nummer.

Chunking:

Es ist auch normal, dass einige Anwendungen, einschließlich Hibernate, mehr als einen Wert gleichzeitig aus einer Sequenz abrufen und sie intern an Transaktionen weitergeben. Das ist zulässig, da Sie nicht erwarten sollten, dass sequenzgenerierte Werte eine sinnvolle Reihenfolge haben oder in irgendeiner Weise vergleichbar sind, außer auf Gleichheit. Für die Rechnungsnummerierung möchten Sie auch bestellen, damit Sie überhaupt nicht sind glücklich, wenn Hibernate die Werte 5900-5999 ergreift und ab 5999 beginnt, sie runter zu zählen oder abwechselnd auf-dann-ab, also Ihre Rechnungsnummern lauten:n, n+1, n+49, n+2, n+48, ... n+50, n+99, n+51, n+98, [n+52 durch Rollback verloren], n+97, ... . Ja, die High-Then-Low-Zuweisung existiert in Hibernate.

Es hilft nichts, es sei denn, Sie definieren einen individuellen @SequenceGenerator s in Ihren Zuordnungen teilt Hibernate gerne eine einzelne Sequenz für jeden generierte ID auch. Hässlich.

Richtige Verwendung:

Eine Reihenfolge ist nur dann sinnvoll, wenn Sie nur verlangen, dass die Nummerierung eindeutig ist. Wenn Sie es auch monoton und ordinal brauchen, sollten Sie über UPDATE ... RETURNING nachdenken, eine gewöhnliche Tabelle mit einem Zählerfeld zu verwenden oder SELECT ... FOR UPDATE ("pessimistisches Sperren" in Hibernate) oder über Hibernate optimistisches Sperren. Auf diese Weise können Sie lückenlose Inkremente ohne Löcher oder Einträge außerhalb der Reihenfolge garantieren.

Was stattdessen zu tun ist:

Erstellen Sie eine Tabelle nur für eine Theke. Haben Sie eine einzelne Zeile darin und aktualisieren Sie sie, während Sie sie lesen. Dadurch wird es gesperrt und verhindert, dass andere Transaktionen eine ID erhalten, bis Ihre festgeschrieben wird.

Da alle Ihre Transaktionen dazu gezwungen werden, seriell zu arbeiten, versuchen Sie, Transaktionen, die Rechnungs-IDs generieren, kurz zu halten, und vermeiden Sie, mehr Arbeit darin zu verrichten, als nötig.

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;

Alternativ können Sie:

  • Definieren Sie eine Entität für Rechnungsnummer, fügen Sie einen @Version hinzu -Spalte, und lassen Sie optimistische Sperren sich um Konflikte kümmern;
  • Definieren Sie eine Entität für Rechnungsnummer und verwenden Sie explizites pessimistisches Sperren in Hibernate, um eine Auswahl ... für eine Aktualisierung und dann eine Aktualisierung durchzuführen.

Alle diese Optionen werden Ihre Transaktionen serialisieren - entweder durch Zurücksetzen von Konflikten mit @Version oder durch Blockieren (Sperren), bis der Inhaber der Sperre festschreibt. In jedem Fall werden lückenlose Sequenzen wirklich Verlangsamen Sie diesen Bereich Ihrer Anwendung, verwenden Sie also lückenlose Sequenzen nur, wenn es sein muss.

@GenerationType.TABLE :Es ist verlockend, @GenerationType.TABLE zu verwenden mit einem @TableGenerator(initialValue=1, ...) . Obwohl Sie mit GenerationType.TABLE eine Zuordnungsgröße über @TableGenerator angeben können, bietet es leider keine Garantien für das Sortier- oder Rollback-Verhalten. Siehe die JPA 2.0-Spezifikation, Abschnitt 11.1.46 und 11.1.17. Insbesondere "Diese Spezifikation definiert nicht das genaue Verhalten dieser Strategien. und Fußnote 102 "Portable Anwendungen sollten die GeneratedValue-Anmerkung nicht für andere persistente Felder oder Eigenschaften verwenden [als @Id Primärschlüssel]" . Daher ist es unsicher, @GenerationType.TABLE zu verwenden für Nummerierungen, die lückenlos sein müssen, oder Nummerierungen, die sich nicht auf einer Primärschlüsseleigenschaft befinden, es sei denn, Ihr JPA-Anbieter gibt mehr Garantien als der Standard.

Wenn Sie mit einer Sequenz nicht weiterkommen :

Der Verfasser stellt fest, dass sie bereits vorhandene Apps haben, die die DB verwenden, die bereits eine Sequenz verwenden, also bleiben sie dabei.

Der JPA-Standard garantiert nicht, dass Sie generierte Spalten verwenden können, außer auf @Id, Sie können (a) das ignorieren und fortfahren, solange Ihr Provider es zulässt, oder (b) die Einfügung mit einem Standardwert durchführen und erneut -aus der Datenbank lesen. Letzteres ist sicherer:

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

Wegen insertable=false Der Anbieter versucht nicht, einen Wert für die Spalte anzugeben. Sie können nun einen passenden DEFAULT einstellen in der Datenbank, wie nextval('some_sequence') und es wird geehrt. Möglicherweise müssen Sie die Entität erneut aus der Datenbank mit EntityManager.refresh() einlesen nach dem Persistieren - ich bin mir nicht sicher, ob der Persistenzanbieter das für Sie tun wird, und ich habe die Spezifikation nicht überprüft oder ein Demoprogramm geschrieben.

Der einzige Nachteil ist, dass die Spalte anscheinend nicht mit @ NotNull oder nullable=false erstellt werden kann , da der Anbieter nicht versteht, dass die Datenbank einen Standardwert für die Spalte hat. Es kann immer noch NOT NULL sein in der Datenbank.

Wenn Sie Glück haben, verwenden Ihre anderen Apps auch den Standardansatz, entweder die Sequenzspalte aus dem INSERT wegzulassen 's Spaltenliste oder die explizite Angabe des Schlüsselworts DEFAULT als Wert, anstatt nextval aufzurufen . Es wird nicht schwer sein, das herauszufinden, indem Sie log_statement = 'all' aktivieren in postgresql.conf und Durchsuchen der Protokolle. Wenn dies der Fall ist, können Sie tatsächlich alles auf lückenlos umstellen, wenn Sie sich dazu entschließen, indem Sie Ihren DEFAULT ersetzen mit einem BEFORE INSERT ... FOR EACH ROW Auslösefunktion, die NEW.invoice_number setzt vom Thekentisch.