Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 738
Or Gal Avatar asked Dec 06 '22 13:12

Or Gal


2 Answers

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.

like image 189
Joel S Avatar answered May 10 '23 23:05

Joel S


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.

like image 23
t0mppa Avatar answered May 10 '23 23:05

t0mppa