@OneToMany/@ManyToOne, Bidirectional, Composite Key BUG

21 Messages Forum Options Options
Embed this topic
Permalink
1 2
egoosen
@OneToMany/@ManyToOne, Bidirectional, Composite Key BUG
Reply Threaded MoreMore options
Print post
Permalink
(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
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key
Reply Threaded MoreMore options
Print post
Permalink
(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
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key BUG
Reply Threaded MoreMore options
Print post
Permalink
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
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key BUG
Reply Threaded MoreMore options
Print post
Permalink
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
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key
Reply Threaded MoreMore options
Print post
Permalink
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
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key
Reply Threaded MoreMore options
Print post
Permalink
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.



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
Jeremy Bauer
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key
Reply Threaded MoreMore options
Print post
Permalink
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
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key
Reply Threaded MoreMore options
Print post
Permalink
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
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key
Reply Threaded MoreMore options
Print post
Permalink
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
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key
Reply Threaded MoreMore options
Print post
Permalink
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
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key
Reply Threaded MoreMore options
Print post
Permalink
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
Re: @OneToMany/@ManyToOne, Bidirectional, Composite Key
Reply Threaded MoreMore options
Print post
Permalink
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.