javax.persistence.OptimisticLockException: Data has changed. updated [0] rows

3,162 views
Skip to first unread message

Thomas Zangl

unread,
Mar 28, 2011, 3:23:45 AM3/28/11
to Ebean ORM
Hi!

I got the following exception while updating a specific row:

2011-03-27 18:48:58,489 [main] ERROR xxx - Got exception during saving
bean: Data has changed. updated [0] rows
javax.persistence.OptimisticLockException: Data has changed. updated
[0] rows
at
com.avaje.ebeaninternal.server.core.PersistRequestBean.checkRowCount(PersistRequestBean.java:
478)
at
com.avaje.ebeaninternal.server.persist.dml.UpdateHandler.execute(UpdateHandler.java:
104)
at
com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:
104)
at
com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.update(DmlBeanPersister.java:
84)
at
com.avaje.ebeaninternal.server.persist.DefaultPersistExecute.executeUpdateBean(DefaultPersistExecute.java:
121)
at
com.avaje.ebeaninternal.server.core.PersistRequestBean.executeNow(PersistRequestBean.java:
418)
at
com.avaje.ebeaninternal.server.core.PersistRequestBean.executeOrQueue(PersistRequestBean.java:
444)
at
com.avaje.ebeaninternal.server.persist.DefaultPersister.update(DefaultPersister.java:
394)
at
com.avaje.ebeaninternal.server.persist.DefaultPersister.saveEnhanced(DefaultPersister.java:
314)
at
com.avaje.ebeaninternal.server.persist.DefaultPersister.saveRecurse(DefaultPersister.java:
286)
at
com.avaje.ebeaninternal.server.persist.DefaultPersister.save(DefaultPersister.java:
270)
at
com.avaje.ebeaninternal.server.core.DefaultServer.save(DefaultServer.java:
1477)
at
com.avaje.ebeaninternal.server.core.DefaultServer.save(DefaultServer.java:
1467)
at com.avaje.ebean.Ebean.save(Ebean.java:535)

The funny thing is, that I just changed a date field, did
Ebean.save(myBean), and then I get the exception (above).
It only happens with ONE, SPECIFIC row (all other rows in the table
are fine!). Using TOAD (a SQL IDE) I was able to send an update stmt
for this row...

DB is Oracle 10g, JDK 6, ..

Any ideas? Cheers, Tom

Rob Bygrave

unread,
Mar 28, 2011, 3:32:01 AM3/28/11
to eb...@googlegroups.com
What is the Oracle DB type (Timestamp or Date - different precision).
What is the Java type used?

The Ebean transaction log will show you the update statement and bind values used - but as a guess it is possibly a precision issue.

Thomas Zangl

unread,
Mar 28, 2011, 3:45:40 AM3/28/11
to Ebean ORM
Hi!

Thanks for the fast reply!

The log says:
INFO: Trans[1009] update RESTORE_WL set DUE_DATE=?, MOD_DAT=?,
MOD_USR=? where id=? and DUE_DATE=? and MOD_DAT=? and MOD_USR=?
27.03.2011 18:48:56
com.avaje.ebeaninternal.server.transaction.log.JuliTransactionLogger
log
INFO: Trans[1009] Binding Update [RESTORE_WL] set[dueDate=Sat Mar 26
04:59:00 CET 2011, modDat=Sun Mar 27 18:48:55 CEST 2011,
modUsr=REST, ] where[id=3370, dueDate=Sun Mar 27 03:59:00 CEST 2011,
modDat=Sun Mar 27 11:54:56 CEST 2011, modUsr=RDER, ]

Any ideas?

Cheers, Tom
On 28 Mrz., 09:32, Rob Bygrave <robin.bygr...@gmail.com> wrote:
> What is the Oracle DB type (Timestamp or Date - different precision).
> What is the Java type used?
>
> The Ebean transaction log will show you the update statement and bind values
> used - but as a guess it is possibly a precision issue.
>

Thomas Zangl

unread,
Mar 28, 2011, 4:21:21 AM3/28/11
to Ebean ORM
Hi,

another thing I noticed: why does Ebean use some random fields to
identify the row for update instead of ONLY the PK (field name = ID in
this case)? As you can see, MOD_DAT was also used...

Maybe thats the reason why the update didn't find the row?

Cheers, Tom

Rob Bygrave

unread,
Mar 28, 2011, 4:34:46 AM3/28/11
to eb...@googlegroups.com, Thomas Zangl
So again, you do need to answer those first questions which where ...

1) What is the Oracle DB type for these columns - specifically is it TIMESTAMP or is it DATE because they have different precision and that is likely to be important.

2) What Java type you are using for these columns (java.util.Date, sql.Date, sql.Timestamp , joda ...)

>> update instead of ONLY the PK

That is for optimistic concurrency checking. So yeah, it would help to know which property (if any) have a @Version annotation.

Thomas Zangl

unread,
Mar 28, 2011, 4:53:39 AM3/28/11
to Ebean ORM
Hi!

