Bahaya: Pertanyaan Anda menyiratkan bahwa Anda mungkin membuat kesalahan desain - Anda mencoba menggunakan urutan basis data untuk nilai "bisnis" yang disajikan kepada pengguna, dalam hal ini nomor faktur.
Jangan gunakan urutan jika Anda perlu melakukan apa pun selain menguji nilai kesetaraan. Ini tidak memiliki urutan. Itu tidak memiliki "jarak" dari nilai lain. Itu hanya sama, atau tidak sama.
Kembalikan: Urutan umumnya tidak sesuai untuk penggunaan seperti itu karena perubahan urutan tidak dibatalkan dengan transaksi ROLLBACK
. Lihat footer pada function-sequence dan CREATE SEQUENCE
.
Rollback diharapkan dan normal. Mereka terjadi karena:
- kebuntuan yang disebabkan oleh urutan pembaruan yang bertentangan atau kunci lain antara dua transaksi;
- pengembalian penguncian yang optimis di Hibernate;
- kesalahan klien sementara;
- pemeliharaan server oleh DBA;
- konflik serialisasi di
SERIALIZABLE
atau snapshot isolasi transaksi
... dan banyak lagi.
Aplikasi Anda akan memiliki "lubang" di penomoran faktur tempat pengembalian tersebut terjadi. Selain itu, tidak ada jaminan pemesanan, jadi sangat mungkin bahwa transaksi dengan nomor urut yang lebih baru akan dilakukan lebih awal (terkadang banyak sebelumnya) dari satu dengan nomor yang lebih baru.
Memotong:
Itu juga normal untuk beberapa aplikasi, termasuk Hibernate, untuk mengambil lebih dari satu nilai dari urutan pada satu waktu dan membagikannya ke transaksi internal. Itu diizinkan karena Anda tidak seharusnya mengharapkan nilai yang dihasilkan urutan memiliki urutan yang berarti atau sebanding dengan cara apa pun kecuali untuk kesetaraan. Untuk penomoran invoice, mau pesan juga, jadi tidak akan sama sekali senang jika Hibernate mengambil nilai 5900-5999 dan mulai membagikannya dari 5999 menghitung turun atau bergantian naik-turun, jadi nomor faktur Anda menjadi:n, n+1, n+49, n+2, n+48, ... n+50, n+99, n+51, n+98, [n+52 kalah dari rollback], n+97, ... . Ya, pengalokasi tinggi-lalu-rendah ada di Hibernate.
Itu tidak membantu kecuali Anda mendefinisikan @SequenceGenerator
individu individual s dalam pemetaan Anda, Hibernate suka membagikan satu urutan untuk setiap ID yang dihasilkan juga. Jelek.
Penggunaan yang benar:
Urutan hanya sesuai jika Anda hanya membutuhkan penomoran untuk menjadi unik. Jika Anda juga membutuhkannya untuk monoton dan ordinal, Anda harus mempertimbangkan untuk menggunakan tabel biasa dengan bidang penghitung melalui UPDATE ... RETURNING
atau SELECT ... FOR UPDATE
("penguncian pesimis" di Hibernate) atau melalui penguncian optimis Hibernate. Dengan begitu, Anda dapat menjamin peningkatan tanpa celah tanpa lubang atau entri yang tidak sesuai pesanan.
Apa yang harus dilakukan sebagai gantinya:
Buat tabel hanya untuk penghitung. Miliki satu baris di dalamnya, dan perbarui saat Anda membacanya. Itu akan menguncinya, mencegah transaksi lain mendapatkan ID sampai Anda melakukan.
Karena ini memaksa semua transaksi Anda untuk beroperasi secara serial, usahakan agar transaksi yang menghasilkan ID faktur singkat dan hindari melakukan lebih banyak pekerjaan di dalamnya daripada yang Anda perlukan.
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;
Sebagai alternatif, Anda dapat:
- Tentukan entitas untuk invoice_number, tambahkan
@Version
kolom, dan biarkan penguncian optimis menangani konflik; - Tentukan entitas untuk invoice_number dan gunakan penguncian pesimistis eksplisit di Hibernate untuk memilih ... untuk pembaruan kemudian pembaruan.
Semua opsi ini akan membuat serial transaksi Anda - baik dengan mengembalikan konflik menggunakan @Version, atau memblokirnya (mengunci) hingga pemegang kunci melakukan. Bagaimanapun, urutan tanpa celah akan benar-benar memperlambat area aplikasi Anda, jadi hanya gunakan urutan tanpa celah jika perlu.
@GenerationType.TABLE
:Sangat menggoda untuk menggunakan @GenerationType.TABLE
dengan @TableGenerator(initialValue=1, ...)
. Sayangnya, sementara GenerationType.TABLE memungkinkan Anda menentukan ukuran alokasi melalui @TableGenerator, itu tidak memberikan jaminan apa pun tentang perilaku pemesanan atau rollback. Lihat spesifikasi JPA 2.0, bagian 11.1.46, dan 11.1.17. Khususnya "Spesifikasi ini tidak mendefinisikan perilaku yang tepat dari strategi ini. dan catatan kaki 102 "Aplikasi portabel tidak boleh menggunakan anotasi GeneratedValue pada bidang atau properti persisten lainnya [selain @Id
kunci utama]" . Jadi tidak aman menggunakan @GenerationType.TABLE
untuk penomoran yang Anda perlukan tanpa celah atau penomoran yang tidak ada di properti kunci utama kecuali penyedia JPA Anda membuat lebih banyak jaminan daripada standar.
Jika Anda terjebak dengan urutan :
Poster tersebut mencatat bahwa mereka memiliki aplikasi yang sudah ada menggunakan DB yang sudah menggunakan urutan, jadi mereka terjebak dengannya.
Standar JPA tidak menjamin bahwa Anda dapat menggunakan kolom yang dihasilkan kecuali pada @Id, Anda dapat (a) mengabaikannya dan melanjutkan selama penyedia Anda mengizinkan Anda, atau (b) melakukan penyisipan dengan nilai default dan ulang -baca dari database. Yang terakhir lebih aman:
@Column(name = "inv_seq", insertable=false, updatable=false)
public Integer getInvoiceSeq() {
return invoiceSeq;
}
Karena insertable=false
penyedia tidak akan mencoba menentukan nilai untuk kolom. Anda sekarang dapat mengatur DEFAULT
yang sesuai dalam database, seperti nextval('some_sequence')
dan itu akan dihormati. Anda mungkin harus membaca ulang entitas dari database dengan EntityManager.refresh()
setelah bertahan - saya tidak yakin apakah penyedia ketekunan akan melakukannya untuk Anda dan saya belum memeriksa spesifikasi atau menulis program demo.
Satu-satunya downside adalah tampaknya kolom tidak dapat dibuat @ NotNull atau nullable=false
, karena penyedia tidak memahami bahwa database memiliki default untuk kolom. Itu masih bisa NOT NULL
dalam basis data.
Jika Anda beruntung, aplikasi Anda yang lain juga akan menggunakan pendekatan standar dengan menghilangkan kolom urutan dari INSERT
daftar kolom atau secara eksplisit menentukan kata kunci DEFAULT
sebagai nilai, alih-alih memanggil nextval
. Tidak akan sulit untuk menemukannya dengan mengaktifkan log_statement = 'all'
di postgresql.conf
dan mencari log. Jika ya, maka Anda benar-benar dapat mengubah semuanya menjadi tanpa celah jika Anda memutuskan perlu dengan mengganti DEFAULT
Anda dengan BEFORE INSERT ... FOR EACH ROW
fungsi pemicu yang menyetel NEW.invoice_number
dari meja konter.