JPA:执行间接插入时锁定 [英] JPA: locking when performing an indirect insertion
问题描述
我正在编写一个跟踪药物使用情况的软件。我正在使用JPA与数据库进行交互。我的模型由两个实体组成:处方
和剂量
。每个处方
都有一个剂量
个实例的集合,这些实例代表了作为此处方的一部分给予患者的剂量,如下所示:
I am writing a piece of software that tracks medication usage. I am using JPA to interact with the database. My model consists of two entities: a Prescription
and a Dose
. Each Prescription
has a collection of Dose
instances which represents the doses given to the patient as part of this prescription like so:
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 剂量
只能存在于处方
的上下文中,因此会插入剂量
通过将其添加到处方的剂量集合中间接进入数据库:
A Dose
can only exist in the context of a Prescription
, and hence a Dose
is inserted into the database indirectly by adding it to its prescription's collection of doses:
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 (长)
可以同时调用。在确定剂量是否可用时,业务逻辑检查处方的剂量集合。如果同时修改此集合,则事务应该失败(例如,通过并发调用 addDose(long)
)。我使用 LockModeType.OPTIMISTIC_FORCE_INCREMENT
来实现这一点(而不是在DOSE表上获取表锁)。在 Keith和Schincariol的Pro JPA 2 中,我读过:
addDose(long)
can be called concurrently. When deciding if a dose is available, the business logic inspects the prescription's collection of doses. The transaction should fail if this collection is concurrently modified (e.g. by concurrent call to addDose(long)
). I use the LockModeType.OPTIMISTIC_FORCE_INCREMENT
to achieve this (instead of acquiring a table lock on the DOSE table). In Pro JPA 2 by Keith and Schincariol I've read that:
写锁定保证了乐观读锁的所有功能,但
也承诺增加事务
中的版本字段,无论用户是否更新实体与否。 [...]
使用OPTIMISTIC_FORCE_INCREMENT的常见情况是保证实体关系变化的
一致性(通常它们是
与目标外键的一对多关系)在对象中
模型实体关系指针发生变化,但在数据模型中
实体表中没有列发生变化。
The write lock guarantees all that the optimistic read lock does, but also pledges to increment the version field in the transaction regardless of whether a user updated the entity or not. [...] the common case for using OPTIMISTIC_FORCE_INCREMENT is to guarantee consistency across entity relationship changes (often they are one-to-many relationships with target foreign keys) when in the object model the entity relationship pointers change, but in the data model no columns in the entity table change.
我对这种锁定模式的理解是否正确?我的当前策略是否确保 addDose
交易将失败,如果处方的剂量集合有任何变化(添加,删除或更新任何剂量)收藏)?
Is my understanding of this lock mode correct? Does my current strategy ensure that the addDose
transaction will fail if there is ANY change whatsoever to the prescription's collection of doses (be that either add, remove or update of any dose in the collection)?
推荐答案
这个答案帮助我更好地理解 OPTIMISTIC_WRITE_LOCK
并让我相信我的实现是正确的。
This answer helped me understand the OPTIMISTIC_WRITE_LOCK
better and convinced me that my implementation is correct.
更精细解释如下(报价在我自己撰写的报告中添加):
A more elaborate explanation follows (quotation added as it appears in a report authored by myself):
虽然EJB事务可能有助于防止对持久状态的并发更改在这种情况下,他们是不够的。
这是因为他们无法检测到Prescription
实体的更改,因为当新的<$ c $时,相应的数据库
行不会更改c>添加剂量。这源于
,Dose
是其与处方$之间关系
的拥有方。 C $ C>。在数据库中,
代表剂量
的行将有一个指向
处方的外键
,但是代表处方
的行将是
没有指向任何Dose
的指针秒。这个问题可以通过
来保护处方
,并带有乐观的写锁定,强制
更新处方$当插入新的
剂量
时,c $ c>的行(具体为:其版本字段)。
While EJB transactions may help prevent concurrent changes to the persisted state of an entity, they are insufficient in this case. This is because they are unable to detect the change to the
Prescription
entity as its corresponding database row is not changed when a newDose
is added to it. This stems from the fact that theDose
is the owning side of the relationship between itself and itsPrescription
. In the database, the row that represents theDose
will have a foreign key pointing to thePrescription
, but the row that represents thePrescription
will have no pointers to any of itsDose
s. The problem is remedied by guarding thePrescription
with an optimistic write lock that forces an update to thePrescription
’s row (to be specific: its version field) when a newDose
is inserted.
这篇关于JPA:执行间接插入时锁定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!