JPA:执行间接插入时锁定 [英] JPA: locking when performing an indirect insertion

查看:84
本文介绍了JPA:执行间接插入时锁定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个跟踪药物使用情况的软件。我正在使用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 new Dose is added to it. This stems from the fact that the Dose is the owning side of the relationship between itself and its Prescription. In the database, the row that represents the Dose will have a foreign key pointing to the Prescription, but the row that represents the Prescription will have no pointers to any of its Doses. The problem is remedied by guarding the Prescription with an optimistic write lock that forces an update to the Prescription’s row (to be specific: its version field) when a new Dose is inserted.

这篇关于JPA:执行间接插入时锁定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