私は薬物使用を追跡するソフトウェアを書いています。 JPAを使用してデータベースと対話しています。私のモデルは、Prescription
とDose
という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
トランザクションが失敗することを確実にしていますか?
私の理解から、同時トランザクションがオブジェクトを変更するかどうかにかかわらず、オブジェクトに並行トランザクションがあると、トランザクションは失敗します。オプティミスティックな並行処理では、変更されたオブジェクトのバージョンと現在のオブジェクトのバージョンが一致しないと、トランザクションは失敗します。 – sturcotte06
どのコンテナ/ Webアプリケーションサーバーを使用していますか?すべてのコンテナが同じ方法でJPAまたはEJB標準を実装しているわけではありません。あるものは、特定のパラメータや注釈を完全に無視します。 –
@JeffreyColeman私はGlassFishオープンソース版を使用しています。 –