Please use the google group to ask questions - thanks.

by Ilya Skorik 03 Jan 20:50
PK save problems, some sentences

Good afternoon, Rob. Thanks for your remarkable library, I only have started to work with it. For me the following problem, here my code. At its execution I receive an PK error:

Edition edition = new Edition();
edition.setColor("FFFFFF");
edition.setTitle("test");
edition.setStitle("This is JPA test");
edition.setEnabled(true);

editionJPA.create(edition);
edition.setEnabled(false);
editionJPA.save(edition);

And such code works:

...

Edition item = ebeanServer.find(Edition.class, UUID.fromString("e86a1e4f-3d22-42af-ab9d-732e23f735b1"));
item.setEnabled(false);
ebeanServer.save(item);

I have a sentence, to add a method "update" which will generate exclusively "sql update" a code and a method "insertOrUpdate" which will check presence of the object and to save or refresh it.

--

And my second sentence to add a method "exist", which will check, whether there is a record in a DB by it PK and to return true or false boolean value.

Thank you!

06 Jan 21:45
by Rob

I don't understand the problem/example.

You are getting a PK exception - which line are you getting it on? It is not clear to me what the editionJPA object is... and what it's create() and save() methods do.

Can you provide more detail?

Thanks, Rob.

07 Jan 13:47
by Ilya Skorik

"editionJPA" is simple POJO object. The problem that I cannot create POJO object and transfers it to EbeanServer.save(java.lang.Object) method. EbeanServer considers that it is new record for which it is necessary to do insert, but it is necessary for me that has been made update.

I looked in source code, a choice between insert and update depends on variables which Ebean adds automatically, for example at obtaining of the object with a ebeanServer.find method. But I create POJO object passing ebeanServer, and in it there are no structures which adds ebean.

I will describe the typical scenario. I obtain the data from an web page through spring-mvc which automatically creates necessary POJO object (Edition) and makes its validation before send to DAO:

@RequestMapping(value = "/settings/editions/update/")
public String Edit(@Valid Edition edition,
BindingResult bindingResult, Model model) {

if (!hasErrors(bindingResult))
editionsService.update(edition);

jsonHelper.getModel(model, bindingResult);
return "jsonView";
}

This is EditionService.update:

public void update(Edition object) {
editionJPA.update(object);
}

And this is EditionJPA.update:

@Override
public void update(Edition object) {

String dml = "UPDATE inp_editions SET " +
"edition_enabled = :enabled, " +
"edition_color = :color, " +
"edition_title = :title, " +
"edition_stitle = :stitle, " +
"edition_description = :description, " +
"edition_updated = now()" +
"WHERE edition_uuid = :id";

SqlUpdate query = ebeanServer.createSqlUpdate(dml)
.setParameter("enabled", object.getEnabled())
.setParameter("color", object.getColor())
.setParameter("title", object.getTitle())
.setParameter("stitle", object.getStitle())
.setParameter("description", object.getDescription())
.setParameter("id", object.getUuid());

int rows = query.execute();
}

All that I want, it to reduce code EditionJPA.update to:

@Override
public void update(Edition object) {
ebeanServer.update(object);
}

As experiment I have added a update method by copy of a code of a save method, having made an unambiguous choice for update, all works fine. Only I do not wish to use own assemblage because of possible problems with compatibility.

11 Jan 00:23
by Rob

>> EbeanServer considers that it is new record for which it is necessary to do insert, but it is necessary for me that has been made update.

I think this is because you don't have a version property/column. That is... your bean is not enhanced etc (I call this a "vanilla" bean in my own terminology). For "vanilla" beans with a version property ... Ebean will check the value of the version property to determine if it is an insert or an update. However, your vanilla bean doesn't have a version property so Ebean just does a update.

Refer to DefaultPersister.saveVanilla(...)

Now, I understand you want to add an explicit ebeanServer.update() method ... but firstly I'd like to know if there is a reason not to have a version property - aka you are not getting any optimistic concurrency checking on your update, so I'd advise that you add a version property if at all possible.

