Nguy hiểm: Câu hỏi của bạn ngụ ý rằng bạn có thể đang mắc lỗi thiết kế - bạn đang cố gắng sử dụng chuỗi cơ sở dữ liệu cho giá trị "doanh nghiệp" được hiển thị cho người dùng, trong trường hợp này là số hóa đơn.
Không sử dụng một chuỗi nếu bạn cần bất cứ điều gì hơn là kiểm tra giá trị cho bằng nhau. Nó không có thứ tự. Nó không có "khoảng cách" với giá trị khác. Nó chỉ bằng hoặc không bằng.
Khôi phục: Các chuỗi thường không thích hợp cho những mục đích sử dụng như vậy vì các thay đổi đối với chuỗi không được khôi phục lại với giao dịch ROLLBACK
. Xem chân trang về hàm-trình tự và CREATE SEQUENCE
.
Phục hồi được mong đợi và bình thường. Chúng xảy ra do:
- bế tắc do xung đột lệnh cập nhật hoặc các khóa khác giữa hai giao dịch;
- quá trình khôi phục có khóa lạc quan ở chế độ Hibernate;
- lỗi máy khách tạm thời;
- bảo trì máy chủ bởi DBA;
- xung đột tuần tự hóa trong
SERIALIZABLE
hoặc các giao dịch cách ly ảnh chụp nhanh
... và hơn thế nữa.
Ứng dụng của bạn sẽ có "lỗ hổng" trong việc đánh số hóa đơn, nơi những lần quay lại này xảy ra. Ngoài ra, không có đảm bảo đặt hàng, vì vậy, hoàn toàn có thể xảy ra trường hợp giao dịch có số thứ tự muộn hơn sẽ cam kết sớm hơn (đôi khi nhiều sớm hơn) so với một có số muộn hơn.
Chunking:
Cũng bình thường đối với một số ứng dụng, bao gồm cả Hibernate, lấy nhiều giá trị từ một chuỗi cùng một lúc và phân phối chúng cho các giao dịch nội bộ. Điều đó được phép vì bạn không được mong đợi các giá trị do trình tự tạo ra có bất kỳ thứ tự có ý nghĩa nào hoặc có thể so sánh theo bất kỳ cách nào ngoại trừ sự bằng nhau. Đối với việc đánh số hóa đơn, bạn cũng muốn đặt hàng, vì vậy bạn sẽ không hoàn toàn rất vui nếu Hibernate lấy các giá trị 5900-5999 và bắt đầu phân phối chúng từ 5999 đếm ngược hoặc luân phiên lên-rồi-xuống, do đó, số hoá đơn của bạn là: n, n + 1, n + 49, n + 2, n + 48, ... n + 50, n + 99, n + 51, n + 98, [n + 52 bị mất khi khôi phục], n + 97, ... . Có, bộ phân bổ cao đến thấp tồn tại ở chế độ Hibernate.
Điều đó không hữu ích trừ khi bạn xác định @SequenceGenerator
riêng lẻ trong ánh xạ của bạn, Hibernate thích chia sẻ một chuỗi duy nhất cho mọi ID cũng được tạo. Xấu xí.
Sử dụng đúng:
Một trình tự chỉ thích hợp nếu bạn chỉ yêu cầu đánh số là duy nhất. Nếu bạn cũng cần nó là đơn điệu và thứ tự, bạn nên nghĩ đến việc sử dụng một bảng thông thường có trường bộ đếm thông qua UPDATE ... RETURNING
hoặc SELECT ... FOR UPDATE
("khóa bi quan" trong Hibernate) hoặc thông qua khóa lạc quan Hibernate. Bằng cách đó, bạn có thể đảm bảo gia tăng không có lỗ hổng hoặc các mục nhập không theo thứ tự.
Phải làm gì thay thế:
Tạo một bảng chỉ cho một quầy. Có một hàng trong đó và cập nhật nó khi bạn đọc nó. Điều đó sẽ khóa nó, ngăn các giao dịch khác nhận được ID cho đến khi bạn cam kết.
Bởi vì nó buộc tất cả các giao dịch của bạn phải hoạt động theo thứ tự, hãy cố gắng giữ cho các giao dịch tạo ID hóa đơn ngắn gọn và tránh thực hiện nhiều công việc hơn bạn cần.
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;
Ngoài ra, bạn có thể:
- Xác định một thực thể cho bill_number, thêm
@Version
và để khóa lạc quan giải quyết các xung đột; - Xác định một thực thể cho bill_number và sử dụng tính năng khóa bi quan rõ ràng trong Hibernate để thực hiện một lựa chọn ... để cập nhật rồi cập nhật.
Tất cả các tùy chọn này sẽ tuần tự hóa các giao dịch của bạn - bằng cách khôi phục các xung đột bằng cách sử dụng @Version hoặc chặn chúng (khóa) cho đến khi người giữ khóa cam kết. Dù bằng cách nào, các chuỗi không có khoảng trống sẽ thực sự làm chậm khu vực đó của ứng dụng của bạn, vì vậy chỉ sử dụng các chuỗi không có khoảng trống khi bạn phải làm như vậy.
@GenerationType.TABLE
:Thật hấp dẫn khi sử dụng @GenerationType.TABLE
với @TableGenerator(initialValue=1, ...)
. Thật không may, trong khi GenerationType.TABLE cho phép bạn chỉ định kích thước phân bổ qua @TableGenerator, nó không cung cấp bất kỳ đảm bảo nào về hành vi đặt hàng hoặc khôi phục. Xem thông số kỹ thuật JPA 2.0, mục 11.1.46 và 11.1.17. Cụ thể là "Đặc điểm kỹ thuật này không xác định hành vi chính xác của các chiến lược này. và chú thích 102 "Các ứng dụng di động không được sử dụng chú thích GeneratedValue trên các trường hoặc thuộc tính liên tục khác [than @Id
khóa chính] " . Vì vậy, không an toàn khi sử dụng @GenerationType.TABLE
để đánh số mà bạn yêu cầu không có khoảng trống hoặc đánh số không thuộc thuộc tính khóa chính trừ khi nhà cung cấp JPA của bạn đảm bảo nhiều hơn tiêu chuẩn.
Nếu bạn gặp khó khăn với một trình tự :
Người đăng lưu ý rằng họ có các ứng dụng hiện có sử dụng DB đã sử dụng một trình tự, vì vậy họ bị mắc kẹt với nó.
Tiêu chuẩn JPA không đảm bảo rằng bạn có thể sử dụng các cột đã tạo ngoại trừ trên @Id, bạn có thể (a) bỏ qua điều đó và tiếp tục miễn là nhà cung cấp của bạn cho phép bạn hoặc (b) thực hiện chèn với giá trị mặc định và -đọc từ cơ sở dữ liệu. Cái sau an toàn hơn:
@Column(name = "inv_seq", insertable=false, updatable=false)
public Integer getInvoiceSeq() {
return invoiceSeq;
}
Vì insertable=false
nhà cung cấp sẽ không cố gắng chỉ định giá trị cho cột. Bây giờ bạn có thể đặt một DEFAULT
phù hợp trong cơ sở dữ liệu, như nextval('some_sequence')
và nó sẽ rất vinh dự. Bạn có thể phải đọc lại thực thể từ cơ sở dữ liệu với EntityManager.refresh()
sau khi kiên trì nó - tôi không chắc liệu nhà cung cấp tính năng ổn định có làm điều đó cho bạn hay không và tôi chưa kiểm tra thông số kỹ thuật hoặc viết một chương trình demo.
Nhược điểm duy nhất là có vẻ như không thể tạo cột @ NotNull hoặc nullable=false
, vì nhà cung cấp không hiểu rằng cơ sở dữ liệu có mặc định cho cột. Nó vẫn có thể là NOT NULL
trong cơ sở dữ liệu.
Nếu bạn may mắn, các ứng dụng khác của bạn cũng sẽ sử dụng cách tiếp cận tiêu chuẩn là bỏ qua cột trình tự khỏi INSERT
danh sách cột của hoặc chỉ định rõ ràng từ khóa DEFAULT
dưới dạng giá trị, thay vì gọi nextval
. Sẽ không khó để tìm ra điều đó bằng cách bật log_statement = 'all'
trong postgresql.conf
và tìm kiếm các bản ghi. Nếu đúng như vậy, thì bạn thực sự có thể chuyển mọi thứ sang trạng thái trống rỗng nếu bạn quyết định cần phải thay thế bằng cách thay thế DEFAULT
của bạn với BEFORE INSERT ... FOR EACH ROW
hàm kích hoạt đặt NEW.invoice_number
từ bàn truy cập.