Java - JPA - chú thích @Version


118

@VersionChú thích hoạt động như thế nào trong JPA?

Tôi đã tìm thấy nhiều câu trả lời khác nhau có trích dẫn như sau:

JPA sử dụng trường phiên bản trong các thực thể của bạn để phát hiện các sửa đổi đồng thời đối với cùng một bản ghi kho dữ liệu. Khi thời gian chạy JPA phát hiện nỗ lực sửa đổi đồng thời cùng một bản ghi, nó sẽ ném ra một ngoại lệ cho giao dịch đang cố gắng thực hiện cuối cùng.

Nhưng tôi vẫn không chắc nó hoạt động như thế nào.


Cũng như từ các dòng sau:

Bạn nên coi các trường phiên bản là bất biến. Thay đổi giá trị trường có kết quả không xác định.

Có nghĩa là chúng ta nên khai báo trường phiên bản của mình là final?


1
Tất cả nó làm là kiểm tra / cập nhật các phiên bản trong mỗi truy vấn cập nhật: UPDATE myentity SET mycolumn = 'new value', version = version + 1 WHERE version = [old.version]. Nếu ai đó cập nhật bản ghi, bản ghi old.versionsẽ không còn khớp với bản ghi trong DB và mệnh đề where sẽ ngăn cập nhật xảy ra. 'Các hàng được cập nhật' sẽ là 0, mà JPA có thể phát hiện để kết luận rằng một sửa đổi đồng thời đã xảy ra.
Stijn de Witt

Câu trả lời:


189

Nhưng tôi vẫn không chắc nó hoạt động như thế nào?

Giả sử một thực thể MyEntitycó thuộc versiontính chú thích :

@Entity
public class MyEntity implements Serializable {    

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Version
    private Long version;

    //...
}

Khi cập nhật, trường được chú thích bằng @Versionsẽ được tăng dần và được thêm vào WHEREmệnh đề, giống như sau:

UPDATE MYENTITY SET ..., VERSION = VERSION + 1 WHERE ((ID = ?) AND (VERSION = ?))

Nếu WHEREmệnh đề không khớp với một bản ghi (vì cùng một thực thể đã được cập nhật bởi một luồng khác), thì trình cung cấp độ bền sẽ ném một OptimisticLockException.

Có nghĩa là chúng ta nên khai báo trường phiên bản của mình là trường cuối cùng

Không nhưng bạn có thể cân nhắc việc bảo vệ setter vì bạn không nên gọi nó.


5
Tôi gặp trường hợp ngoại lệ con trỏ null với đoạn mã này, bởi vì Long khởi tạo là null, có lẽ nên được khởi tạo nhanh chóng với 0L.
Markus

2
Đừng dựa vào việc tự động mở hộp longhoặc chỉ cần gọi longValue()nó. Bạn cần nullkiểm tra rõ ràng . Tự khởi tạo nó một cách rõ ràng Tôi sẽ không thực hiện vì trường này được nhà cung cấp ORM quản lý. Tôi nghĩ rằng nó được đặt 0Lở lần chèn đầu tiên vào DB, vì vậy nếu nó được đặt thành nullnày cho bạn biết bản ghi này vẫn chưa được lưu giữ.
Stijn de Witt

Điều này có ngăn cản việc tạo một thực thể được (cố gắng) tạo nhiều lần không? Ý tôi là giả sử một thông báo chủ đề JMS kích hoạt tạo một thực thể và có nhiều trường hợp ứng dụng lắng nghe thông báo. Tôi chỉ muốn tránh lỗi vi phạm ràng buộc duy nhất ..
Manu

Xin lỗi, tôi nhận ra rằng đây là một chuỗi cũ, nhưng @Version có tính đến bộ đệm bẩn trong môi trường được phân cụm không?
Shawn Eion Smith,

3
Nếu sử dụng Spring Data JPA, tốt hơn là nên sử dụng một trình bao bọc Longhơn là một trình bao bọc nguyên thủy longcho @Versiontrường, vì SimpleJpaRepository.save(entity)có thể sẽ coi trường phiên bản rỗng như một chỉ báo rằng thực thể là mới. Nếu thực thể là mới (như được chỉ ra bởi phiên bản là null), Spring sẽ gọi em.persist(entity). Nhưng nếu phiên bản có một giá trị, thì nó sẽ gọi em.merge(entity). Đây cũng là lý do tại sao trường phiên bản được khai báo là loại trình bao bọc nên không được khởi tạo.
rdguam 24-07-19

33

Mặc dù câu trả lời @Pascal là hoàn toàn hợp lệ, từ kinh nghiệm của tôi, tôi thấy mã bên dưới hữu ích để thực hiện khóa lạc quan:

@Entity
public class MyEntity implements Serializable {    
    // ...

