I'm asking and answering my own question, but i'm not assuming i have the best answer. If you have a better one, please post it!
Related questions:
I have a pair of classes which are in a simple aggregation relationship: any instance of one owns some number of instances of the other. The owning class has some sort of primary key of its own, and the owned class has a many-to-one to this class via a corresponding foreign key. I would like the owned class to have a primary key comprising that foreign key plus some additional information.
For the sake of argument, let's use those perennial favourites, Order and OrderLine.
The SQL looks something like this:
-- 'order' may have been a poor choice of name, given that it's an SQL keyword!
create table Order_ (
orderId integer primary key
);
create table OrderLine (
orderId integer not null references Order_,
lineNo integer not null,
primary key (orderId, lineNo)
);
I would like to map this into Java using JPA. Order is trivial, and OrderLine can be handled with an @IdClass. Here's the code for that - the code is fairly conventional, and i hope you'll forgive my idiosyncrasies.
However, using @IdClass involves writing an ID class which duplicates the fields in the OrderLine. I would like to avoid that duplication, so i would like to use @EmbeddedId instead.
However, a naive attempt to do this fails:
@Embeddable
public class OrderLineKey {
@ManyToOne
private Order order;
private int lineNo;
}
OpenJPA rejects the use of that as an @EmbeddedId. I haven't tried other providers, but i wouldn't expect them to succeed, because the JPA specification requires that the fields making up an ID class be basic, not relationships.
So, what can i do? How can i write a class whose key contains @ManyToOne relationship, but is handled as an @EmbeddedId?
I don't know of a way to do this which doesn't involve duplicating any fields (sorry!). But it can be done in a straightforward and standard way that involves duplicating only the relationship fields. The key is the @MapsId annotation introduced in JPA 2.
The embeddable key class looks like this:
@Embeddable
public class OrderLineKey {
private int orderId;
private int lineNo;
}
And the embedding entity class looks like this:
@Entity
public class OrderLine{
@EmbeddedId
private OrderLineKey id;
@ManyToOne
@MapsId("orderId")
private Order order;
}
The @MapsId annotation declares that the relationship field to which it is applied effectively re-maps a basic field from the embedded ID.