JPA: Sperren beim Durchführen einer indirekten Einfügung

Ich schreibe eine Software, die den Medikamentenverbrauch verfolgt. Ich benutze JPA, um mit der Datenbank zu interagieren. Mein Modell besteht aus zwei Einheiten: aPrescription und einDose. JederPrescription hat eine Sammlung vonDose Instanzen, die die dem Patienten im Rahmen dieser Verschreibung verabreichten Dosen wie folgt darstellen:

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 Dose kann nur im Kontext eines @ existierPrescription und damit einDose wird indirekt in die Datenbank eingefügt, indem es der Dosisensammlung des verschreibungspflichtigen Arzneimittels hinzugefügt wird:

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) kann gleichzeitig aufgerufen werden. Bei der Entscheidung, ob eine Dosis verfügbar ist, überprüft die Geschäftslogik die Dosisensammlung des Rezepts. Die Transaktion sollte fehlschlagen, wenn diese Sammlung gleichzeitig geändert wird (z. B. durch gleichzeitigen Aufruf vonaddDose(long)). Ich verwende dasLockModeType.OPTIMISTIC_FORCE_INCREMENT, um dies zu erreichen (anstatt eine Tabellensperre für die DOSE-Tabelle zu erhalten). ImPro JPA 2 von Keith und Schincariol Ich habe gelesen, dass:

Die Schreibsperre garantiert alles, was die optimistische Lesesperre bewirkt, verpflichtet sich jedoch auch, das Versionsfeld in der Transaktion zu erhöhen, unabhängig davon, ob ein Benutzer die Entität aktualisiert hat oder nicht. [...] Der gängige Fall für die Verwendung von OPTIMISTIC_FORCE_INCREMENT ist die Gewährleistung der Konsistenz über Entitätsbeziehungsänderungen hinweg (häufig handelt es sich um Eins-zu-Viele-Beziehungen mit Zielfremdschlüsseln), wenn sich im Objektmodell die Entitätsbeziehungszeiger ändern, im Datenmodell jedoch Es werden keine Spalten in der Entitätstabelle geändert.

Ist mein Verständnis dieses Sperrmodus korrekt? Stellt meine aktuelle Strategie sicher, dass dasaddDose Die Transaktion schlägt fehl, wenn sich die Dosismenge der Verschreibung in irgendeiner Weise ändert (entweder Hinzufügen, Entfernen oder Aktualisieren einer Dosis in der Dosismenge).

Antworten auf die Frage(4)

Ihre Antwort auf die Frage