    @Version
    @Column(name = "optlock", columnDefinition = "integer DEFAULT 0", nullable = false)
    private long version = 0L;

    // ...
}

Tại sao? Bởi vì:

  1. Khóa lạc quan sẽ không hoạt động nếu trường được chú thích bằng @Versionvô tình được đặt thành null.
  2. Vì trường đặc biệt này không nhất thiết phải là phiên bản doanh nghiệp của đối tượng, để tránh gây hiểu lầm, tôi thích đặt tên trường như vậy thành một cái gì đó tương tự optlockhơn là version.

Điểm đầu tiên không quan trọng nếu sử dụng ứng dụng chỉ JPA để chèn dữ liệu vào cơ sở dữ liệu, như JPA nhà cung cấp sẽ thi hành 0cho @versionlĩnh vực vào thời điểm sáng tạo. Nhưng hầu như luôn luôn sử dụng các câu lệnh SQL thuần túy (ít nhất là trong quá trình kiểm tra đơn vị và tích hợp).


Tôi gọi đó là u_lmod(người dùng sửa đổi lần cuối) trong đó người dùng có thể là con người hoặc quy trình / thực thể / ứng dụng được xác định (tự động) (vì vậy việc lưu trữ bổ sung u_lmod_idcũng có thể có ý nghĩa). (Theo quan điểm của tôi, đây là một thuộc tính meta kinh doanh rất cơ bản). Nó thường lưu trữ giá trị của nó dưới dạng DATE (TIME) (có nghĩa là thông tin về năm ... mili) theo giờ UTC (nếu "vị trí" của trình chỉnh sửa là quan trọng để lưu trữ thì với múi giờ). cũng rất hữu ích cho các kịch bản đồng bộ hóa DWH.
Andreas Dietrich

7
Tôi nghĩ rằng bigint thay vì một số nguyên nên được sử dụng trong loại db để phù hợp với một loại java dài.
Dmitry

Điểm tốt Dmitry, cảm ơn, mặc dù khi nói đến lập phiên bản IMHO, điều đó không thực sự quan trọng.
G. Demecki

9

Mỗi khi một thực thể được cập nhật trong cơ sở dữ liệu, trường phiên bản sẽ được tăng thêm một. Mọi thao tác cập nhật thực thể trong cơ sở dữ liệu sẽ được thêm vào WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASEtruy vấn của nó.

Khi kiểm tra các hàng bị ảnh hưởng trong hoạt động của bạn, khuôn khổ jpa có thể đảm bảo rằng không có sửa đổi đồng thời giữa tải và duy trì thực thể của bạn vì truy vấn sẽ không tìm thấy thực thể của bạn trong cơ sở dữ liệu khi số phiên bản của nó đã được tăng lên giữa quá trình tải và tồn tại.


Không qua JPAUpdateQuery thì không. Vì vậy, "Mọi thao tác cập nhật thực thể trong cơ sở dữ liệu sẽ có thêm phiên bản WHERE = VERSION_THAT_WAS_LOADED_FROM_DATABASE vào truy vấn của nó" là không đúng.
Pedro Borges

2

Phiên bản được sử dụng để đảm bảo rằng chỉ có một bản cập nhật trong một thời gian. Nhà cung cấp JPA sẽ kiểm tra phiên bản, nếu phiên bản dự kiến ​​đã tăng lên thì người khác đã cập nhật thực thể nên một ngoại lệ sẽ được đưa ra.

Vì vậy, việc cập nhật giá trị thực thể sẽ an toàn hơn, tối ưu hơn.

Nếu giá trị thay đổi thường xuyên, thì bạn có thể cân nhắc không sử dụng trường phiên bản. Ví dụ: "một thực thể có trường bộ đếm, trường đó sẽ tăng lên mỗi khi một trang web được truy cập"


0

Chỉ cần thêm một chút thông tin.

JPA quản lý phiên bản cho bạn, tuy nhiên nó không làm như vậy khi bạn cập nhật bản ghi của mình JPAUpdateClause, trong những trường hợp như vậy, bạn cần phải thêm phần tăng phiên bản vào truy vấn theo cách thủ công.

Tương tự có thể nói về cập nhật thông qua JPQL, tức là không phải là một thay đổi đơn giản đối với thực thể, mà là lệnh cập nhật cho cơ sở dữ liệu ngay cả khi điều đó được thực hiện bằng chế độ ngủ đông

Pedro


3
JPAUpdateClausegì?
Karl Richter

Câu hỏi hay, câu trả lời đơn giản là: ví dụ xấu :) JPAUpdateClause là QueryDSL cụ thể, hãy nghĩ rằng chạy một bản cập nhật với JPQL thay vì chỉ ảnh hưởng đến trạng thái của thực thể trong mã.
Pedro Borges
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.