我正在编写一个跟踪药物使用情况的软件。我正在使用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添加到其处方的剂量集合中,可以将addDose(long)间接插入数据库中:

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)可以被同时调用。在确定是否有剂量时,业务逻辑将检查处方的剂量集合。如果同时修改此集合(例如,通过并发调用LockModeType.OPTIMISTIC_FORCE_INCREMENT),则事务将失败。我使用addDose实现了这一点(而不是在DOSE表上获取表锁)。在Pro JPA 2 by Keith and Schincariol中,我读到:

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

我对这种锁定模式的理解正确吗?我当前的策略是否可以确保如果处方药剂量集合发生任何变化(无论是增加,删除还是更新该剂量集合中的任何剂量),ojit_code事务处理都会失败吗?

最佳答案

似乎正确。

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

entityMgr.lock(p, LockModeType.OPTIMISTIC_FORCE_INCREMENT);

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

09-25 20:43