Java - JPA - คำอธิบายประกอบ @Version


118

วิธีการ@Versionทำงานของคำอธิบายประกอบใน JPA?

ฉันพบคำตอบต่างๆซึ่งมีสารสกัดดังนี้:

JPA ใช้ฟิลด์เวอร์ชันในเอนทิตีของคุณเพื่อตรวจหาการแก้ไขพร้อมกันกับเรกคอร์ดที่เก็บข้อมูลเดียวกัน เมื่อรันไทม์ JPA ตรวจพบความพยายามที่จะแก้ไขเร็กคอร์ดเดียวกันพร้อมกันมันจะแสดงข้อยกเว้นสำหรับธุรกรรมที่พยายามคอมมิตครั้งสุดท้าย

แต่ฉันยังไม่แน่ใจว่ามันทำงานอย่างไร


จากบรรทัดต่อไปนี้:

คุณควรพิจารณาฟิลด์เวอร์ชันไม่เปลี่ยนรูป การเปลี่ยนค่าฟิลด์มีผลลัพธ์ที่ไม่ได้กำหนดไว้

หมายความว่าเราควรประกาศฟิลด์เวอร์ชันของเราเป็นfinal?


1
ทั้งหมดก็ไม่สามารถตรวจสอบ / UPDATE myentity SET mycolumn = 'new value', version = version + 1 WHERE version = [old.version]อัปเดตเวอร์ชันในการสอบถามการปรับปรุงทุก: หากมีคนอัปเดตระเบียนold.versionจะไม่ตรงกับข้อมูลในฐานข้อมูลอีกต่อไปและส่วนคำสั่งจะป้องกันไม่ให้การอัปเดตเกิดขึ้น 'แถวที่อัปเดต' จะเป็น0ซึ่ง JPA สามารถตรวจพบเพื่อสรุปว่ามีการแก้ไขพร้อมกันเกิดขึ้น
Stijn de Witt

คำตอบ:


189

แต่ฉันยังไม่แน่ใจว่ามันทำงานอย่างไร?

สมมติว่าเอนทิตีMyEntityมีversionคุณสมบัติที่มีคำอธิบายประกอบ:

@Entity
public class MyEntity implements Serializable {    

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Version
    private Long version;

    //...
}

ในการอัปเดตฟิลด์ที่มีคำอธิบายประกอบ@Versionจะถูกเพิ่มขึ้นและเพิ่มเข้าไปในWHEREส่วนคำสั่งเช่นนี้:

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

หากWHEREประโยคไม่ตรงกับเรกคอร์ด (เนื่องจากเอนทิตีเดียวกันได้รับการอัปเดตโดยเธรดอื่นแล้ว) ผู้ให้บริการการคงอยู่จะโยนOptimisticLockExceptionไฟล์.

หมายความว่าเราควรประกาศฟิลด์เวอร์ชันของเราเป็นที่สิ้นสุด

ไม่ได้ แต่คุณสามารถพิจารณาให้ setter ได้รับการป้องกันเนื่องจากคุณไม่ควรเรียกมัน


5
ฉันได้รับข้อยกเว้นของตัวชี้ค่า null ด้วยส่วนของรหัสนี้เนื่องจาก Long initialises เป็น null น่าจะเริ่มต้นอย่างชัดเจนด้วย 0L
Markus

2
อย่าพึ่งพาการแกะกล่องอัตโนมัติlongหรือเพียงแค่เรียกlongValue()ใช้ คุณต้องnullตรวจสอบอย่างชัดเจน การเริ่มต้นด้วยตัวเองอย่างชัดเจนฉันจะไม่ทำเนื่องจากฟิลด์นี้ควรได้รับการจัดการโดยผู้ให้บริการ ORM ฉันคิดว่าได้รับการตั้งค่า0Lเป็นแทรกครั้งแรกในฐานข้อมูลดังนั้นหากตั้งค่าเป็นnullสิ่งนี้จะบอกคุณว่าบันทึกนี้ยังไม่ได้รับการยืนยัน
Stijn de Witt