Are you able to add a version property/column?

11 Jan 11:58
by Ilya Skorik

I develop the web application, in which 90 % of queries standard and very simple CRUD operations. I think that they do not know about "optimistic concurrency checking". Here the interface with base DAO for all CRUD objects:

public interface BasicDao {

public void create (T object);
public void update (T object);
public void delete (UUID id);

public void enable (UUID id);
public void disable (UUID id);

public Boolean exist (UUID id);

public List findAll ();

public T findById (UUID id);

}

Certainly I can add version propperty and I will without fail try to make it. This property should be saved in a DB?

11 Jan 15:12
by Ilya Skorik

1. I have made changes to my domain object:

@Column(insertable = false, updatable = false) private Timestamp created;

@Version private Timestamp updated;

The version (timestamp) forms in a database. But still I receive an error "ERROR: duplicate key value violates unique constraint "inp_editions_pkey"" Means ebean still considers that it needs to make insert query.

2. I have added property version:

private Timestamp version;

public Timestamp getVersion() {
return version;
}

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

It too has not helped.

11 Jan 15:21
by Ilya Skorik

1.1, I have made changes to my DaoImpl for "create" method:

object.setUpdated(new Timestamp((new java.util.Date()).getTime()));
ebeanServer.save(object);

And now I have this error:
"javax.persistence.OptimisticLockException: Data has changed. updated [0] rows"

11 Jan 15:35
by Ilya Skorik

I have understood, how it works. What to do update, the object should contain the version propperty and then the query will look as "update... where version =:version". But I do not want together with PK propperty move the version propperty through a web layer.

Therefore I vote for separate "update" method which as a matter of fact will be the designer of sql update query for object.class.

12 Jan 01:17
by Rob

Hmmm, I'm not convinced that the version property was added correctly. Can you show me your entire entity bean?

Specifically I'm looking to see if you added a version property like:

@Version
Timestamp lastUpdate;

public Timestamp getLastUpdate() {
return lastUpdate;
}

public void setLastUpdate(Timestamp lastUpdate) {
this.lastUpdate = lastUpdate;
}

Thanks, Rob.

12 Jan 01:21
by Rob

Also note that you (your application code) doesn't need to set the value for a @Version property - Ebean will do that.

Aka you don't need code like this:
>> object.setUpdated(new Timestamp((new java.util.Date()).getTime()));

Similarly if you use @CreatedTimestamp for a created timestamp Ebean will automatically set that value when a bean is created (and not include it in update statements).

15 Jan 22:33
by Ilya Skorik

This is my Domain object:

@Entity
public class Edition {

@Id
@NotNull
private UUID id;

@NotNull
private String color = "FFFFFF";

@NotNull
private String title;

@NotNull
private String stitle;

@NotNull
private String description = "";

@NotNull
private Boolean enabled = true;

@Column(insertable = false, updatable = false)
private Timestamp created;

@Version
private Timestamp updated;

// standart getters and setters...

-----

This is my DaoImpl class

public void update(Edition object) {
ebeanServer.save(object);
}

Where the Edition object is checked already up and is correct and valid.

This is error:
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "inp_editions_pkey"

This is query from log:

trans[1010], 01:26:33.624, Binding Insert [inp_editions] set[id=63c666d4-d8c1-41e1-b67d-27e4b262d7a3, color=000000, tit
le=blablabla, stitle=blablabla, description=, enabled=true, updated=2010-01-16 01:26:33.624, ]

Ebean continues to consider that it needs to make insert query.

15 Jan 22:34
by Ilya Skorik

With direct call update it would be much easier.

17 Jan 20:48
by Rob

With the version column... the second save should result in an update. I'll try and reproduce this.

Aka, we need to resolve when you are getting a second insert before we add an update() method.

18 Jan 01:30
by Ilya Skorik

Why the second insert? It is the first and unique call of a "save" method. Edition entity created in a program code, on the fly. Not from a database by ebean call.

I.e. I create Edition object (instead of I call it from a database), I assign to it an id and I wish to save (update) this in a database.

18 Jan 07:02
by Rob

I can't reproduce your behaviour:

package com.avaje.tests.model.basic;

import java.sql.Timestamp;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;

@Entity
@Table(name="e_basicver")
public class EBasicVer {
    
