2016-07-29 36 views
2

我正在編寫一個跟蹤藥物使用情況的軟件。我正在使用JPA與數據庫進行交互。我的模型由兩個實體組成:一個Prescription和一個Dose。每個PrescriptionDose實例集合代表給患者的劑量爲像這樣這個藥方的一部分:JPA:執行間接插入時鎖定

Prescription.java

@Entity 
@XmlRootElement 
public class Prescription { 

    private long id; 
    private Collection<Dose> doses = new ArrayList<Dose>(); 
    /** 
    * Versioning field used by JPA to track concurrent changes. 
    */ 
    private long version; 
    // Other properties omitted... 

    @Id 
    @GeneratedValue(strategy = GenerationType.TABLE) 
    public long getId() { 
     return id; 
    } 

    public void setId(long id) { 
     this.id = id; 
    } 

    // We specify cascade such that when we modify this collection, it will propagate to the DOSE table (e.g. when 
    // adding a new dose to this collection, a corresponding record will be created in the DOSE table). 
    @OneToMany(mappedBy = "prescription", cascade = CascadeType.ALL) 
    public Collection<Dose> getDoses() { 
     // todo update to list or collection interface. 
     return doses; 
    } 

    public void setDoses(Collection<Dose> doses) { 
     this.doses = doses; 
    } 

    @Version 
    public long getVersion() { 
     return version; 
    } 

    /** 
    * Application code should not call this method. However, it must be present for JPA to function. 
    * @param version 
    */ 
    public void setVersion(long version) { 
     this.version = version; 
    } 
} 

Dose.java

@Entity 
@XmlRootElement 
public class Dose { 

    private long id; 
    private Prescription prescription; 
    // Other properties omitted... 

    @Id 
    @GeneratedValue(strategy = GenerationType.TABLE) 
    public long getId() { 
     return id; 
    } 

    public void setId(long id) { 
     this.id = id; 
    } 

    @XmlTransient 
    @ManyToOne 
    @JoinColumn(name = "PRESCRIPTION_ID") // Specifies name of column pointing back to the parent prescription. 
    public Prescription getPrescription() { 
     return prescription; 
    } 

    public void setPrescription(Prescription prescription) { 
     this.prescription = prescription; 
    } 

} 

A Dose只能存在於Prescription的上下文中,因此Dose插入到數據庫間接地將它添加到其處方的收藏劑量:

DoseService.java

@Stateless 
public class DoseService { 

    @PersistenceContext(unitName = "PrescriptionUnit") 
    private EntityManager entityMgr; 

    /** 
    * Insert a new dose for a given prescription ID. 
    * @param prescriptionId The prescription ID. 
    * @return The inserted {@code Dose} instance if insertion was successful, 
    * or {@code null} if insertion failed (if there is currently no doses available for the given prescription ID). 
    */ 
    @TransactionAttribute(value = TransactionAttributeType.REQUIRED) 
    public Dose addDose(long prescriptionId) { 
     // Find the prescription. 
     Prescription p = entityMgr.find(Prescription.class, prescriptionId); 
     if (p == null) { 
      // Invalid prescription ID. 
      throw new IllegalArgumentException("Prescription with id " + prescriptionId + " does not exist."); 
     } 
     // TODO is this sufficient locking? 
     entityMgr.lock(p, LockModeType.OPTIMISTIC_FORCE_INCREMENT); 
     Dose d = null; 
     if (isDoseAvailable(p)) { 
      // A dose is available, create it and insert it into the database. 
      d = new Dose(); 
      // Setup the link between the new dose and its parent prescription. 
      d.setPrescription(p); 
      p.getDoses().add(d); 
     } 
     try { 
      // Flush changes to database. 
      entityMgr.flush(); 
      return d; 
     } catch (OptimisticLockException ole) { 
      // Rethrow application-managed exception to ensure that caller will have a chance of detecting failure due to concurrent updates. 
      // (OptimisticLockExceptions can be swallowed by the container) 
      // See "Recovering from Optimistic Failures" (page 365) in "Pro JPA 2" by M. Keith and M. Schincariol for details. 
      throw new ChangeCollisionException(); 
     } 
    } 


    /** 
    * Checks if a dose is available for a given prescription. 
    * @param p The prescription for which to look up if a dose is available. 
    * @return {@code true} if a dose is available, {@code false} otherwise. 
    */ 
    @TransactionAttribute(value = TransactionAttributeType.MANDATORY) 
    private boolean isDoseAvailable(Prescription p) { 
     // Business logic that inspects p.getDoses() and decides if it is safe to give the patient a dose at this time. 
    } 

} 

addDose(long)可以同時調用。當決定是否有劑量時,業務邏輯會檢查處方的劑量集合。如果同時修改此集合(例如,通過併發調用addDose(long)),則該事務將失敗。我使用LockModeType.OPTIMISTIC_FORCE_INCREMENT來實現此目的(而不是獲取DOSE表中的表鎖)。在Pro JPA 2 by Keith and Schincariol我讀過:

寫鎖保證所有的樂觀讀鎖定,但 還承諾要增加版本字段中交易 無論用戶是否更新了實體與否。 使用OPTIMISTIC_FORCE_INCREMENT的常見情況是在對象 模型中實體關係指針更改時保證 跨實體關係更改的一致性(通常它們是 與目標外鍵的一對多關係),但在數據模型 中,實體表中沒有列更改。

我對這種鎖模式的理解是否正確?我目前的策略是否確保如果對處方的劑量收集做出任何更改(是添加,刪除還是更新收集中的任何劑量),交易將會失敗?

+0

根據我的理解,如果對象上存在併發事務,則無論該併發事務是否修改了您的對象,您的事務都將失敗。在樂觀併發中,如果修改對象的版本和當前對象的版本不匹配,事務將失敗。 – sturcotte06

+0

您使用的是什麼容器/ Web應用程序服務器?並非所有容器都以同樣的方式實現JPA或EJB標準。一些完全忽略某些參數或註釋。 –

+0

@JeffreyColeman我正在使用GlassFish開源版本。 –

回答

0

This answer幫助我更好地理解OPTIMISTIC_WRITE_LOCK,並讓我相信我的實施是正確的。

一個更詳盡的解釋如下(報價增加,因爲它出現在由自己撰寫的報告):

雖然EJB交易可能有助於防止實體的持久狀態併發的變化,他們在這個不足案件。 這是因爲它們無法檢測到對Prescription實體的更改,因爲其相應的數據庫 行在添加新Dose時未更改。這起源於 這一事實,即Dose是其自身與其Prescription之間的關係 的擁有方。在數據庫中, 代表Dose的行將具有指向 Prescription的外鍵,但表示Prescription的行將不會指向其任何Dose s。當插入新的Dose時, 通過樂觀寫鎖鎖定 ,強制 更新到Prescription的行(具體爲:其版本字段)。

1

它看起來是正確的。

不過,我會建議首先測試它...更簡單的方法來做到這一點,是通過調試......用你喜歡的IDE,設置了一句後調試點:

entityMgr.lock(p, LockModeType.OPTIMISTIC_FORCE_INCREMENT); 

稍後,嘗試從兩個不同的客戶端調用addDose(prescriptionId),提供相同的處方ID ...並讓一個客戶端先完成,然後看看另一個客戶端發生了什麼。

+0

感謝您提供調試建議。我添加了+1。然而,爲了接受答案(例如解釋特定於該場景的鎖定細節的答案),我正在尋找比「看起來合適」更強的正確性確認。 –