2016-07-29 1 views
2

私は薬物使用を追跡するソフトウェアを書いています。 JPAを使用してデータベースと対話しています。私のモデルは、PrescriptionDoseという2つのエンティティで構成されています。各Prescriptionがそうように、この処方の一部として患者に与えられる線量を表すDoseインスタンスのコレクションがあります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; 
    } 

} 

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を使用する一般的なケースは、 エンティティリレーションシップの変更(多くの場合、ターゲット外部キーと の一対多のリレーションシップである)の一致を保証することです。 モデルではエンティティリレーションシップポインタが変更されますデータモデルでは エンティティテーブルの列は変更されません。

このロックモードの理解は正しいですか?私の現在の戦略は、処方薬の投与量の変更(収集における投与量の追加、削除、更新)があれば、addDoseトランザクションが失敗することを確実にしていますか?

+0

私の理解から、同時トランザクションがオブジェクトを変更するかどうかにかかわらず、オブジェクトに並行トランザクションがあると、トランザクションは失敗します。オプティミスティックな並行処理では、変更されたオブジェクトのバージョンと現在のオブジェクトのバージョンが一致しないと、トランザクションは失敗します。 – sturcotte06

+0

どのコンテナ/ Webアプリケーションサーバーを使用していますか?すべてのコンテナが同じ方法でJPAまたはEJB標準を実装しているわけではありません。あるものは、特定のパラメータや注釈を完全に無視します。 –

+0

@JeffreyColeman私はGlassFishオープンソース版を使用しています。 –

答えて

0

This answer私はOPTIMISTIC_WRITE_LOCKをよく理解し、私の実装が正しいことを確信しました。

より精巧な説明は、(それが自分が執筆したレポートに表示される引用が追加された)次の:

EJBのトランザクションがエンティティの永続状態への同時変更を防ぐのを助けるかもしれないが、彼らはこの中で不十分です場合。 Prescriptionエンティティへの変更を対応するデータベースとして検出できないためです。 新しいDoseが追加された場合、その行は変更されません。これは、 から、Doseがそれ自身とPrescriptionの間の関係 の所有側であるという事実に起因します。データベースでは、 がDoseを表す行には、 Prescriptionを指す外部キーがありますが、Prescriptionを表す行は、Doseのいずれのポインタも持ちません。新しいDoseが挿入されたときにPrescriptionの行(具体的にはそのバージョンフィールド)への更新を強制的に実行する楽観的書き込みロックでPrescriptionを守ることによって、この問題は改善されます。

1

それが正しいようです。

entityMgr.lock(p, LockModeType.OPTIMISTIC_FORCE_INCREMENT); 

しかし、私が最初にそれをテストするために...これを行うには、より簡単な方法を提案します、デバッグすることです...お好みのIDEを使用して、文の後にデバッグポイントを設定

その後、同じ処方IDを指定して2つの異なるクライアントからaddDose(prescriptionId)を呼び出してみましょう。そして、1つのクライアントが先に終了し、もう1つのクライアントで何が起こるかを見てみましょう。

+0

デバッグの提案をありがとう。私は+1を加えました。しかし、回答を受け入れるためには、正しいと思われるよりも確かな確かな確認が必要です(このシナリオに固有のロックの詳細を説明する回答など)。 –

関連する問題