    @Id
    Integer id;
    
    String name;
    
    @Version
    Timestamp lastUpdate;
    
    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Timestamp getLastUpdate() {
        return lastUpdate;
    }

    public void setLastUpdate(Timestamp lastUpdate) {
        this.lastUpdate = lastUpdate;
    }

}
18 Jan 07:03
by Rob
package com.avaje.tests.basic;

import java.sql.Timestamp;

import junit.framework.Assert;
import junit.framework.TestCase;

import com.avaje.ebean.Ebean;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.tests.model.basic.EBasicVer;

public class TestIUDVanilla extends TestCase {

    public void test() {
        
        EBasicVer e0 = new EBasicVer();
        e0.setName("vanilla");
        
        Ebean.save(e0);
        
        boolean entity = (e0 instanceof EntityBean);
        Assert.assertTrue(!entity);
        
        Assert.assertNotNull(e0.getId());
        Assert.assertNotNull(e0.getLastUpdate());
        
        Timestamp lastUpdate0 = e0.getLastUpdate();
        
        e0.setName("modified");
        Ebean.save(e0);
        
        Timestamp lastUpdate1 = e0.getLastUpdate();
        Assert.assertNotNull(lastUpdate1);
        Assert.assertNotSame(lastUpdate0, lastUpdate1);
        
    }
}
18 Jan 07:03
by Rob

The transaction log:

trans[1003], 07:01:01.254, insert into e_basicver (id, name, last_update) values (?, ?, ?)
trans[1003], 07:01:01.257, Binding Insert [e_basicver] set[id=1, name=vanilla, lastUpdate=2010-01-18 07:01:01.256, ]
trans[1003], 07:01:01.283, Inserted [EBasicVer] [1]
trans[1004], 07:01:01.284, update e_basicver set name=?, last_update=? where id=? and last_update=?
trans[1004], 07:01:01.286, Binding Update [e_basicver] set[name=modified, lastUpdate=2010-01-18 07:01:01.285, ] where[id=1, lastUpdate=2010-01-18 07:01:01.256, ]
trans[1004], 07:01:01.287, Updated [EBasicVer] [1]

18 Jan 07:04
by Rob

Can you try the code above and/or produce a test case that shows your problem.

Thanks, Rob.

18 Jan 07:06
by Rob

Note that I have turned off enhancement... hence Assert.assertTrue(!entity); ... and the second save produces an update statement.

18 Jan 08:09
by Ilya Skorik

Rob, excuse me. I probably very badly explain the thought. It is a language problem.

Your test absolutely correctly works. My problem in other. I _do_not_ save a bean before update it. In my case the first strings of the test would look like:

EBasicVer e0 = new EBasicVer();
e0.setId(1);
e0.setName("vanilla");
Ebean.save(e0);

And in this case I need to make update query, instead of insert query.

It is actual for my web program. In which web interface I edit properties of the object, then I press a "save" button. Data are transferred to a server with get url server.com?id=1&name=la-la-la.

I certainly can do something like

EBasicVer object = ebean->find(EBasicVer.class, id), then modify object via get/set and then fo ebean->save(object);

but I would not like to do superfluous find query. I trust the received data and I wish to make at once save, i.e. to make update query.

18 Jan 10:02
by Rob
public void test() {
        
        EBasicVer e0 = new EBasicVer();
        e0.setName("vanilla");
        
        Ebean.save(e0);
      
        // only use the below test when not using enhancement
        boolean entity = (e0 instanceof EntityBean);
        Assert.assertTrue(!entity);
        
        Assert.assertNotNull(e0.getId());
        Assert.assertNotNull(e0.getLastUpdate());
        
        Timestamp lastUpdate0 = e0.getLastUpdate();
        
        e0.setName("modified");
        Ebean.save(e0);
        
        Timestamp lastUpdate1 = e0.getLastUpdate();
        Assert.assertNotNull(lastUpdate1);
        Assert.assertNotSame(lastUpdate0, lastUpdate1);
        
        // This does an update because it has a lastUpdate/version
        // property set ... AND it will use optimistic concurrency checking
        EBasicVer e2 = new EBasicVer();
        e2.setName("forcedUpdate");
        e2.setId(e0.getId());
        e2.setLastUpdate(lastUpdate1);
        
        Ebean.save(e2);
        
    }
18 Jan 10:03
by Rob

trans[1003], 10:00:43.259, insert into e_basicver (id, name, last_update) values (?, ?, ?)
trans[1003], 10:00:43.262, Binding Insert [e_basicver] set[id=1, name=vanilla, lastUpdate=2010-01-18 10:00:43.261, ]
trans[1003], 10:00:43.263, Inserted [EBasicVer] [1]
trans[1004], 10:00:43.265, update e_basicver set name=?, last_update=? where id=? and last_update=?
trans[1004], 10:00:43.266, Binding Update [e_basicver] set[name=modified, lastUpdate=2010-01-18 10:00:43.265, ] where[id=1, lastUpdate=2010-01-18 10:00:43.261, ]
trans[1004], 10:00:43.267, Updated [EBasicVer] [1]
trans[1005], 10:00:43.268, update e_basicver set name=?, last_update=? where id=? and last_update=?
trans[1005], 10:00:43.268, Binding Update [e_basicver] set[name=forcedUpdate, lastUpdate=2010-01-18 10:00:43.268, ] where[id=1, lastUpdate=2010-01-18 10:00:43.265, ]
trans[1005], 10:00:43.268, Updated [EBasicVer] [1]

18 Jan 10:05
by Rob

Aka... it will do an update (for a "vanilla/non-enhanced bean") when the version property has a value set. Note that IMO this value SHOULD be set ... as that is what enables the Optimistic Concurrency checking (aka to avoid lost updates).

Does that make sense?

18 Jan 20:03
by Rob

Ok, been doing a bit more thinking ... and there is more to this.

Specifically when we only want to update part of a bean (effectively a partial object or thinking said way... when only some of the properties of the bean exist in your stateless web page).

I'll add some more details here about that scenario (which I'd expect to be fairly common).