> 1) What is the Oracle DB type for these columns - specifically is it
> TIMESTAMP or is it DATE because they have different precision and that is
> likely to be important.

DUE_DATE DATE NOT NULL,

> 2) What Java type you are using for these columns (java.util.Date, sql.Date,
> sql.Timestamp , joda ...)

import java.util.Date;
[...]
@Temporal(TemporalType.DATE)
@Column(name = "DUE_DATE", nullable = false)
private Date dueDate;

> >> update instead of ONLY the PK
>
> That is for optimistic concurrency checking. So yeah, it would help to know
> which property (if any) have a @Version annotation.

I think the source of the POJO would be a good base for further
discussions:

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

/**
* The persistent class for the MAS_RESTORE_WL database table.
*
*/
@Entity
@Table(name = "MAS_RESTORE_WL")
public class MasRestoreWl implements Serializable {
private static final long serialVersionUID = 1L;

@Id
@SequenceGenerator(name = "MAS_RESTORE_WL_ID_GENERATOR",
sequenceName = "MAS_RESTORE_WL_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"MAS_RESTORE_WL_ID_GENERATOR")
@Column(unique = true, nullable = false, precision = 15)
private long id;

@Column(name = "COPY_ID", length = 45)
private String copyId;

@Temporal(TemporalType.DATE)
@Column(name = "CRE_DAT", nullable = false)
private Date creDat;

@Column(name = "CRE_USR", nullable = false, length = 90)
private String creUsr;

@Column(name = "DIR_NAME", nullable = false, length = 384)
private String dirName;

@Column(name = "IMAGE_TYPE", nullable = false, length = 6)
private String imageType;

@Column(precision = 18)
private long imagesize;

@Column(nullable = false, length = 3)
private String dochecksum;

@Temporal(TemporalType.DATE)
@Column(name = "DUE_DATE", nullable = false)
private Date dueDate;

@Column(length = 450)
private String filename;

@Column(name = "FROM_LOC", length = 750)
private String fromLoc;

@Column(nullable = false, length = 30)
private String grp;

@Column(length = 150)
private String location;

@Column(name = "MAS_ORDERNR", precision = 10)
private Integer masOrdernr;

@Temporal(TemporalType.DATE)
@Column(name = "MOD_DAT")
private Date modDat;

@Column(name = "MOD_USR", length = 90)
private String modUsr;

@Column(name = "OFFERING_ID", length = 45)
private String offeringId;

@Column(length = 450)
private String prodgrp2;

@Column(precision = 3)
private Integer progress;

@Column(name = "REF_KEY1", length = 750)
private String refKey1;

@Column(name = "REF_KEY2", length = 750)
private String refKey2;

@Column(name = "REF_KEY3", precision = 6)
private Integer refKey3;

@Column(length = 4000)
private String remark;

@Column(length = 60)
private String source;

@Column(length = 9)
private String status;

@Column(name = "TO_LOC", nullable = false, length = 750)
private String toLoc;

@Temporal(TemporalType.DATE)
@Column(name = "TRANSFER_END")
private Date transferEnd;

@Temporal(TemporalType.DATE)
@Column(name = "TRANSFER_START")
private Date transferStart;

@Column(name = "TSM_ID", precision = 15)
private Integer tsmId;

public MasRestoreWl() {
}

public MasRestoreWl(String dirName, String toLoc, String imageType,
Date dueDate) {
this.dirName = dirName;
this.toLoc = toLoc;
this.imageType = imageType;
this.dochecksum = "Y";
this.creDat = new Date();
this.dueDate = dueDate;
this.status = "0";
}

public long getId() {
return this.id;
}

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

public String getImageType() {
return this.imageType;
}

public void setImageType(String imageType) {
this.imageType = imageType;
}

public String getCopyId() {
return this.copyId;
}

public void setCopyId(String copyId) {
this.copyId = copyId;
}

public Date getCreDat() {
return this.creDat;
}

public void setCreDat(Date creDat) {
this.creDat = creDat;
}

public String getCreUsr() {
return this.creUsr;
}

public void setCreUsr(String creUsr) {
this.creUsr = creUsr;
}

public String getDirName() {
return this.dirName;
}

public void setDirName(String dirName) {
this.dirName = dirName;
}

public String getDochecksum() {
return this.dochecksum;
}

public void setDochecksum(String dochecksum) {
this.dochecksum = dochecksum;
}

public Date getDueDate() {
return this.dueDate;
}

public void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}

public String getFilename() {
return this.filename;
}

public void setFilename(String filename) {
this.filename = filename;
}

public String getFromLoc() {
return this.fromLoc;
}

public void setFromLoc(String fromLoc) {
this.fromLoc = fromLoc;
}

public String getGrp() {
return this.grp;
}

public void setGrp(String grp) {
this.grp = grp;
}

public long getImageSize() {
return this.imagesize;
}

public void setImageSize(long imagesize) {
this.imagesize = imagesize;
}

public String getLocation() {
return this.location;
}

public void setLocation(String location) {
this.location = location;
}

public Integer getMasOrdernr() {
return this.masOrdernr;
}

public void setMasOrdernr(Integer masOrdernr) {
this.masOrdernr = masOrdernr;
}

public Date getModDat() {
return this.modDat;
}

public void setModDat(Date modDat) {
this.modDat = modDat;
}

public String getModUsr() {
return this.modUsr;
}

public void setModUsr(String modUsr) {
this.modUsr = modUsr;
}

public String getOfferingId() {
return this.offeringId;
}

public void setOfferingId(String offeringId) {
this.offeringId = offeringId;
}

public String getProdgrp2() {
return this.prodgrp2;
}

public void setProdgrp2(String prodgrp2) {
this.prodgrp2 = prodgrp2;
}

public Integer getProgress() {
return this.progress;
}

public void setProgress(Integer progress) {
this.progress = progress;
}

public String getRefKey1() {
return this.refKey1;
}

public void setRefKey1(String refKey1) {
this.refKey1 = refKey1;
}

public String getRefKey2() {
return this.refKey2;
}

public void setRefKey2(String refKey2) {
this.refKey2 = refKey2;
}

public Integer getRefKey3() {
return this.refKey3;
}

public void setRefKey3(Integer refKey3) {
this.refKey3 = refKey3;
}

public String getRemark() {
return this.remark;
}

public void setRemark(String remark) {
this.remark = remark;
}

public String getSource() {
return this.source;
}

public void setSource(String source) {
this.source = source;
}

public String getStatus() {
return this.status;
}

public void setStatus(String status) {
this.status = status;
}

public String getToLoc() {
return this.toLoc;
}

public void setToLoc(String toLoc) {
this.toLoc = toLoc;
}

public Date getTransferEnd() {
return this.transferEnd;
}

public void setTransferEnd(Date transferEnd) {
this.transferEnd = transferEnd;
}

public Date getTransferStart() {
return this.transferStart;
}

public void setTransferStart(Date transferStart) {
this.transferStart = transferStart;
}

public Integer getTsmId() {
return this.tsmId;
}

public void setTsmId(Integer tsmId) {
this.tsmId = tsmId;
}

}