สิ่งนี้จะป้องกันการสร้างเอนทิตีที่ (พยายาม) สร้างมากกว่าหนึ่งครั้งหรือไม่ ฉันหมายถึงสมมติว่าข้อความหัวข้อ JMS ทริกเกอร์การสร้างเอนทิตีและมีหลายอินสแตนซ์ของแอปพลิเคชันที่รับฟังข้อความ ฉันแค่ต้องการหลีกเลี่ยงข้อผิดพลาดการละเมิดข้อ จำกัด ที่ไม่ซ้ำกัน ..
มนู

ขอโทษนะฉันรู้ว่านี่เป็นกระทู้เก่า แต่ @Version จะคำนึงถึงแคชสกปรกในสภาพแวดล้อมคลัสเตอร์ด้วยหรือไม่
Shawn Eion Smith

3
หากใช้ Spring Data JPA อาจเป็นการดีกว่าที่จะใช้ wrapper Longแทนที่จะเป็นแบบดั้งเดิมlongสำหรับ@Versionฟิลด์เนื่องจากSimpleJpaRepository.save(entity)มีแนวโน้มที่จะถือว่าฟิลด์เวอร์ชันว่างเป็นตัวบ่งชี้ว่าเอนทิตีนั้นใหม่ หาก บริษัท ที่เป็นของใหม่ (ตามที่ระบุโดยรุ่นที่เป็นอยู่ null), em.persist(entity)ฤดูใบไม้ผลิจะเรียก แต่ถ้าเวอร์ชั่นมีค่าก็จะโทรem.merge(entity). นี่คือเหตุผลที่ช่องเวอร์ชันที่ประกาศเป็นประเภท Wrapper จึงควรปล่อยไว้โดยไม่ได้เริ่มต้น
rdguam

33

แม้ว่าคำตอบของ @Pascal จะใช้ได้อย่างสมบูรณ์แบบ แต่จากประสบการณ์ของฉันฉันพบว่ารหัสด้านล่างมีประโยชน์ในการล็อกการมองโลกในแง่ดีให้สำเร็จ:

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

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

    // ...
}

ทำไม? เพราะ:

  1. ล็อคในแง่ดีจะไม่ทำงานถ้าฟิลด์ข้อเขียนที่มีการตั้งค่าโดยไม่ตั้งใจที่จะ@Versionnull
  2. ในฐานะที่เป็นเขตพิเศษนี้ไม่จำเป็นต้องเป็นรุ่นธุรกิจของวัตถุเพื่อหลีกเลี่ยงความเข้าใจผิดที่ฉันชอบที่จะตั้งชื่อข้อมูลดังกล่าวไปยังสิ่งที่ต้องการมากกว่าoptlockversion

จุดแรกไม่สำคัญว่าถ้าใช้แอพลิเคชันเฉพาะ JPA สำหรับการแทรกข้อมูลลงในฐานข้อมูลที่เป็น JPA ผู้ขายจะบังคับใช้0สำหรับ@versionสนามที่เวลาการสร้าง แต่ก็มีการใช้คำสั่ง SQL ธรรมดา (อย่างน้อยระหว่างการทดสอบหน่วยและการรวม)


ฉันเรียกมันว่าu_lmod(ผู้ใช้แก้ไขล่าสุด) โดยที่ผู้ใช้สามารถเป็นกระบวนการ / เอนทิตี / แอปที่เป็นมนุษย์หรือที่กำหนดไว้ (โดยอัตโนมัติ) (ดังนั้นการจัดเก็บเพิ่มเติมu_lmod_idก็สมเหตุสมผลเช่นกัน) (ในมุมมองของฉันเป็นธุรกิจ - เมตา - แอตทริบิวต์ขั้นพื้นฐาน) โดยทั่วไปจะจัดเก็บค่าเป็น DATE (TIME) (หมายถึง infos เกี่ยวกับปี ... มิลลิวินาที) ใน UTC (หาก "ตำแหน่ง" ของตัวแก้ไขมีความสำคัญในการจัดเก็บตามเขตเวลา) นอกจากนี้ยังมีประโยชน์มากสำหรับสถานการณ์การซิงค์ DWH
Andreas Dietrich