18 Jan 20:05
by Rob

Also to be explicit... in my last example the e2 instance is only saved once... and results in an update.

19 Jan 20:05
by Rob

Ok, the situation is you have a stateless webapp and you want to effectively do an update.

The options are to use Ebean.createUpdate(...), Ebean.createSqlUpdate(...), or when you are using dynamic subclasses (aka not enhancement) you can use code like the above.

The problem with the code above (which relies on the existance of the version property) is that there is an underlying assumption that the bean is fully populated.

That is, any property on the bean which is left null will result in it being set to null via the update. You can get around this using EbeanServer.createEntity(...) and BeanState.setLoaded(...) but that is relatively advanced and I would expect people not to be keen on that.

The result is that I'm looking to implement a Ebean.update(...) and EbeanServer.update(...) methods which will handle partial objects and choose the appropriate concurrency mode based on the existance of a version property value.

20 Jan 08:24
by Rob

For ENH 198 ... added an update() method to Ebean and EbeanServer.

That now makes it much easier to do this. The main thing to note is that by default only the non-null properties are included in the update - the thinking here is that otherwise adding a property to the model would effectively break existing updates.

Create a New Topic

Title:
Body:
 
Introduction User Guide (pdf) Install/Configure Public JavaDoc Whitepapers
General Database Specific Byte Code Deployment Annotations Features
Top Bugs Top Enhancements
woResponse