persistence@glassfish.java.net

Foreign key as part of compound key and one-to-many relationship

From: Jochen Seifarth <js_at_agentbob.info>
Date: Tue, 14 Feb 2006 13:40:12 +0100 (CET)

(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;
}
}