EBean is not doing updates! it's trying to do inserts and failing
Asked Answered
A

2

4

i have a simple User model like this:

package models;

import java.util.*;
import javax.persistence.*;
import play.db.ebean.*;

@Entity
public class User extends Model {

        @Id
    public Long id;
    public String userName;
    public String email;
    public String workPlace;
    public Date birthDate;
    @Version
    public Long version;

    public static Finder<Long,User> find = new Finder<Long,User>(
        Long.class, User.class
    );

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getWorkPlace() {
        return workPlace;
    }

    public void setWorkPlace(String workPlace) {
        this.workPlace = workPlace;
    }

    public Date getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }

    public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
        this.version = version;
    }
}

in MySql there is already a record with id=1, version=1

i create a new model with the same id+version, change the userName

then i try the save() method

it fails with:

javax.persistence.PersistenceException: ERROR executing DML bindLog[] error[Duplicate entry '1' for key 'PRIMARY']
        at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:116) ~[ebean.jar:na]
        at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.insert(DmlBeanPersister.java:76) ~[ebean.jar:na]
        at com.avaje.ebeaninternal.server.persist.DefaultPersistExecute.executeInsertBean(DefaultPersistExecute.java:91) ~[ebean.jar:na]
        at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeNow(PersistRequestBean.java:527) ~[ebean.jar:na]
        at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeOrQueue(PersistRequestBean.java:557) ~[ebean.jar:na]
        at com.avaje.ebeaninternal.server.persist.DefaultPersister.insert(DefaultPersister.java:404) ~[ebean.jar:na]
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'PRIMARY'
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) [na:1.7.0_05]
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) [na:1.7.0_05]
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) [na:1.7.0_05]
        at java.lang.reflect.Constructor.newInstance(Unknown Source) [na:1.7.0_05]
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:411) ~[mysql-connector-java-5.1.18.jar:na]
        at com.mysql.jdbc.Util.getInstance(Util.java:386) ~[mysql-connector-java-5.1.18.jar:na]

what am i doing wrong? inserting a new record works. so it looks to me like some ebean bug where its not recognizing that it should do an update. or is it some config that i forgot somewhere?

thanks

Adult answered 28/7, 2012 at 9:15 Comment(2)
Please show the code where you call the save() method.Auberon
why don't you call update()Culley
I
10

I think it's related to the bytecode enhancement that both play and ebean performs.

I had weird issues similar to yours when I mixed public fields with getters and setters. Sometimes values weren't set when using the field directly:

user.id = 1L;   // didn't work
user.setId(1L); // ok

This felt like a potential death trap so I decided to be consistent and only use public fields. The drawback was that ebeans autofetch stopped working, but on the other hand that forced me to tune my queries with ebean fetches.

This excellent post by Timo shed some light on how ebean and play performs their bytecode magic.

Iliac answered 3/9, 2012 at 12:21 Comment(2)
Had a similar issue, trying to run EBean tests in eclipse, it will complain since the class is un-enhanced (saying there's no setters/getters). So I added them, but adding them caused some EBean tests to fail when you run them from the play command line. Kind of a drag you can't run the unit tests in eclipse.Illuminometer
I just had a very similar issue, pretty strange behaviour. This is the reason I love Springs JdbcTemplate its all out in the open. Really helpedTea
E
5

If you have a self-created object and call save(), then Ebean makes an insert statement as SQL. If you call update() it will make an update statement as SQL.

There is no logic in Ebean itself that would make an additional query to DB just to check, if there happens to be a row with similar primary key in the DB, and if so use an update statement otherwise an insert statement. That would simply be inefficient behaviour on its part, as usually you'll know which one to make and thus don't want to execute two SQL statements, just one. In that case you actually have to tell it to use an update, not to try inserting the row again.

// Create user.
User user1 = new User();
user1.setId(1L);
user1.setVersion(1L);
user1.setUserName("foo");
user1.setWorkPlace("bar");
user1.setEmail("[email protected]");
user1.save();

// Change details later and update DB.
User user2 = new User();
user2.setId(1L);
user2.setVersion(1L);
user2.setUserName("scooby_doo");
user2.save(); // This will crash.
user2.update(); // This will work.

However, if you fetch the object from DB, then calling save() on it will use an update statement in SQL, since Ebean knows its state is not new rather than existing.

// Find user (and let Ebean know its state as an existing row).
User user3 = User.find.byId(1L);

// Now you can change details and save without issues.
user3.setEmail("[email protected]");
user3.setVersion(2L);
user3.save(); // This will work.

See Ebean documentation.

Electroshock answered 28/10, 2013 at 1:29 Comment(8)
Thank you for this! It seemed to me that the behaviour of .save() was inconsistent (eg. one time .save() will UPDATE, the next it will attempt to INSERT), but perhaps I was just manually deleting data without realizing it. Anyway, this solution works fine! In my case, I was able to just test to see if the item has an id property set, and if it does, I know it pre-exists, so I can call .update(), otherwise I call .save(). That saves me a DB call.Eighty
Hmmm. Issues like these make EBean awful to use with Scala/Play.Kwang
@Ashesh: So you would rather Ebean did extra SQL queries just to find out whether it should insert or update on a save()? That would be just poor performance.Electroshock
@Electroshock that isn't what I meant. What I'm trying to achieve is a rather simple unidirectional – @OneToOne with Ebean which fails because cascading tries to re-insert the same ID on the child row and results in a SQL exception. I've wasted one full day on that.Kwang
@Ashesh: That just begs the question why use cascade in the first place, but comments really aren't the place for a lengthy discussion that is off-topic for this question.Electroshock
@Electroshock where would you suggest we discuss this?Kwang
@Ashesh: Obvious answer is make a new question that describes your issue or then in chat.Electroshock
@Electroshock here goes then: #36943109Kwang

© 2022 - 2024 — McMap. All rights reserved.