OptimisticLockException with Ebean and Play Framework 2
Asked Answered
A

2

7

I am using Ebean with Play Framework 2 and sometimes it falls with OptimisticLockException of such kind:

play.core.ActionInvoker$$anonfun$receive$1$$anon$1: Execution exception [[OptimisticLockException: Data has changed. updated [0] rows sql[update manager set modification_time=?, session_id=?, expiration_date=? where id=? and rating=? and creation_time=? and modification_time=? and name=? and surname=? and login=? and password_hash=? and email=? and session_id=? and expiration_date=?] bind[null]]]

This happen when few actors start to access database.

So, Manager class is:

public class Manager extends Model {
@Getter @Setter
Long id;

@Getter @Setter
private String name;

@Getter @Setter
private String surname;

@Column(unique = true)
@Getter @Setter
private String login;

@Getter @Setter
private String passwordHash;

@Getter @Setter
private String email;

@Embedded
@Getter @Setter
private ManagerSession session;

@Getter
private Timestamp creationTime;

@Getter
private Timestamp modificationTime;

@Override
public void save() {
    this.creationTime       = new Timestamp(System.currentTimeMillis());
    this.modificationTime   = new Timestamp(System.currentTimeMillis());
    super.save();
}

@Override
public void update() {
    this.modificationTime   = new Timestamp(System.currentTimeMillis());
    super.update();
}

}

save() and update() hooks used instead @PrePersist annotations, because of Ebean doesn't support it. As I know @Version annotation allways brings Optimistic lock mode, so I start to use such trick. I know what Optimistick lock is, but how this situation should be solved, when many actors should modify same db record, where last modification wins?

Alienor answered 27/9, 2012 at 8:59 Comment(0)
S
15

Solution:

The problem: Saving a detached EBean model directly from the Play Form causes either OptimisticLockException, or when using @Version it causes NullpointerException.

Form<Venue> form = form(Venue.class).bindFromRequest();
form.get().update(id); // this causes the exception

The solution: The form should support supplying it an object from database, before binding from the request parameters. The parameters found in the request should then overwrite the relevant properties on the object. Perhaps call fill() before bindFromRequest():

Form<Venue> form = form(Venue.class).fill(Venue.find.byId(id)).bindFromRequest();
form.get().update(id);
Stipitate answered 28/10, 2012 at 13:50 Comment(2)
Yes, you right. But my trouble was not here. Problem was in @Version annotation deep in class hierarchy. Anyway, your answer is very useful for one who face such trouble for a first time. Let it be accepted.Alienor
How do you get id to call Venue.find.byId(id)) before form binding?Lizabethlizard
B
2

I resolved this OptmistLockException issue just passing the entity id to the controller when trying to update. To prevent user from changing values, the value was passed as a hidden field.

 <input type="hidden" name="id" value="@formVar(<identifierVariable>).value"/>

On the controller side, I check if the received form has errors. In negative case, I update the attached entity in the form.

 public static Result update() {
     Form<Entity> filledForm = form.bindFromRequest();
     if ( filledForm.hasErrors() ) {
        flashError("app.oh_snap", "app.change_things");
     } else {
        Entity entity = filledForm.get();
        entity.update();
     }
 }
Brevier answered 18/4, 2013 at 12:48 Comment(1)
Any clever user will be able to view and, most importantly, change the value of the hidden field from UI. This cannot be a good solution.Lizabethlizard

© 2022 - 2024 — McMap. All rights reserved.