This is a bug in openjpa. JIRA-1371 is open to address this problem.
----- Original Message ---- From: Constantine Kulak <[email protected]> To: [email protected] Sent: Fri, October 30, 2009 12:54:19 AM Subject: Re: Insert is called instead of Update when merge() with complex IDs Hello! Neither of those, I just execute the program twice. So, it's not about the managed / detached entities. Fay Wang wrote: > The em.merge will return the merged entity. > > EntityA mergedEntity = em.merge(newEntity); > > During your second merge call, did you call > em.merge(mergedEntity) > or > em.merge(newEntity)? > > Fay > > > > ----- Original Message ---- > From: Constantine Kulak <[email protected]> > To: [email protected] > Sent: Thu, October 29, 2009 4:19:11 AM > Subject: Insert is called instead of Update when merge() with complex IDs > > Hello! > > I need some help in the following situation - completely stuck with it :( > Can you please point me in some direction? Anyway, thanks in advance! > OpenJPA version is 2.0.0-M3. > > So, I have three entities: > > Prognosis contains many PrognosisEntry, and PrognosisEntry references some > Stock. So, it's a many-to-many for Prognosis and Stock, having some > additional data at PrognosisEntry join table. Fields: > > Stock: > index (PK) > length > > Prognosis: > station (PK) > type (PK) > > PrognosisEntry: > prognosis.station (FK, PK) > prognosis.type (FK, PK) > stock.index (FK, PK) > localState > timestamp > > Relationship between Prognosis and PrognosisEntry is bidirectional (I really > need Prognosis.getEntries() method). All IDs are taken from the real world > and not generated because this data comes from the backend in XML and then > deserialized using JAXB (annotations are removed from the sources below to > simplify it). Then I do merge() and expect the data in the DB to be updated > with the fresh one. When I call merge() for the 1st time (against the empty > DB), all the necessary INSERTs are done. But when called for the 2nd time > with the same data (I expect no changes to the DB), the exception is thrown > because of the duplicate primary keys. Unexpectedly, it does INSERT instead > of UPDATE in this case. The problem is seems to be with the > PrognosisEntry-Stock relationship, because when I remove it (replacing > reference to Stock in the PrognosisEntry with some dummy ID field) it starts > working as expected (calls UPDATE). Exception and logs are given after the > sources below. > > ***** Source for Prognosis.java: > > @Entity(name = "Prognosis") > @Table(name = "PROGNOSIS") > @IdClass(Prognosis.PrognosisId.class) > @Inheritance(strategy = InheritanceType.JOINED) > public class Prognosis { > > protected List<PrognosisEntry> entries; > protected String station; > protected String type; > > @OneToMany(targetEntity = PrognosisEntry.class, cascade = > {CascadeType.MERGE}, mappedBy="prognosis", fetch=FetchType.EAGER) > public List<PrognosisEntry> getEntries() { > if (entries == null) { > entries = new ArrayList<PrognosisEntry>(); > } > return this.entries; > } > > public void setEntries(List<PrognosisEntry> entries) { > this.entries = entries; > } > > @Id > @Column(name = "STATION") > public String getStation() { > return station; > } > > public void setStation(String value) { > this.station = value; > } > > @Id > @Column(name = "TYPE_") > public String getType() { > return type; > } > > public void setType(String value) { > this.type = value; > } > > public boolean equals(Object object) { ... } > public int hashCode() { ... } > > public static class PrognosisId { > protected String station; > protected String type; > > public String getStation() { > return station; > } > > public void setStation(String value) { > this.station = value; > } > > public String getType() { > return type; > } > > public void setType(String value) { > this.type = value; > } > > public boolean equals(Object object) { ... } > public int hashCode() { ... } > } > } > > > ***** Source for PrognosisEntry.java: > > @Entity(name = "PrognosisEntry") > @Table(name = "PROGNOSISENTRY") > @Inheritance(strategy = InheritanceType.JOINED) > @IdClass(PrognosisEntry.PrognosisEntryId.class) > public class PrognosisEntry { > > protected String timestamp; > protected String localState; > protected Prognosis prognosis; > > protected Stock stock; > > @Id > @ManyToOne(targetEntity = Stock.class, cascade = { CascadeType.MERGE }, > fetch = FetchType.EAGER) > public Stock getStock() { > return stock; > } > > public void setStock(Stock stock) { > this.stock = stock; > } > > @Id > @ManyToOne(targetEntity = Prognosis.class, cascade = { CascadeType.MERGE > }, fetch = FetchType.EAGER) > public Prognosis getPrognosis() { > return prognosis; > } > > public void setPrognosis(Prognosis prognosis) { > this.prognosis = prognosis; > } > > @Column(name = "TIMESTAMP_", length = 255) > public String getTimestamp() { > return timestamp; > } > > public void setTimestamp(String value) { > this.timestamp = value; > } > > @Basic > @Column(name = "LOCALSTATE", length = 255) > public String getLocalState() { > return localState; > } > > public void setLocalState(String value) { > this.localState = value; > } > > public boolean equals(Object object) { ... } > public int hashCode() { ... } > > public static class PrognosisEntryId { > > protected Prognosis.PrognosisId prognosis; > protected String stock; > > public String getStock() { > return stock; > } > > public void setStock(String stock) { > this.stock = stock; > } > > public Prognosis.PrognosisId getPrognosis() { > return prognosis; > } > > public void setPrognosis(Prognosis.PrognosisId prognosis) { > this.prognosis = prognosis; > } > > public boolean equals(Object object) { ... } > public int hashCode() { ... } > } > } > > > ***** Source for Stock.java: > > @Entity(name = "Stock") > @Table(name = "STOCK") > @Inheritance(strategy = InheritanceType.JOINED) > public class Stock { > protected String index; > protected String length; > > @Id > @Column(name = "INDEX_") > public String getIndex() { > return index; > } > > public void setIndex(String value) { > this.index = value; > } > @Basic > @Column(name = "LENGTH_", length = 255) > public String getLength() { > return length; > } > > public void setLength(String value) { > this.length = value; > } > > public boolean equals(Object object) { ... } > public int hashCode() { ... } > } > > > ***** Generated DDL: > > CREATE TABLE PROGNOSIS (STATION VARCHAR(254) NOT NULL, TYPE_ VARCHAR(254) NOT > NULL, PRIMARY KEY (STATION, TYPE_)); > CREATE TABLE PROGNOSISENTRY (PROGNOSIS_STATION VARCHAR(254) NOT NULL, > PROGNOSIS_TYPE_ VARCHAR(254) NOT NULL, STOCK_INDEX_ VARCHAR(254) NOT NULL, > LOCALSTATE VARCHAR(254), TIMESTAMP_ VARCHAR(254), PRIMARY KEY > (PROGNOSIS_STATION, PROGNOSIS_TYPE_, STOCK_INDEX_)); > CREATE TABLE STOCK (INDEX_ VARCHAR(254) NOT NULL, LENGTH_ VARCHAR(254), > WEIGHT VARCHAR(254), PRIMARY KEY (INDEX_)); > ALTER TABLE PROGNOSISENTRY ADD FOREIGN KEY (PROGNOSIS_STATION, > PROGNOSIS_TYPE_) REFERENCES PROGNOSIS (STATION, TYPE_); > ALTER TABLE PROGNOSISENTRY ADD FOREIGN KEY (STOCK_INDEX_) REFERENCES STOCK > (INDEX_); > > > ***** Executed SQL and exception: > > 3063 TRACE [main] openjpa.jdbc.SQL - <t 1183336072, conn 1004551136> > executing prepstmnt 379983526 SELECT t0.STATION, t0.TYPE_ FROM PROGNOSIS t0 > WHERE t0.STATION = ? AND t0.TYPE_ = ? [params=(String) 1400, (String) IN] > 3063 TRACE [main] openjpa.jdbc.SQL - <t 1183336072, conn 1004551136> [0 ms] > spent > 3078 TRACE [main] openjpa.jdbc.JDBC - <t 1183336072, conn 1004551136> [0 > ms] close > 3125 TRACE [main] openjpa.jdbc.SQL - <t 1183336072, conn 1901752666> > executing prepstmnt 1663984430 SELECT t0.PROGNOSIS_STATION, > t0.PROGNOSIS_TYPE_, t0.STOCK_INDEX_, t0.LOCALSTATE, t1.INDEX_, t1.LENGTH_, > t1.WEIGHT, t0.TIMESTAMP_ FROM PROGNOSISENTRY t0 LEFT OUTER JOIN STOCK t1 ON > t0.STOCK_INDEX_ = t1.INDEX_ WHERE t0.PROGNOSIS_STATION = ? AND > t0.PROGNOSIS_TYPE_ = ? [params=(String) 1400, (String) IN] > 3156 TRACE [main] openjpa.jdbc.SQL - <t 1183336072, conn 1901752666> [31 > ms] spent > 3156 TRACE [main] openjpa.jdbc.JDBC - <t 1183336072, conn 1901752666> [0 > ms] close > 3203 TRACE [main] openjpa.jdbc.SQL - <t 1183336072, conn 1242843668> > executing prepstmnt 140118106 SELECT t0.LOCALSTATE, t1.STATION, t1.TYPE_, > t2.INDEX_, t2.LENGTH_, t2.WEIGHT, t0.TIMESTAMP_ FROM PROGNOSISENTRY t0 LEFT > OUTER JOIN PROGNOSIS t1 ON t0.PROGNOSIS_STATION = t1.STATION AND > t0.PROGNOSIS_TYPE_ = t1.TYPE_ LEFT OUTER JOIN STOCK t2 ON t0.STOCK_INDEX_ = > t2.INDEX_ WHERE t0.PROGNOSIS_STATION = ? AND t0.PROGNOSIS_TYPE_ = ? AND > t0.STOCK_INDEX_ IS NULL optimize for 1 row [params=(String) 1400, (String) > IN] > 3203 TRACE [main] openjpa.jdbc.SQL - <t 1183336072, conn 1242843668> [0 ms] > spent > 3219 WARN [main] openjpa.Runtime - Finder for "PrognosisEntry" is not > cachable. > 3219 TRACE [main] openjpa.Runtime - finder-invalidate > 3219 TRACE [main] openjpa.jdbc.JDBC - <t 1183336072, conn 1242843668> [0 > ms] close > 3281 TRACE [main] openjpa.jdbc.SQL - <t 1183336072, conn 391649112> > executing prepstmnt 317330154 SELECT t0.LENGTH_, t0.WEIGHT FROM STOCK t0 > WHERE t0.INDEX_ = ? [params=(String) 3] > 3281 TRACE [main] openjpa.jdbc.SQL - <t 1183336072, conn 391649112> [0 ms] > spent > 3281 TRACE [main] openjpa.jdbc.JDBC - <t 1183336072, conn 391649112> [0 ms] > close > 3344 TRACE [main] openjpa.jdbc.JDBC - The batch limit is set to 100. > 3344 TRACE [main] openjpa.jdbc.SQL - <t 1183336072, conn 1494898970> > executing prepstmnt 686827760 INSERT INTO PROGNOSISENTRY (PROGNOSIS_STATION, > PROGNOSIS_TYPE_, STOCK_INDEX_, LOCALSTATE, TIMESTAMP_) VALUES (?, ?, ?, ?, ?) > [params=(String) 1400, (String) IN, (String) 3, (null) null, (String) > 2009-12-09-17.34] > 3547 TRACE [main] openjpa.jdbc.SQL - <t 1183336072, conn 1494898970> [171 > ms] spent > > <openjpa-2.0.0-M3-r422266:822833 fatal store error> > org.apache.openjpa.util.StoreException: The transaction has been rolled back. > See the nested exceptions for details on the errors that occurred. > at > org.apache.openjpa.kernel.BrokerImpl.newFlushException(BrokerImpl.java:2249) > at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:2096) > at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:1994) > at > org.apache.openjpa.kernel.BrokerImpl.beforeCompletion(BrokerImpl.java:1912) > at > org.apache.openjpa.kernel.LocalManagedRuntime.commit(LocalManagedRuntime.java:81) > at org.apache.openjpa.kernel.BrokerImpl.commit(BrokerImpl.java:1436) > at > org.apache.openjpa.kernel.DelegatingBroker.commit(DelegatingBroker.java:895) > at > org.apache.openjpa.persistence.EntityManagerImpl.commit(EntityManagerImpl.java:557) > at test.persistParent(test.java:34) > at test.main(test.java:16) > Caused by: <openjpa-2.0.0-M3-r422266:822833 fatal store error> > org.apache.openjpa.util.ObjectExistsException: One or more values in the > INSERT statement, UPDATE statement, or foreign key update caused by a DELETE > statement are not valid because the primary key, unique constraint or unique > index identified by "1" constrains table "PROGNOSISENTRY" from having > duplicate values for the index key. > FailedObject: prepstmnt 686827760 INSERT INTO PROGNOSISENTRY > (PROGNOSIS_STATION, PROGNOSIS_TYPE_, STOCK_INDEX_, LOCALSTATE, TIMESTAMP_) > VALUES (?, ?, ?, ?, ?) > [org.apache.openjpa.jdbc.kernel.JDBCStoreManager$CancelPreparedStatement_] > at org.apache.openjpa.jdbc.sql.DBDictionary.narrow(DBDictionary.java:4575) > at > org.apache.openjpa.jdbc.sql.DBDictionary.newStoreException(DBDictionary.java:4543) > at > org.apache.openjpa.jdbc.sql.DB2Dictionary.newStoreException(DB2Dictionary.java:541) > ... -- Constantine Kulak wintermuteblog.blogspot.com