7
ฉันคิดว่าควรใช้ bigint แทนจำนวนเต็มในประเภท db เพื่อให้ตรงกับประเภท java แบบยาว
Dmitry

จุดดีมิทรีขอบคุณแม้ว่าเมื่อพูดถึงการกำหนดเวอร์ชัน IMHO มันไม่สำคัญจริงๆ
G. Demecki

9

ทุกครั้งที่มีการอัปเดตเอนทิตีในฐานข้อมูลฟิลด์เวอร์ชันจะเพิ่มขึ้นทีละรายการ ทุกการดำเนินการที่อัปเดตเอนทิตีในฐานข้อมูลจะผนวกเข้าWHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASEกับแบบสอบถาม

ในการตรวจสอบแถวที่ได้รับผลกระทบของการดำเนินการของคุณกรอบงาน jpa สามารถตรวจสอบให้แน่ใจว่าไม่มีการแก้ไขพร้อมกันระหว่างการโหลดและการคงอยู่ของเอนทิตีของคุณเนื่องจากแบบสอบถามจะไม่พบเอนทิตีของคุณในฐานข้อมูลเมื่อมีการเพิ่มหมายเลขเวอร์ชันระหว่างโหลดและคงอยู่


ไม่ผ่าน JPAUpdateQuery มันไม่ได้ ดังนั้น "ทุกการดำเนินการที่อัปเดตเอนทิตีในฐานข้อมูลจะมีการต่อท้าย WHERE version = VERSION_THAT_WAS_LOADED_FROM_DATABASE ในแบบสอบถาม" จึงไม่เป็นความจริง
Pedro Borges

2

เวอร์ชันที่ใช้เพื่อให้แน่ใจว่ามีการอัปเดตเพียงครั้งเดียว ผู้ให้บริการ JPA จะตรวจสอบเวอร์ชันหากเวอร์ชันที่คาดไว้เพิ่มขึ้นแล้วมีคนอื่นอัปเดตเอนทิตีอยู่แล้วดังนั้นข้อยกเว้นจะถูกยกเลิก

ดังนั้นการอัปเดตค่าเอนทิตีจะปลอดภัยมากขึ้นและมองโลกในแง่ดี

หากค่าเปลี่ยนแปลงบ่อยคุณอาจพิจารณาไม่ใช้ฟิลด์เวอร์ชัน ตัวอย่างเช่น "เอนทิตีที่มีฟิลด์ตัวนับซึ่งจะเพิ่มขึ้นทุกครั้งที่มีการเข้าถึงหน้าเว็บ"


0

เพียงแค่เพิ่มข้อมูลเพิ่มเติมเล็กน้อย

JPA จะจัดการเวอร์ชันภายใต้ประทุนให้คุณอย่างไรก็ตามจะไม่ทำเช่นนั้นเมื่อคุณอัปเดตบันทึกของคุณผ่านJPAUpdateClauseในกรณีเช่นนี้คุณต้องเพิ่มการเพิ่มเวอร์ชันลงในแบบสอบถามด้วยตนเอง

อาจกล่าวได้เช่นเดียวกันเกี่ยวกับการอัปเดตผ่าน JPQL กล่าวคือไม่ใช่การเปลี่ยนแปลงเอนทิตีง่ายๆ แต่เป็นการอัปเดตคำสั่งไปยังฐานข้อมูลแม้ว่าจะทำโดยการจำศีลก็ตาม

เปโดร


3
คืออะไรJPAUpdateClause?
Karl Richter

คำถามที่ดีคำตอบง่ายๆคือ: ตัวอย่างที่ไม่ดี :) JPAUpdateClause เป็น QueryDSL ที่เฉพาะเจาะจงคิดว่าการใช้งานการอัปเดตด้วย JPQL แทนที่จะส่งผลต่อสถานะของเอนทิตีในโค้ด
Pedro Borges
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.