危険: あなたの質問は、設計ミスを犯している可能性があることを示唆しています。ユーザーに提示される「ビジネス」値(この場合は請求書番号)にデータベースシーケンスを使用しようとしています。
値が等しいかどうかをテストする以上のことが必要な場合は、シーケンスを使用しないでください。順序はありません。別の値からの「距離」はありません。等しいか、等しくないかです。
ロールバック: シーケンスへの変更はトランザクションROLLBACK
でロールバックされないため、シーケンスは通常、このような使用には適していません。 。関数のフッターを参照してください-シーケンスとCREATE SEQUENCE
。
ロールバックは予想され、正常です。次の理由で発生します:
- 2つのトランザクション間での更新順序の競合またはその他のロックによって引き起こされるデッドロック。
- Hibernateでの楽観的なロックロールバック;
- 一時的なクライアントエラー;
- DBAによるサーバーのメンテナンス;
-
SERIALIZABLE
でのシリアル化の競合 またはスナップショットアイソレーショントランザクション
...など。
アプリケーションには、これらのロールバックが発生する請求書番号に「穴」があります。さらに、順序の保証はないため、シーケンス番号が遅いトランザクションが早くコミットされる可能性があります(場合によっては 早い)後の番号のものより。
チャンキング:
また、Hibernateを含む一部のアプリケーションでは、シーケンスから一度に複数の値を取得して、それらを内部でトランザクションに渡すことも正常です。シーケンスで生成された値が意味のある順序を持っていることや、等しいことを除いて何らかの方法で比較可能であることを期待することは想定されていないため、これは許容されます。請求書の番号付けについては、注文も必要なので、まったくなりません。 Hibernateが5900〜5999の値を取得し、5999からカウントダウンしてそれらを配布し始めたら幸せです または交互に上下に移動するため、請求書番号は次のようになります: n、n + 1、n + 49、n + 2、n + 48、... n + 50、n + 99、n + 51、 n + 98、[n + 52はロールバックに負けました]、n + 97、... 。はい、High-then-LowアロケータはHibernateに存在します。
個別の@SequenceGenerator
を定義しない限り、それは役に立ちません。 ■マッピングでは、Hibernateはすべての単一のシーケンスを共有するのが好きです。 IDも生成されます。醜い。
正しい使用法:
シーケンスは、のみの場合にのみ適切です。 番号付けは一意である必要があります。単調で序数である必要がある場合は、UPDATE ... RETURNING
を介してカウンターフィールドを持つ通常のテーブルを使用することを検討する必要があります。 またはSELECT ... FOR UPDATE
(Hibernateの「悲観的ロック」)またはHibernateの楽観的ロックを介して。そうすれば、穴や順序が狂ったエントリのないギャップのない増分を保証できます。
代わりに行うこと:
カウンター専用のテーブルを作成します。その中に単一の行があり、それを読んでいるときにそれを更新します。それはそれをロックし、あなたがコミットするまで他のトランザクションがIDを取得するのを防ぎます。
すべてのトランザクションが連続して動作するように強制されるため、請求書IDを生成するトランザクションを短くし、必要以上の作業を行わないようにしてください。
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;
または、次のことができます。
- invoice_numberのエンティティを定義し、
@Version
を追加します 列を作成し、楽観的なロックで競合を処理します。 - invoice_numberのエンティティを定義し、Hibernateで明示的な悲観的ロックを使用して、更新のために...を選択してから更新を実行します。
これらのオプションはすべて、@ Versionを使用して競合をロールバックするか、ロック所有者がコミットするまで競合をブロック(ロック)することにより、トランザクションをシリアル化します。いずれにせよ、ギャップのないシーケンスは本当に アプリケーションのその領域の速度を落とすため、必要な場合にのみギャップのないシーケンスを使用してください。
@GenerationType.TABLE
:@GenerationType.TABLE
を使いたくなります @TableGenerator(initialValue=1, ...)
を使用 。残念ながら、GenerationType.TABLEでは@TableGeneratorを介して割り当てサイズを指定できますが、順序付けやロールバックの動作については保証されません。 JPA 2.0仕様、セクション11.1.46、および11.1.17を参照してください。特に"この仕様は、これらの戦略の正確な動作を定義していません。 および脚注102"ポータブルアプリケーションは、[@Id
以外の永続的なフィールドまたはプロパティでGeneratedValueアノテーションを使用しないでください] 主キー]" 。したがって、@GenerationType.TABLE
を使用するのは安全ではありません ギャップレスである必要がある番号付け、またはJPAプロバイダーが標準よりも多くの保証を行わない限り、主キープロパティにない番号付け。
シーケンスで行き詰まっている場合 :
ポスターは、すでにシーケンスを使用しているDBを使用している既存のアプリがあるため、それで立ち往生していると述べています。
JPA標準は、@ Idを除いて生成された列を使用できることを保証しません。(a)それを無視して、プロバイダーが許可する限り先に進むか、(b)デフォルト値で挿入を実行して再実行することができます。 -データベースから読み取ります。後者の方が安全です:
@Column(name = "inv_seq", insertable=false, updatable=false)
public Integer getInvoiceSeq() {
return invoiceSeq;
}
insertable=false
のため プロバイダーは列の値を指定しようとはしません。これで、適切なDEFAULT
を設定できます。 nextval('some_sequence')
のようなデータベース内 そしてそれは光栄に思うでしょう。 EntityManager.refresh()
を使用して、データベースからエンティティを再読み込みする必要がある場合があります 永続化した後-永続化プロバイダーがあなたに代わってそれを行うかどうかはわかりません。仕様を確認したり、デモプログラムを作成したりしていません。
唯一の欠点は、列を@NotNullまたはnullable=false
にできないように見えることです。 、プロバイダーはデータベースに列のデフォルトがあることを理解していないためです。それでもNOT NULL
にすることができます データベース内。
運が良ければ、他のアプリもINSERT
からシーケンス列を省略するという標準的なアプローチを使用します。 の列リストまたはキーワードDEFAULT
を明示的に指定する nextval
を呼び出す代わりに、値として 。 log_statement = 'all'
を有効にすることで、それを見つけるのは難しくありません。 postgresql.conf
内 ログを検索します。その場合、DEFAULT
を置き換える必要があると判断した場合は、実際にすべてをギャップレスに切り替えることができます。 BEFORE INSERT ... FOR EACH ROW
NEW.invoice_number
を設定するトリガー関数 カウンターテーブルから。