JPA:执行间接插入时锁定

时间:2016-07-29 18:00:53

标签: java jpa

我正在编写一个跟踪药物使用情况的软件。我正在使用JPA与数据库进行交互。我的模型由两个实体组成:PrescriptionDose。每个Prescription都有Dose个实例的集合,代表作为此处方的一部分给予患者的剂量,如下所示:

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添加到其处方的剂量集合中间接插入数据库@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. } }

DoseService.java

addDose(long)

addDose(long)可以同时调用。在决定剂量是否可用时,业务逻辑检查处方的剂量集合。如果同时修改此集合(例如,通过并发调用LockModeType.OPTIMISTIC_FORCE_INCREMENT),则事务应该失败。我使用addDose来实现这一点(而不是在DOSE表上获取表锁)。在Pro JPA 2 by Keith and Schincariol我已经读过:

  

写锁定保证了乐观读锁的所有功能,但是   还承诺增加交易中的版本字段   无论用户是否更新了实体。 [...]   使用OPTIMISTIC_FORCE_INCREMENT的常见情况是保证   实体关系变化的一致性(通常是   在对象中时与目标外键的一对多关系   模型实体关系指针发生了变化,但在数据模型中   实体表中没有列发生变化。

我对这种锁定模式的理解是否正确?我的当前策略是否确保public void myMethod() { if (condition1) { doSomething1(); if (condition2) { doSomething2(); } else { doSomething3(); } } } 交易如果对处方的剂量集合有任何改变(无论是添加,删除或更新集合中的任何剂量)都会失败?

2 个答案:

答案 0 :(得分:1)

看起来是对的。

但是,我建议先测试一下......更简单的方法是通过调试...使用你喜欢的IDE,在句子后面设置一个调试点:

entityMgr.lock(p, LockModeType.OPTIMISTIC_FORCE_INCREMENT);

稍后,尝试从两个不同的客户端调用您的addDose(prescriptionId),提供相同的prescriptionID ...并让一个客户端先完成,看看另一个客户端发生了什么。

答案 1 :(得分:0)

This answer帮助我更好地理解OPTIMISTIC_WRITE_LOCK,并使我确信我的实施是正确的。

下面是一个更详细的解释(引用在我自己撰写的报告中添加时引用):

  

虽然EJB事务可能有助于防止对实体的持久状态进行并发更改,但在这种情况下它们是不够的。   这是因为他们无法检测到Prescription实体对其相应数据库的更改   添加新Dose时,行不会更改。这源于   事实上Dose是关系的拥有方   在它自己和Prescription之间。在数据库中,行   表示Dose将有一个指向的外键   Prescription,但代表Prescription的行会   没有指向任何Dose的指针。这个问题可以解决   使用强制写入锁定来保护Prescription   插入新Prescription时对Dose行的更新(具体为:其版本字段)。