persistence@glassfish.java.net

Re:Solved - my fault - Foreign key as part of compound key and one-to-many relationship

From: Jochen Seifarth <js_at_agentbob.info>
Date: Wed, 15 Feb 2006 00:37:57 +0100 (CET)

Guy,
you are absolutly right, @MapKey works fine !

I had assumed that I needed to specify the composite key (e.g.
@MapKey(name="AddressPK") and that's caused the error on deployment.
'_at_MayKey' works fine and uses the right key fields... as the spec says it
should be using the primary key (whether that's a composite key or not).

Conclusion:
-Sometimes it helps to read the source
-Sometimes it helps even more to read the manual/spec
-...and sometimes you just need to be told by somebody who knows

Cheers & Thanks for you help !
Jochen


On Di, Februar 14, 2006 19:43, Guy Pelletier wrote:
> Jochen,
>
> Using a mapkey with a composite primary key is now supported and has been
> for some time. The code below indicates that the version of the code you
> are
> running supports this functionality, however if possible you should run
> against the latest version of Glassfish and if the problem persists,
> please
> enter a bug.
>
> Cheers,
> Guy
>
> ----- Original Message -----
> From: "Jochen Seifarth" <js_at_agentbob.info>
> To: <persistence_at_glassfish.dev.java.net>
> Sent: Tuesday, February 14, 2006 12:00 PM
> Subject: RE: Foreign key as part of compound key and one-to-many
> relationship
>
>
>> Sorry for wasting your time......sometimes it helps to read the source
>> ;-)
>>
>> Currently, Toplink in Glassfish just doesn't support using @MapKey for
>> composite keys, regardless if the composite primary key is specified via
>> @IdClass or @EmbeddedId. So what I was trying to achieve is simply not
>> supported yet. Any idea anybody when this will be supported ?
>>
>> http://fisheye5.cenqua.com/viewrep/glassfish/entity-persistence/src/java/oracle/toplink/essentials/internal/annotations/EJBAnnotationsProcessor.java?r=1.14
>> which is the most recent version:
>>
>> 1592 if (name.equals("") && referenceDmd.hasCompositePrimaryKey()) {
>> 1593 // No persistent property or field name has been provided, and
>> 1594 // the reference class has a composite primary key class. Let
>> 1595 // it fall through to return null for the map key. Internally,
>> 1596 // TopLink will use an instance of the composite primary key
>> 1597 // class as the map key.
>> 1598 } else {
>> 1599 // A persistent property or field name may have have been
>> 1600 // provided. If one has not we will default to the primary
>> 1601 // key of the reference class. The primary key cannot be
>> 1602 // composite at this point.
>> 1603 String fieldOrPropertyName = getName(name,
>> referenceDmd.getIdAttributeName(),
>> EJBAnnotationsLogger.MAP_KEY_ATTRIBUTE_NAME, accessor);
>>
>>
>> On Di, Februar 14, 2006 16:25, Jochen Seifarth wrote:
>>> Thanks for your reply Gordon,
>>>
>>> my question really is how do I change my code to use
>>> @EmbeddedId AddressPK
>>> instead of @IdClass(AddressPK) and 2 @Id's for the two key elements ?
>>> Of course, I would like to maintain the relationship to Person.
>>> Is that feasible ?
>>>
>>> The code as I posted it deploys fine but I keep getting complaints
>>> about
>>> multiple writable instances of one key element (ADDRESS.GID) when
>>> trying
>>> to use @EmbeddedId - even though I do specify (insertable=false,
>>> updatable=false).
>>> (I thought b32f was pretty recent as it was released in early Feb or
>>> should I rather use b36 ?)
>>>
>>> The reason I would like to use @EmbeddedId is that I would like to use
>>> a
>>> Map<AddressPK,Address> in Person (instead of a Collection<Address>) to
>>> hold the addresses, so I could use @MapKey<AddressPK> on the Person
>>> side.....
>>> or could I still use it even though AddressPK is not declared as
>>> @EmbeddedId ?
>>>
>>> Unfortunaly the example you refer to also uses @IdClass and mupltiple
>>> @Id's rather than @EmbeddedId....
>>>
>>>
>>> On Di, Februar 14, 2006 16:04, Gordon Yorke wrote:
>>>> This may be an issue with using an older Glassfish build. The classes
>>>> you
>>>> have attached below should deploy successfully.
>>>>
>>>> In the Glassfish entity-persistence-tests module
>>>> oracle/toplink/essentials/testing/models/cmp3/advanced package you can
>>>> see
>>>> an example similar to yours using Employee and PhoneNumber where
>>>> PhoneNumber has a compound Primary Key a component of which is a
>>>> foreign
>>>> key to Employee.
>>>>
>>>> --Gordon
>>>>
>>>> -----Original Message-----
>>>> From: Jochen Seifarth [mailto:js_at_agentbob.info]
>>>> Sent: Tuesday, February 14, 2006 8:01 AM
>>>> To: persistence_at_glassfish.dev.java.net
>>>> Subject: Foreign key as part of compound key and one-to-many
>>>> relationship
>>>>
>>>>
>>>> (Apologies as this is a cross-post from the forum but this seems to be
>>>> the
>>>> more appropriate place.)
>>>>
>>>> Can anybody help with my problem described below ? Is this due to:
>>>> a) my ignorance being new to EJB persistence
>>>> b) a limitation of JSR-220
>>>> c) a problem in the Glassfish build (b32f) I am using
>>>>
>>>> Trying out EJB3 persistence on Glassfish I have come across the
>>>> following
>>>> problem:
>>>>
>>>> In a one-to-many relationship, one Person with many Addresses (e.g.
>>>> home,
>>>> work, etc.), I would like to use the primary key of Person (gid) as
>>>> part
>>>> of the compound key for Address (gid, aid).
>>>> So, I define an @Embeddable AddressPK class to hold the compound key
>>>> (gid,
>>>> aid), use this class as @IdClass on @Entity Address and define @Id gid
>>>> and
>>>> @Id aid.
>>>> So far so good, however, according to my understanding of the spec
>>>> (=JSR
>>>> 220, proposed final draft) I should also be able to specify
>>>> @EmbeddedId
>>>> AddressPK on @Entity Address, which to my mind is cleaner as it avoids
>>>> duplication of columns where the names have to match. Using
>>>> @EmbeddedId
>>>> AddressPK works fine as long as I am not trying to map @ManyToOne to
>>>> Person.
>>>> I tried various approaches:
>>>> -Mapping @ManyToOne from Address, specifying to
>>>> @JoinColumn(name="GID"):
>>>> Glassfish (actually Oracle Toplink) keeps complaining about multiple
>>>> writeable instances of GID, even though I had set them to
>>>> (insertable=false, updatable=false), both in @Column and @JoinColumn,
>>>> in
>>>> either and in various other combinations....
>>>> -Trying to map @ManyToOne from AddressPK fails as Toplink complains
>>>> that
>>>> AddressPK is not an entity....
>>>>
>>>> OK, maybe the spec just doesn't cater for such cases as it refers to
>>>> such
>>>> compound keys as coming from 'legacy' systems several times. On the
>>>> other
>>>> hand, from a database perspective there are pretty good reasons for
>>>> such
>>>> keys and relationships. Also, from a business point of view compound
>>>> keys
>>>> make a lot of sense.
>>>>
>>>> The next thing I was trying to archieve is to hold the Addresses in
>>>> Person
>>>> in a Map<AddressPK,Address>, rather than in a Collection<Address> to
>>>> allow
>>>> me to access items directly by key rather than having to search the
>>>> whole
>>>> Collection for it. @MapKey should achieve this.... but if I cannot get
>>>> AddressPK to be the @EmbeddedId for Address, what would @MapKey use as
>>>> the
>>>> key ? .....
>>>>
>>>> Here is my code - I would really appreciate if anybody could show me
>>>> how
>>>> to change it to use AddressPK as @EmbeddedId while maintaining the
>>>> bidirectional relationship with Person.
>>>>
>>>>
>>>> @Embeddable
>>>> public class AddressPK implements Serializable {
>>>> private String gid;
>>>> private String aid;
>>>>
>>>> public AddressPK() {
>>>> this("", "");
>>>> }
>>>>
>>>> public AddressPK(String gid, String aid) {
>>>> this.gid = gid;
>>>> this.aid = aid;
>>>> }
>>>>
>>>> public boolean equals(AddressPK anotherPK) {
>>>> return (this.gid.equals(anotherPK.gid) && this.aid
>>>> .equals(anotherPK.aid));
>>>> }
>>>>
>>>> public int hashCode() {
>>>> return gid.concat(aid).hashCode();
>>>> }
>>>>
>>>> public String getGid() {
>>>> return gid;
>>>> }
>>>> public void setGid(String gid) {
>>>> this.gid = gid;
>>>> }
>>>>
>>>> public String getAid() {
>>>> return aid;
>>>> }
>>>>
>>>> public void setAid(String aid) {
>>>> this.aid = aid;
>>>> }
>>>> }
>>>>
>>>> ----------------------------------------------------------
>>>>
>>>> @Entity
>>>> @IdClass(AddressPK.class)
>>>> public class Address implements Serializable {
>>>> private AddressPK pk;
>>>> private Person person;
>>>> private String street;
>>>>
>>>> public Address() {
>>>> this(new AddressPK());
>>>> }
>>>>
>>>> public Address(AddressPK pk) {
>>>> this.pk = pk;
>>>> street = "";
>>>> }
>>>>
>>>> public boolean equals(Address another) {
>>>> return (this.pk.equals(another.pk));
>>>> }
>>>>
>>>> public int hashCode(){
>>>> return pk.hashCode();
>>>> }
>>>>
>>>> @Id
>>>> @Column(insertable=false,updatable=false)
>>>> public String getGid() {
>>>> return pk.getGid();
>>>> }
>>>>
>>>> public void setGid(String gid) {
>>>> this.pk.setGid(gid);
>>>> }
>>>>
>>>> @Id
>>>> public String getAid() {
>>>> return pk.getAid();
>>>> }
>>>>
>>>> public void setAid(String aid) {
>>>> this.pk.setAid(aid);
>>>> }
>>>>
>>>> @ManyToOne
>>>> @JoinColumn(name="GID")
>>>> public Person getPerson() {
>>>> return person;
>>>> }
>>>>
>>>> public void setPerson(Person person) {
>>>> this.person = person;
>>>> this.pk.setGid(person.getGid());
>>>> }
>>>>
>>>> public String getStreet() {
>>>> return street;
>>>> }
>>>>
>>>> public void setStreet(String street) {
>>>> this.street = street;
>>>> }
>>>>
>>>> @Transient
>>>> public AddressPK getPk() {
>>>> return pk;
>>>> }
>>>> }
>>>>
>>>> -----------------------------------------------------------
>>>>
>>>> @Entity
>>>> public class Person implements Serializable {
>>>> private String gid;
>>>> private String name;
>>>> private Collection<Address> addresses;
>>>>
>>>> public Person() {
>>>> this("");
>>>> }
>>>>
>>>> public Person(String gid) {
>>>> this.gid = gid;
>>>> }
>>>>
>>>> @Id
>>>> public String getGid() {
>>>> return gid;
>>>> }
>>>>
>>>> public void setGid(String gid) {
>>>> this.gid = gid;
>>>> }
>>>>
>>>> @OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
>>>> public Collection<Address> getAddresses() {
>>>> return addresses;
>>>> }
>>>>
>>>> public void setAddresses(Collection<Address> addresses) {
>>>> this.addresses = addresses;
>>>> }
>>>>
>>>> public String getName() {
>>>> return name;
>>>> }
>>>>
>>>> public void setName(String name) {
>>>> this.name = name;
>>>> }
>>>> }
>>>>
>>>>
>>>>
>>>>
>>>>
>>>>
>>>
>>>
>>
>>
>