tags:

views:

26

answers:

2

I have the following model

@Entity
class Element {
    @Id
    int id;

    @Version
    int version;

    @ManyToOne
    Type type;
}

@Entity
class Type {
    @Id
    int id;

    @Version
    int version;

    @OneToMany(mappedBy="type")
    Collection<Element> elements;

    @Basic(optional=false)
    boolean disabled;
}

and would like to allow Type.disabled = true only if Type.elements is empty. Is there a way to do it atomically?

I would like to prevent an insertion of an Element in a transaction while the corresponding Type is being disabled by an other transaction.

Update: sorry I didn't make myself clear. I'm not asking how to trigger the check, but how to prevent a sequence like this:

Transaction 1 checks that Type.elements is empty
Transaction 2 checks that Type.disabled = false
Transaction 1 updates Type and sets disabled = true
Transaction 2 persists a new Element
Transaction 2 commits
Transaction 1 commits

I then have a situation where Type.elements is not empty and Type.disabled = true (broken invariant). How can I avoid this situation? In native SQL, I would use pessimistic locking, but JPA 1.0 does not support this. Can the problem be solved using optimistic locking?

+2  A: 

I would like to prevent an insertion of an Element in a transaction while the corresponding Type is being disabled by an other transaction.

I would use the Bean Validation API (JSR 303) and a custom constraint for this purpose. If you are not familiar with Bean Validation, I suggest reading the following entries:

Pascal Thivent
A: 

while Bean Validation as suggested by Pascal is more elegant, a quick and easy solution would be entity lifecycle listener methods inside the entity class:

@PrePersist @PreUpdate
protected void validate(){
    if(this.disabled && !this.elements.isEmpty()){
            throw new IllegalArgumentException(
                    "Only types without elements may be disabled");
    }
}

or better yet, have an Assert class (as in JUnit or Spring that encapsulates the exception throwing):

@PrePersist @PreUpdate
protected void validate(){
    Assert.same( // but this implies a bi-directional constraint
            this.disabled, this.elements.isEmpty(),
            "Only types without elements may be disabled and vice-versa"
    );
}
seanizer