Cheers, Tom

edge

unread,
Mar 28, 2011, 5:01:19 AM3/28/11
to Ebean ORM
It looks like you don't have an @Version property - so Ebean is using
all the old properties to establish the version when updating.
Add a version column and you won't have this problem anymore something
like

@Version
private int version

Thomas Zangl

unread,
Mar 28, 2011, 6:19:42 AM3/28/11
to Ebean ORM
Is it enough to have it in the bean only or does it need to be a
database column as well?

Anyway, its a strange bug and it might happen due to DST change this
weekend.

Cheers, Tom

edge

unread,
Mar 28, 2011, 6:21:56 AM3/28/11
to Ebean ORM
of course it needs to be in the db otherwise you'll get other errors
and it won't work
> > >     return...
>
> read more »

Thomas Zangl

unread,
Mar 28, 2011, 6:23:28 AM3/28/11
to Ebean ORM
Well then its a no-go. Anyway, its a work-around for a bug in my
eyes :-)

Cheers, Tom
> ...
>
> Erfahren Sie mehr »

edge

unread,
Mar 28, 2011, 6:57:46 AM3/28/11
to Ebean ORM
well sorry to disagree but it's not a bug - you basically have no
concurrency protection in you model without a reliable @Version
property.
We're guessing it's a precision problem (Date v Timestamp) that is
causing the problem with the default implementation but we can't say
from here

Anyway why don't you use the property
private Date modDat;
as your version column? That would make sense as I'm assuming this
gets updated every time a change is made - hence it can be used as a
version.
> > > > >   }...
>
> read more »

Thomas Zangl

unread,
Mar 28, 2011, 7:02:30 AM3/28/11
to Ebean ORM
Hi,

then please answer my question: why does it work since months and on
Sunday it crashed on ONE specific record? (reproducable)
Why should it be a version issue affecting only one simple row? (And
working for 1000s of others)

Cheers, Tom
> ...
>
> Erfahren Sie mehr »

edge

unread,
Mar 28, 2011, 7:40:25 AM3/28/11
to Ebean ORM
Thomas,

it's reproducible today then it doesn't have anything to do with DST I
think (even though it looks suspicious). Is that correct?
I don't know why it worked for months and suddenly you get this error
on one row - the only thing we can think of is the precision issue
that Rob mentioned. There are no triggers in the background doing
anything unusual? Was the row loaded before the DST change and updated
afterwards?

But... I would recommend using the modDat as your version column as
this will narrow the problem and will remove all the unnecessary where
clauses when updating.
Can you try that and see if it helps?

Otherwise we'll need a test case to reproduce this problem so as we
can take a look at it.

Cheers
Eddie
> > > > > > >   public void setGrp(String grp) {...
>
> read more »
Reply all
Reply to author
Forward
0 new messages