I have the following design:
(source: kawoolutions.com)
Simple logic: Foos uses a multi-column PK to PostAddresses using the same columns (same key).
Here are the JPA 2.0 @EmbeddedId mappings (using a nested ID class PostAddressId in FooId and the @MapsId annotation in Foo to map to it):
@Entity
@Table(name = "PostAddresses")
public class PostAddress implements Serializable
{
@EmbeddedId
private PostAddressId embeddedId;
...
}
@Embeddable
public class PostAddressId implements Serializable
{
@Column(name = "contact_id")
private Integer contactId;
@Column(name = "ordinal_nbr")
private Integer ordinalNbr = 1;
...
}
@Entity
@Table(name = "Foos")
public class Foo implements Serializable
{
@EmbeddedId
private FooId embeddedId;
@MapsId(value = "postAddressId")
@OneToOne
@JoinColumns(value = {
@JoinColumn(name = "contact_id", referencedColumnName = "contact_id", insertable = false, updatable = false),
@JoinColumn(name = "ordinal_nbr", referencedColumnName = "ordinal_nbr", insertable = false, updatable = false)
})
private PostAddress postAddress;
...
}
@Embeddable
public class FooId implements Serializable
{
@Embedded
private PostAddressId postAddressId;
...
}
Stack trace:
Exception [EclipseLink-46] (Eclipse Persistence Services - 2.3.0.v20110429-r9282): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: There should be one non-read-only mapping defined for the primary key field [Foos.ordinal_nbr].
Descriptor: RelationalDescriptor(tld.transmuc.model.Foo --> [DatabaseTable(Foos)])
Runtime Exceptions:
---------------------------------------------------------
at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:535)
at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:476)
at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:435)
at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.postConnectDatasource(DatabaseSessionImpl.java:673)
at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.loginAndDetectDatasource(DatabaseSessionImpl.java:618)
at org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.login(EntityManagerFactoryProvider.java:206)
at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:460)
... 4 more
To me the mappings are correct. I checked them several times. Note, using the @Embedded annotation in FooId or not makes no difference in EL.
Please pay attention to Foo.postAddress relationship's @JoinColumn annotations which both were defined with insertable = false, updatable = false to indicate that the embedded ID properties to be the writable ones.
Note, I'm not looking for a solution that works, I can get that simply be deleting the read-only insertable = false, updatable = false stuff. The question however is why the above mappings fail in EclipseLink. Hibernate has no problems with the code. Furthermore the following, functionally equivalent code - which works without problems in EL - using JPA 2.0 @IdClass derived identities looks like:
@Entity
@Table(name = "PostAddresses")
@IdClass(value = PostAddressId.class)
public class PostAddress implements Serializable
{
@Id
@Column(name = "contact_id")
private Integer contactId;
@Id
@Column(name = "ordinal_nbr")
private Integer ordinalNbr = 1;
...
}
public class PostAddressId implements Serializable
{
private Integer contactId;
private Integer ordinalNbr = 1;
...
}
@Entity
@Table(name = "Foos")
@IdClass(value = FooId.class)
public class Foo implements Serializable
{
@Id
@OneToOne
@JoinColumns(value = {
@JoinColumn(name = "contact_id", referencedColumnName = "contact_id", insertable = false, updatable = false),
@JoinColumn(name = "ordinal_nbr", referencedColumnName = "ordinal_nbr", insertable = false, updatable = false)
})
private PostAddress postAddress;
...
}
public class FooId implements Serializable
{
private PostAddressId postAddress;
...
}
As you can see, Foo.postAddress also houses the read-only insertable = false, updatable = false attributes. Foo.postAddress gets mapped to FooId.postAddress by naming them the same. FooId then "points" to the writable PostAddressId class, which is no different from the @EmbeddedId logic above. At least the logic is no different to me. The only difference would be, because I'm using @EmbeddedId, that the writable @Column annotations end up in the @Embeddable class, which is valid. (JPA only allows simple annotations in @Embeddable classes @Embedded, @Basic, @Column, @Temporal, @Enum, and @Binary AFAIK.)
Is there anything I'm missing? What is it the JPA 2.0 spec says here?
Or is this a bug in EclipseLink?
Final note: this question is obviously similar to JPA (Hibernate, EclipseLink) mapping: why doesn't this code work (chain of 2 relationships using JPA 2.0, @EmbeddedId composite PK-FK)? but not the same. The linked question's EL has been fixed in the meantime and I am using that fixed version of EL, so the issues aren't the same. This one is solely about the read-only attributes on @JoinColumn in JPA 2.0 @EmbeddedId using nested ID classes + @MapsId in EclipseLink.