|
|
|
1
2
|
|
egoosen
|
(This post was updated on )
I can't get cascading persist working on my entities which have the following mappings.
TblScmpdt.java //Parent @OneToMany(fetch = FetchType.LAZY,mappedBy="tblScmpdt",cascade={CascadeType.MERGE,CascadeType.REMOVE}) private Collection<TblPdtbnf> tblPdtbnfs = new ArrayList<TblPdtbnf>(); TblPdtbnf.java //Child @Id @Column(name = "PDTBNF_ID",nullable=false) private Integer pdtbnfId; //Foreign key to TblPdtbnfcde.java @Id @Column(name = "SCMPDT_ID",nullable=false) private Integer scmpdtId; //Foreign key to TblScmpdt.java @ManyToOne(fetch = FetchType.LAZY,cascade=CascadeType.MERGE) @JoinColumn(name = "SCMPDT_ID",referencedColumnName="SCMPDT_ID") private TblScmpdt tblScmpdt; -------------------------------------- Here's the test code: em.getTransaction().begin(); TblScmpdt tblScmpdt = new TblScmpdt(); tblScmpdt.setAdmsysCde("EBSTA"); tblScmpdt.setFndCde("1526"); tblScmpdt.setGccCde("A1526"); tblScmpdt.setPflcmnDte(new Date()); TblPdtcde tblPdtcde = em.getReference(TblPdtcde.class, new Integer(121)); TblPdtbnfcde tblPdtbnfcde = em.getReference(TblPdtbnfcde.class, new Integer(13)); tblScmpdt.setPdtcdeId(tblPdtcde.getPdtcdeId()); TblPdtbnf tblPdtbnf = new TblPdtbnf(); tblPdtbnf.setCmnDte(new Date()); tblPdtbnf.setPdtbnfId(tblPdtbnfcde.getPdtbnfId()); tblPdtbnf.setTblScmpdt(tblScmpdt); tblScmpdt.addTblPdtbnf(tblPdtbnf); // tblScmpdt.getTblPdtbnfs().add(tblPdtbnf); tblScmpdt = em.merge(tblScmpdt); em.getTransaction().commit(); ---------------------------------- The exception: Caused by: <openjpa-1.0.0-r420667:568756 fatal user error> org.apache.openjpa.persistence.InvalidStateException: Attempt to set column "TBL_PDTBNF.SCMPDT_ID" to two different values: (null)"null", (class java.lang.Integer)"700" This can occur when you fail to set both sides of a two-sided relation between objects, or when you map different fields to the same column, but you do not keep the values of these fields in synch. at org.apache.openjpa.jdbc.sql.PrimaryRow.setObject(PrimaryRow.java:338) at org.apache.openjpa.jdbc.sql.RowImpl.flushJoinValues(RowImpl.java:289) at org.apache.openjpa.jdbc.sql.RowImpl.flushForeignKey(RowImpl.java:222) at org.apache.openjpa.jdbc.sql.RowImpl.setForeignKey(RowImpl.java:197) at org.apache.openjpa.jdbc.sql.PrimaryRow.setForeignKey(PrimaryRow.java:172) at org.apache.openjpa.jdbc.meta.ValueMappingImpl.setForeignKey(ValueMappingImpl.java:317) at org.apache.openjpa.jdbc.meta.FieldMapping.setForeignKey(FieldMapping.java:966) at org.apache.openjpa.jdbc.meta.strats.RelationFieldStrategy.insert(RelationFieldStrategy.java:207) at org.apache.openjpa.jdbc.meta.FieldMapping.insert(FieldMapping.java:555) at org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.insert(AbstractUpdateManager.java:203) at org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.populateRowManager(AbstractUpdateManager.java:145) at org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:85) at org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:72) at org.apache.openjpa.jdbc.kernel.JDBCStoreManager.flush(JDBCStoreManager.java:514) at org.apache.openjpa.kernel.DelegatingStoreManager.flush(DelegatingStoreManager.java:130) at org.apache.openjpa.datacache.DataCacheStoreManager.flush(DataCacheStoreManager.java:544) at org.apache.openjpa.kernel.DelegatingStoreManager.flush(DelegatingStoreManager.java:130) ... 9 more |
||||
|
egoosen
|
(This post was updated on )
I commented out the @Id fields in:
TblPdtbnf.java and added: @EmbeddedId private TblPdtbnfPK tblPdtbnfPK; Now I get this exception, which seems more promising, but still no cigar. Caused by: org.apache.openjpa.lib.jdbc.ReportingSQLException: DB2 SQL error: SQLCODE: -530, SQLSTATE: 23503, SQLERRMC: EBSTATUS.TBL_PDTBNF.RCR_PDTBNF_01 {prepstmnt 895244 INSERT INTO EBSTATUS.TBL_PDTBNF (pdtbnfId, scmpdtId, CMN_DTE, TMN_DTE, VRS_NBR, SCMPDT_ID, PDTBNF_ID) VALUES (?, ?, ?, ?, ?, ?, ?) [params=(null) null, (null) null, (Date) 2008-06-12, (Date) 1001-01-01, (int) 1, (int) 1500, (int) 13]} [code=-530, state=23503] at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.wrap(LoggingConnectionDecorator.java:192) at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator.access$800(LoggingConnectionDecorator.java:57) at org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator$LoggingConnection$LoggingPreparedStatement.executeUpdate(LoggingConnectionDecorator.java:858) at org.apache.openjpa.lib.jdbc.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:269) at org.apache.openjpa.jdbc.kernel.JDBCStoreManager$CancelPreparedStatement.executeUpdate(JDBCStoreManager.java:1363) at org.apache.openjpa.jdbc.kernel.PreparedStatementManagerImpl.flushInternal(PreparedStatementManagerImpl.java:97) ... 18 more |
||||||||||||||||||
|
egoosen
|
In reply to this post by egoosen
This is clearly a bug in OpenJPA, because the IdClass' field is not getting sychronized with the Entities Id field.
In this case, TblPdtbnf.scmpdtId is not getting copied to TblPdtbnfPK.scmpdtId. Hence this error: Attempt to set column "TBL_PDTBNF.SCMPDT_ID" to two different values: (null)"null", (class java.lang.Integer)"700" |
||||||||||||||||||
|
egoosen
|
I updated to OpenJPA 1.1.0, and I'm not getting the above exception anymore, but its still not working 100%.
Updated example code: em.getTransaction().begin(); TblScmpdt tblScmpdt = new TblScmpdt(); tblScmpdt.setAdmsysCde("EBSTA"); tblScmpdt.setFndCde("0001526"); tblScmpdt.setGccCde("A1526"); tblScmpdt.setPflcmnDte(new Date()); TblPdtbnfcde tblPdtbnfcde = em.getReference(TblPdtbnfcde.class, new Integer(13)); tblScmpdt.setPdtcdeId(tblPdtbnfcde.getTblPdtcde().getPdtcdeId()); tblScmpdt.setTblPdtcde(tblPdtbnfcde.getTblPdtcde()); //First need to save parent, this wouldn't be necessary if the child didn't have a composite key tblScmpdt = em.merge(tblScmpdt); em.flush(); //Have to flush otherwise JPA tries to persist child before parent TblPdtbnf tblPdtbnf = new TblPdtbnf(); tblPdtbnf.setCmnDte(new Date()); tblPdtbnf.setTblPdtbnfcde(tblPdtbnfcde); tblPdtbnf.setPdtbnfId(tblPdtbnfcde.getPdtbnfId()); tblPdtbnf.setTblScmpdt(tblScmpdt); tblPdtbnf.setScmpdtId(tblScmpdt.getScmpdtId()); //this should get done automatically by JPA tblScmpdt.getTblPdtbnfs().add(tblPdtbnf); //merge again to save child(ren) tblScmpdt = em.merge(tblScmpdt); em.getTransaction().commit(); |
||||||||||||||||||
|
Fay Wang
|
In reply to this post by egoosen
Enrico,
What is the primary key field in the TblScmpdt entity? Did you set primary key for tblScmpdt before calling merge? I made an integer primary key field in TblScmpdt, and set the primary key to 1 in tblScmpdt before calling merge, and your test code works fine for me (I also omit TblPdtbnfcde in your test code for lack of detailed information). If you already set the primary key and still get the error, please specify more detail of your TblScmpdt class. -f --- On Thu, 6/12/08, Enrico Goosen <egoosen2@...> wrote: > From: Enrico Goosen <egoosen2@...> > Subject: @OneToMany/@ManyToOne, Bidirectional, Composite Key > To: users@... > Date: Thursday, June 12, 2008, 7:24 AM > I can't get cascading persist working on my entities > which have the following > mappings. > > TblScmpdt.java //Parent > @OneToMany(fetch = > FetchType.LAZY,mappedBy="tblScmpdt",cascade={CascadeType.MERGE,CascadeType.REMOVE}) > private Collection<TblPdtbnf> tblPdtbnfs = new > ArrayList<TblPdtbnf>(); > > TblPdtbnf.java //Child > @Id > @Column(name = "PDTBNF_ID",nullable=false) > private Integer pdtbnfId; //Foreign key to > TblPdtbnfcde.java > > @Id > @Column(name = "SCMPDT_ID",nullable=false) > private Integer scmpdtId; //Foreign key to > TblScmpdt.java > > @ManyToOne(fetch = > FetchType.LAZY,cascade=CascadeType.MERGE) > @JoinColumn(name = > "SCMPDT_ID",referencedColumnName="SCMPDT_ID") > > private TblScmpdt tblScmpdt; > > -------------------------------------- > > Here's the test code: > > em.getTransaction().begin(); > > TblScmpdt tblScmpdt = new TblScmpdt(); > tblScmpdt.setAdmsysCde("EBSTA"); > tblScmpdt.setFndCde("1526"); > tblScmpdt.setGccCde("A1526"); > tblScmpdt.setPflcmnDte(new Date()); > > TblPdtcde tblPdtcde = em.getReference(TblPdtcde.class, > new Integer(121)); > TblPdtbnfcde tblPdtbnfcde = > em.getReference(TblPdtbnfcde.class, new > Integer(13)); > > tblScmpdt.setPdtcdeId(tblPdtcde.getPdtcdeId()); > > TblPdtbnf tblPdtbnf = new TblPdtbnf(); > tblPdtbnf.setCmnDte(new Date()); > tblPdtbnf.setPdtbnfId(tblPdtbnfcde.getPdtbnfId()); > tblPdtbnf.setTblScmpdt(tblScmpdt); > > tblScmpdt.addTblPdtbnf(tblPdtbnf); > // tblScmpdt.getTblPdtbnfs().add(tblPdtbnf); > > tblScmpdt = em.merge(tblScmpdt); > > em.getTransaction().commit(); > > ---------------------------------- > > The exception: > > Caused by: <openjpa-1.0.0-r420667:568756 fatal user > error> > org.apache.openjpa.persistence.InvalidStateException: > Attempt to set column > "TBL_PDTBNF.SCMPDT_ID" to two different values: > (null)"null", (class > java.lang.Integer)"700" This can occur when you > fail to set both sides of a > two-sided relation between objects, or when you map > different fields to the > same column, but you do not keep the values of these fields > in synch. > at > org.apache.openjpa.jdbc.sql.PrimaryRow.setObject(PrimaryRow.java:338) > at > org.apache.openjpa.jdbc.sql.RowImpl.flushJoinValues(RowImpl.java:289) > at > org.apache.openjpa.jdbc.sql.RowImpl.flushForeignKey(RowImpl.java:222) > at > org.apache.openjpa.jdbc.sql.RowImpl.setForeignKey(RowImpl.java:197) > at > org.apache.openjpa.jdbc.sql.PrimaryRow.setForeignKey(PrimaryRow.java:172) > at > org.apache.openjpa.jdbc.meta.ValueMappingImpl.setForeignKey(ValueMappingImpl.java:317) > at > org.apache.openjpa.jdbc.meta.FieldMapping.setForeignKey(FieldMapping.java:966) > at > org.apache.openjpa.jdbc.meta.strats.RelationFieldStrategy.insert(RelationFieldStrategy.java:207) > at > org.apache.openjpa.jdbc.meta.FieldMapping.insert(FieldMapping.java:555) > at > org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.insert(AbstractUpdateManager.java:203) > at > org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.populateRowManager(AbstractUpdateManager.java:145) > at > org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:85) > at > org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:72) > at > org.apache.openjpa.jdbc.kernel.JDBCStoreManager.flush(JDBCStoreManager.java:514) > at > org.apache.openjpa.kernel.DelegatingStoreManager.flush(DelegatingStoreManager.java:130) > at > org.apache.openjpa.datacache.DataCacheStoreManager.flush(DataCacheStoreManager.java:544) > at > org.apache.openjpa.kernel.DelegatingStoreManager.flush(DelegatingStoreManager.java:130) > ... 9 more > > -- > View this message in context: > http://www.nabble.com/%40OneToMany-%40ManyToOne%2C-Bidirectional%2C-Composite-Key-tp17801245p17801245.html > Sent from the OpenJPA Users mailing list archive at > Nabble.com. |
||||||||||||||||||
|
egoosen
|
Hi Fay,
The primary key for TblScmpdt is database generated. Here's the mapping: @TableGenerator(name="baseGenerator",schema="EBSTATUS",table="TBL_KEYGEN",pkColumnName="PRIMARY_KEY_COLUMN" ,valueColumnName="LAST_USED_ID",pkColumnValue="TBL_SCMPDT_ID",allocationSize=100) @Id @GeneratedValue(strategy=GenerationType.TABLE,generator="baseGenerator") @Column(name = "SCMPDT_ID",nullable=false) private Integer scmpdtId; As I mentioned in my previous post, the code works now (not getting exceptions anymore since upgrade to OpenJPA 1.1.0), but its flawed because of the bug in openJPA when performing a cascade persist on a OneToMany entity where the child (many) entity has a composite key. I should simply be able to do the following: TblScmpdt tblScmpdt = new TblScmpdt(); //new (non-persistent) parent entity //set fields on tblScmpdt ... TblPdtbnf tblPdtbnf = new TblPdtbnf(); //new (non-persistent) child entity //set fields on tblPdtbnf ... tblScmpdt.addTblpdtbnf(tblPdtbnf); //see method below, which sets the parent referrence on child //TblScmpdt method: public void addTblPdtbnf(TblPdtbnf tblPdtbnf) { tblPdtbnf.setTblScmpdt(this); //need to set both sides of bidirectional relationship getTblPdtbnfs().add(tblPdtbnf); } //Now I should be able to persist parent and cascade persist child automatically according to JPA docs tblScmpdt = em.merge(tblScmpdt); ...but unfortunately, the above doesn't work. I have to merge/persist the parent first > then get the ID of the newly persisted parent and set it on the new child > then add the child to the parent > then merge the parent again. Tedious! Do you understand the problem? Regarding TblPdtbnfcde, this is a OneToOne reference on TblPdtbnf. TblPdtbnf's composite primary key is made up of TblPdtbnfcde.pdtbnfId and TblScmpdt.scmpdtId By the way, cascade persist works fine on my other OneToMany classes where there isn't a composite key. So this is definitely a bug.
|
||||||||||||||||||
|
Jeremy Bauer
|
Hi Enrico.
Do you have an embedded id or id class defined for TblPdtbnf? Section 2.1.4 of the JPA spec says the following regarding composite primary keys: <spec>A composite primary key must correspond to either a single persistent field or property or to a set of such fields or properties as described below. A primary key class must be defined to represent a composite primary key. Composite primary keys typically arise when mapping from legacy databases when the database key is comprised of several columns. The EmbeddedId and IdClass annotations are used to denote composite primary keys. See sections 9.1.14 and 9.1.15. </spec> If you do not have an embedded id or id class defined, defining one may correct your problem. The spec has some good examples of their usage. -Jeremy On Sat, Jun 14, 2008 at 7:46 AM, Enrico Goosen <egoosen2@...> wrote: > > Hi Fay, > > The primary key for TblScmpdt is database generated. Here's the mapping: > > @TableGenerator(name="baseGenerator",schema="EBSTATUS",table="TBL_KEYGEN",pkColumnName="PRIMARY_KEY_COLUMN" > > ,valueColumnName="LAST_USED_ID",pkColumnValue="TBL_SCMPDT_ID",allocationSize=100) > @Id > @GeneratedValue(strategy=GenerationType.TABLE,generator="baseGenerator") > @Column(name = "SCMPDT_ID",nullable=false) > private Integer scmpdtId; > > As I mentioned in my previous post, the code works (no exceptions), but its > flawed because of the bug in JPA when performing a cascade persist on a > OneToMany entity where the child (many) entity has a composite key. > > I should simply be able to do the following: > > TblScmpdt tblScmpdt = new TblScmpdt(); //new (non-persistent) parent entity > //set fields on tblScmpdt ... > TblPdtbnf tblPdtbnf = new TblPdtbnf(); //new (non-persistent) child entity > //set fields on tblPdtbnf ... > tblScmpdt.addTblpdtbnf(tblPdtbnf); //see method below, which sets the parent > referrence on child > > //TblScmpdt method: > public void addTblPdtbnf(TblPdtbnf tblPdtbnf) { > tblPdtbnf.setTblScmpdt(this); //need to set both sides of bidirectional > relationship > getTblPdtbnfs().add(tblPdtbnf); > } > //Now I should be able to persist parent and cascade persist child > automatically according to JPA docs > tblScmpdt = em.merge(tblScmpdt); > > ...but unfortunately, the above doesn't work. > I have to merge/persist the parent first > then get the ID of the newly > persisted parent and set it on the new child > then add the child to the > parent > then merge the parent again. Tedious! > > Do you understand the problem? > > Regarding TblPdtbnfcde, this is a OneToOne reference on TblPdtbnf. > TblPdtbnf's composite primary key is made up of TblPdtbnfcde.pdtbnfId and > TblScmpdt.scmpdtId > > By the way, cascade persist works fine on my other OneToMany classes where > there isn't a composite key. > So this is definitely a bug. > > > > Enrico, > > What is the primary key field in the TblScmpdt entity? Did you set > primary key for tblScmpdt before calling merge? I made an integer primary > key field in TblScmpdt, and set the primary key to 1 in tblScmpdt before > calling merge, and your test code works fine for me (I also omit > TblPdtbnfcde in your test code for lack of detailed information). If you > already set the primary key and still get the error, please specify more > detail of your TblScmpdt class. > > > -f > > -- > View this message in context: http://www.nabble.com/%40OneToMany-%40ManyToOne%2C-Bidirectional%2C-Composite-Key-BUG-tp17801245p17839130.html > Sent from the OpenJPA Users mailing list archive at Nabble.com. > > |
||||||||||||||||||
|
Fay Wang
|
In reply to this post by egoosen
ok. I can reproduce your problem. Let me take a look.
-f --- On Sat, 6/14/08, Enrico Goosen <egoosen2@...> wrote: > From: Enrico Goosen <egoosen2@...> > Subject: Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key > To: users@... > Date: Saturday, June 14, 2008, 5:46 AM > Hi Fay, > > The primary key for TblScmpdt is database generated. > Here's the mapping: > > @TableGenerator(name="baseGenerator",schema="EBSTATUS",table="TBL_KEYGEN",pkColumnName="PRIMARY_KEY_COLUMN" > > ,valueColumnName="LAST_USED_ID",pkColumnValue="TBL_SCMPDT_ID",allocationSize=100) > @Id > @GeneratedValue(strategy=GenerationType.TABLE,generator="baseGenerator") > @Column(name = "SCMPDT_ID",nullable=false) > private Integer scmpdtId; > > As I mentioned in my previous post, the code works (no > exceptions), but its > flawed because of the bug in JPA when performing a cascade > persist on a > OneToMany entity where the child (many) entity has a > composite key. > > I should simply be able to do the following: > > TblScmpdt tblScmpdt = new TblScmpdt(); //new > (non-persistent) parent entity > //set fields on tblScmpdt ... > TblPdtbnf tblPdtbnf = new TblPdtbnf(); //new > (non-persistent) child entity > //set fields on tblPdtbnf ... > tblScmpdt.addTblpdtbnf(tblPdtbnf); //see method below, > which sets the parent > referrence on child > > //TblScmpdt method: > public void addTblPdtbnf(TblPdtbnf tblPdtbnf) { > tblPdtbnf.setTblScmpdt(this); //need to set both sides of > bidirectional > relationship > getTblPdtbnfs().add(tblPdtbnf); > } > //Now I should be able to persist parent and cascade > persist child > automatically according to JPA docs > tblScmpdt = em.merge(tblScmpdt); > > ...but unfortunately, the above doesn't work. > I have to merge/persist the parent first > then get the > ID of the newly > persisted parent and set it on the new child > then add > the child to the > parent > then merge the parent again. Tedious! > > Do you understand the problem? > > Regarding TblPdtbnfcde, this is a OneToOne reference on > TblPdtbnf. > TblPdtbnf's composite primary key is made up of > TblPdtbnfcde.pdtbnfId and > TblScmpdt.scmpdtId > > By the way, cascade persist works fine on my other > OneToMany classes where > there isn't a composite key. > So this is definitely a bug. > > > > Enrico, > > What is the primary key field in the TblScmpdt entity? > Did you set > primary key for tblScmpdt before calling merge? I made an > integer primary > key field in TblScmpdt, and set the primary key to 1 in > tblScmpdt before > calling merge, and your test code works fine for me (I also > omit > TblPdtbnfcde in your test code for lack of detailed > information). If you > already set the primary key and still get the error, please > specify more > detail of your TblScmpdt class. > > > -f > > -- > View this message in context: > http://www.nabble.com/%40OneToMany-%40ManyToOne%2C-Bidirectional%2C-Composite-Key-BUG-tp17801245p17839130.html > Sent from the OpenJPA Users mailing list archive at > Nabble.com. |
||||||||||||||||||
|
Fay Wang
|
In reply to this post by Fay Wang
Hi Ernico,
The cause of the problem is not the composite primary key but the mapping of the primary key column and join column in the child table: (1) in the parent class TblScmpdt: the primary key: scmpdtId (the column: SCMPDT_ID) (2) in the child class the composite primary key: pdtbnfId (column: PDTBNF_ID) scmpdtId (column: SCMPDT_ID) the join column: SCMPDT_ID Please note that in this mapping, you have a primary key and the join value all mapped to the column SCMPDT_ID in the child table. In your test case: TblScmpdt tblScmpdt = new TblScmpdt(); (1) TblPdtbnf tblPdtbnf = new TblPdtbnf(); (2) TblScmpdt.addTblpdtbnf(tblPdtbnf); (3) Since you did not have the primary key set in the parent table (TblScmpdt ), openjpa will regard this entity as a new one, and perform persist() on it even though you call merge(). The problem is in the child entity. In statement (3), you establish the 1-many relationship between tblScmpdt and tblPdtbnf. The join value is therefore the primary key of tblScmpdt, which is automatically generated by openjpa via table generator. However, since you did not set the primary keys (pdtbnfId and scmpdtId, both are Integer class) for tblPdtbnf, they are null values (the default values). When both the null value of scmpdtId primary key and the non-null join value are set to column SCMPDT_ID, you got the following error message: Caused by: <openjpa-1.0.0-r420667:568756 fatal user error> org.apache.openjpa.persistence.InvalidStateException: Attempt to set column "TBL_PDTBNF.SCMPDT_ID" to two different values: (null)"null", (class java.lang.Integer)"700" This can occur when you fail to set both sides of a two-sided relation between objects, or when you map different fields to the same column, but you do not keep the values of these fields in synch. This scenario can not be distinguished from the following one which is obviously a user-error (note Statement (2.1)): TblScmpdt tblScmpdt = new TblScmpdt(); (1) TblPdtbnf tblPdtbnf = new TblPdtbnf(); (2) tblPdtbnf.setScmpdtId(null); (2.1) TblScmpdt.addTblpdtbnf(tblPdtbnf); (3) This problem can be fixed by not using the primary key column as the join column. Specifically, setting the join-column name to something other than SCMPDT_ID should solve this problem: @ManyToOne(fetch = FetchType.LAZY,cascade=CascadeType.MERGE) @JoinColumn(name = "XX_ID",referencedColumnName="SCMPDT_ID") private TblScmpdt tblScmpdt; If you really want to use the primary key column as the join column, then don't let openjpa to automatically generate the primary key, so that you can explicitly set the primary keys of the child class the same as the join value. If you really want openjpa to automatically generate primary key, then you have to use the "lousy" way you described in your earlier email. Please let me know what you think. Thanks! -f --- On Sat, 6/14/08, Enrico Goosen <egoosen2@...> wrote: > From: Enrico Goosen <egoosen2@...> > Subject: Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key > To: users@... > Date: Saturday, June 14, 2008, 5:46 AM > Hi Fay, > > The primary key for TblScmpdt is database generated. > Here's the mapping: > > @TableGenerator(name="baseGenerator",schema="EBSTATUS",table="TBL_KEYGEN",pkColumnName="PRIMARY_KEY_COLUMN" > > ,valueColumnName="LAST_USED_ID",pkColumnValue="TBL_SCMPDT_ID",allocationSize=100) > @Id > @GeneratedValue(strategy=GenerationType.TABLE,generator="baseGenerator") > @Column(name = "SCMPDT_ID",nullable=false) > private Integer scmpdtId; > > As I mentioned in my previous post, the code works (no > exceptions), but its > flawed because of the bug in JPA when performing a cascade > persist on a > OneToMany entity where the child (many) entity has a > composite key. > > I should simply be able to do the following: > > TblScmpdt tblScmpdt = new TblScmpdt(); //new > (non-persistent) parent entity > //set fields on tblScmpdt ... > TblPdtbnf tblPdtbnf = new TblPdtbnf(); //new > (non-persistent) child entity > //set fields on tblPdtbnf ... > tblScmpdt.addTblpdtbnf(tblPdtbnf); //see method below, > which sets the parent > referrence on child > > //TblScmpdt method: > public void addTblPdtbnf(TblPdtbnf tblPdtbnf) { > tblPdtbnf.setTblScmpdt(this); //need to set both sides of > bidirectional > relationship > getTblPdtbnfs().add(tblPdtbnf); > } > //Now I should be able to persist parent and cascade > persist child > automatically according to JPA docs > tblScmpdt = em.merge(tblScmpdt); > > ...but unfortunately, the above doesn't work. > I have to merge/persist the parent first > then get the > ID of the newly > persisted parent and set it on the new child > then add > the child to the > parent > then merge the parent again. Tedious! > > Do you understand the problem? > > Regarding TblPdtbnfcde, this is a OneToOne reference on > TblPdtbnf. > TblPdtbnf's composite primary key is made up of > TblPdtbnfcde.pdtbnfId and > TblScmpdt.scmpdtId > > By the way, cascade persist works fine on my other > OneToMany classes where > there isn't a composite key. > So this is definitely a bug. > > > > Enrico, > > What is the primary key field in the TblScmpdt entity? > Did you set > primary key for tblScmpdt before calling merge? I made an > integer primary > key field in TblScmpdt, and set the primary key to 1 in > tblScmpdt before > calling merge, and your test code works fine for me (I also > omit > TblPdtbnfcde in your test code for lack of detailed > information). If you > already set the primary key and still get the error, please > specify more > detail of your TblScmpdt class. > > > -f > > -- > View this message in context: > http://www.nabble.com/%40OneToMany-%40ManyToOne%2C-Bidirectional%2C-Composite-Key-BUG-tp17801245p17839130.html > Sent from the OpenJPA Users mailing list archive at > Nabble.com. |
||||||||||||||||||
|
egoosen
|
Hi Fay,
I tried out your suggestion: @ManyToOne(fetch = FetchType.LAZY,cascade=CascadeType.MERGE) @JoinColumn(name = "XYZ_ID",referencedColumnName="SCMPDT_ID") private TblScmpdt tblScmpdt; But unfortunately, still no luck. Got this exception: <openjpa-1.1.0-r422266:657916 fatal store error> org.apache.openjpa.persistence.RollbackException: DB2 SQL error: SQLCODE: -407, SQLSTATE: 23502, SQLERRMC: TBSPACEID=2, TABLEID=263, COLNO=0 at org.apache.openjpa.persistence.EntityManagerImpl.commit(EntityManagerImpl.java:523) at test.za.co.metcapri.Tester.test(Tester.java:100) at test.za.co.metcapri.Tester.main(Tester.java:21) Caused by: <openjpa-1.1.0-r422266:657916 nonfatal general error> org.apache.openjpa.persistence.PersistenceException: DB2 SQL error: SQLCODE: -407, SQLSTATE: 23502, SQLERRMC: TBSPACEID=2, TABLEID=263, COLNO=0 FailedObject: prepstmnt 14779369 INSERT INTO EBSTATUS.TBL_PDTBNF (PDTBNF_ID, SCMPDT_ID, CMN_DTE) VALUES (?, ?, ?) [org.apache.openjpa.jdbc.kernel.JDBCStoreManager$CancelPreparedStatement] SQLSTATE 23502: An insert or update value is null, but the column cannot contain null values. The closest I came to solving this problem was a suggestion I saw in the Hibernate forums, where a user was experiencing the same problem. http://forum.hibernate.org/viewtopic.php?t=987126&highlight=detached&sid=48c7ceada0b8df5718275a74d6dcafc4 I changed TblPdtbnf.class to use an @EmbeddedId: @EmbeddedId private TblPdtbnfPK tblPdtbnfPK; Changed TblPdtbnfPK to @Embeddable. I also had to modify the setters on TblPdtbnf like so: public void setTblScmpdt(TblScmpdt tblScmpdt) { this.tblScmpdt = tblScmpdt; if(this.tblPdtbnfPK == null){ this.tblPdtbnfPK = new TblPdtbnfPK(); } if(tblScmpdt != null){ this.tblPdtbnfPK.setScmpdtId(tblScmpdt.getScmpdtId()); } } public void setTblPdtbnfcde(TblPdtbnfcde tblPdtbnfcde) { this.tblPdtbnfcde = tblPdtbnfcde; if(this.tblPdtbnfPK == null){ this.tblPdtbnfPK = new TblPdtbnfPK(); } if(tblPdtbnfcde != null){ this.tblPdtbnfPK.setPdtbnfId(tblPdtbnfcde.getPdtbnfId()); } } I was able to perform a cascading persist, but when I checked the database, there were two new columns on TBL_PDTBNF, viz. scmpdtId, and pdtbnfId, in addition to the existing columns SCMPDT_ID and PDTBNF_ID. I tried renaming the fields on TblPdtbnfPK.class to match the database columns, to prevent this problem, but that didn't help. As a last resort, I tried switching JPA providers to Hibernate, and I found that the problem exists in Hibernate as well. I give up...
|
||||||||||||||||||
|
Fay Wang
|
Hmmm. Here is my test case and it works fine. Four classes are listed:
(1) TblPdtbnf0.java (2) TblPdtbnfId.java (3) TblScmpdt0.java (4) Test0.java You might still want to try it? :=)) -f ==================================================== (1) TblPdtbnf0.java package insert; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @Entity @IdClass(TblPdtbnfId.class) public class TblPdtbnf0 { @Id @Column(name = "PDTBNF_ID", nullable = false) private Integer pdtbnfId; @Id @Column(name = "SCMPDT_ID", nullable = false) private Integer scmpdtId; @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE) @JoinColumn(name = "XYZ_ID", referencedColumnName = "SCMPDT_ID") private TblScmpdt0 tblScmpdt; public Integer getPdtbnfId() { return pdtbnfId; } public void setPdtbnfId(Integer pdtbnfId) { this.pdtbnfId = pdtbnfId; } public Integer getScmpdtId() { return scmpdtId; } public TblScmpdt0 getTblScmpdt() { return tblScmpdt; } public void setTblScmpdt(TblScmpdt0 tblScmpdt) { this.tblScmpdt = tblScmpdt; this.scmpdtId = tblScmpdt.getScmpdtId(); } } ============================================================= (2)TblPdtbnfId.java package insert; import java.io.Serializable; public class TblPdtbnfId implements Serializable{ private Integer pdtbnfId; private Integer scmpdtId; public TblPdtbnfId(){} public TblPdtbnfId(Integer pdtbnfId, Integer scmpdtId) { this.pdtbnfId = pdtbnfId; this.scmpdtId = scmpdtId; } public Integer getScmpdtId() { return scmpdtId; } public Integer getPdtbnfId() { return pdtbnfId; } public boolean equals(Object o) { return (o instanceof TblPdtbnfId) && pdtbnfId.intValue() == ((TblPdtbnfId)o).getPdtbnfId().intValue() && scmpdtId.intValue() == ((TblPdtbnfId)o).getScmpdtId().intValue(); } public int hashCode() { int hc = 0; if (pdtbnfId != null) hc = hc + pdtbnfId.hashCode(); if (scmpdtId != null) hc = hc + scmpdtId.hashCode(); return hc; } } ============================================== (3)TblScmpdt0.java package insert; import java.util.ArrayList; import java.util.Collection; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.TableGenerator; @Entity public class TblScmpdt0 { @TableGenerator(name="baseGenerator",schema="EBSTATUS",table="TBL_KEYGEN", pkColumnName="PRIMARY_KEY_COLUMN", valueColumnName="LAST_USED_ID", pkColumnValue="TBL_SCMPDT_ID",allocationSize=100) @Id @GeneratedValue(strategy=GenerationType.TABLE,generator="baseGenerator") @Column(name = "SCMPDT_ID",nullable=false) private Integer scmpdtId; @OneToMany(fetch = FetchType.LAZY, mappedBy="tblScmpdt", cascade={CascadeType.MERGE,CascadeType.REMOVE, CascadeType.PERSIST}) private Collection<TblPdtbnf0> tblPdtbnfs = new ArrayList<TblPdtbnf0>(); private String admsysCde; private String fndCde; private String gccCde; public Collection getTblPdtbnfs() { return tblPdtbnfs; } public void setTblPdtbnfs(Collection tblPdtbnfs) { this.tblPdtbnfs = tblPdtbnfs; } public void addTblPdtbnf(TblPdtbnf0 tblPdtbnf) { tblPdtbnfs.add(tblPdtbnf); } public Integer getScmpdtId() { return scmpdtId; } public String getAdmsysCde() { return admsysCde; } public void setAdmsysCde(String admsysCde) { this.admsysCde = admsysCde; } public void setFndCde(String fndCde) { this.fndCde = fndCde; } public String getFndCde(){ return fndCde; } public void setGccCde(String gccCde){ this.gccCde = gccCde; } public String getGccCde() { return gccCde; } } ======================================================== (4) Test0.java: package insert; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public class Test0 { public static void main(String[] args) { try{ EntityManagerFactory emf = Persistence.createEntityManagerFactory("insert"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); TblScmpdt0 tblScmpdt = new TblScmpdt0(); tblScmpdt.setAdmsysCde("EBSTA"); tblScmpdt.setFndCde("1526"); tblScmpdt.setGccCde("A1526"); TblPdtbnf0 tblPdtbnf = new TblPdtbnf0(); tblPdtbnf.setTblScmpdt(tblScmpdt); tblScmpdt.addTblPdtbnf(tblPdtbnf); tblScmpdt = em.merge(tblScmpdt); em.getTransaction().commit(); } catch (Exception e){ e.printStackTrace(); } } } --- On Tue, 6/17/08, Enrico Goosen <egoosen2@...> wrote: > From: Enrico Goosen <egoosen2@...> > Subject: Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key > To: users@... > Date: Tuesday, June 17, 2008, 1:32 AM > Hi Fay, > > I tried out your suggestion: > @ManyToOne(fetch = > FetchType.LAZY,cascade=CascadeType.MERGE) > @JoinColumn(name = > "XYZ_ID",referencedColumnName="SCMPDT_ID") > > private TblScmpdt tblScmpdt; > > But unfortunately, still no luck. > Got this exception: > <openjpa-1.1.0-r422266:657916 fatal store error> > org.apache.openjpa.persistence.RollbackException: DB2 SQL > error: SQLCODE: > -407, SQLSTATE: 23502, SQLERRMC: TBSPACEID=2, TABLEID=263, > COLNO=0 > at > org.apache.openjpa.persistence.EntityManagerImpl.commit(EntityManagerImpl.java:523) > at test.za.co.metcapri.Tester.test(Tester.java:100) > at test.za.co.metcapri.Tester.main(Tester.java:21) > Caused by: <openjpa-1.1.0-r422266:657916 nonfatal > general error> > org.apache.openjpa.persistence.PersistenceException: DB2 > SQL error: SQLCODE: > -407, SQLSTATE: 23502, SQLERRMC: TBSPACEID=2, TABLEID=263, > COLNO=0 > FailedObject: prepstmnt 14779369 INSERT INTO > EBSTATUS.TBL_PDTBNF (PDTBNF_ID, > SCMPDT_ID, CMN_DTE) VALUES (?, ?, ?) > [org.apache.openjpa.jdbc.kernel.JDBCStoreManager$CancelPreparedStatement] > > SQLSTATE 23502: An insert or update value is null, but the > column cannot > contain null values. > > The closest I came to solving this problem was a suggestion > I saw in the > Hibernate forums, where a user was experiencing the same > problem. > http://forum.hibernate.org/viewtopic.php?t=987126&highlight=detached&sid=48c7ceada0b8df5718275a74d6dcafc4 > http://forum.hibernate.org/viewtopic.php?t=987126&highlight=detached&sid=48c7ceada0b8df5718275a74d6dcafc4 > > > I changed TblPdtbnf.class to use an @EmbeddedId: > > @EmbeddedId > private TblPdtbnfPK tblPdtbnfPK; > > Changed TblPdtbnfPK to @Embeddable. > > I also had to modify the setters on TblPdtbnf like so: > > public void setTblScmpdt(TblScmpdt tblScmpdt) { > this.tblScmpdt = tblScmpdt; > if(this.tblPdtbnfPK == null){ > this.tblPdtbnfPK = new TblPdtbnfPK(); > } > if(tblScmpdt != null){ > this.tblPdtbnfPK.setScmpdtId(tblScmpdt.getScmpdtId()); > } > } > public void setTblPdtbnfcde(TblPdtbnfcde tblPdtbnfcde) { > this.tblPdtbnfcde = tblPdtbnfcde; > if(this.tblPdtbnfPK == null){ > this.tblPdtbnfPK = new TblPdtbnfPK(); > } > if(tblPdtbnfcde != null){ > this.tblPdtbnfPK.setPdtbnfId(tblPdtbnfcde.getPdtbnfId()); > } > } > > I was able to perform a cascading persist, but when I > checked the database, > there were two new columns on TBL_PDTBNF, viz. scmpdtId, > and pdtbnfId, in > addition to the existing columns SCMPDT_ID and PDTBNF_ID. > I tried renaming the fields on TblPdtbnfPK.class to match > the database > columns, to prevent this problem, but that didn't help. > > As a last resort, I tried switching JPA providers to > Hibernate, and I found > that the problem exists in Hibernate as well. > > I give up...:-(( > -- > View this message in context: > http://www.nabble.com/%40OneToMany-%40ManyToOne%2C-Bidirectional%2C-Composite-Key-BUG-tp17801245p17880499.html > Sent from the OpenJPA Users mailing list archive at > Nabble.com. |
||||||||||||||||||
|
egoosen
|
Just want to update this post in case someone else stubbles on the same problems that we did.
My colleague (who's a OpenJPA Ninja) rewrote our DAO's to overcome the problems I mentioned before. This included adding the OpenJPA specific annotation @ForeignKey to @ManyToOne relationships, cos otherwise DB2 tries to insert a child entity before a parent entity. Apparently this isn't required for MySQL. PS. I'm not sure if this annotation has been added to the OpenJPA docs recently, but it wasn't mentioned anywhere in the 1.0.2 manual. He also added methods to synchronize parent and child relationships. This can be done be defining a BaseEntity from which all JPA entities extend, that implement these methods. So, after em.merge and before transaction commit, synch the relationships, and all will be fine. |
||||||||||||||||||