12

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?

Community
  • 1
  • 1
Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
  • Could you explicitly state which class owns many of the other class? It keeps twisting my neurons. (What is an "oderline"?! Not the most explicit example) – Jonath P Nov 25 '20 at 10:10
  • @JonathP If you buy a hat and some gloves from an online shop, then you have one "order", with two "order lines" - one line for the hat, one line for the gloves. [It's a common term in commerce](https://stackoverflow.com/a/10367237/116639), but admittedly is not general knowledge! The order can exist without either of the lines (although an order with no lines would be strange), but the lines can not exist without the order, so we say that the order owns the order lines. – Tom Anderson Nov 28 '20 at 18:07

1 Answers1

21

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.

Here's the code for OrderId.

Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
  • 11
    What if the entity you want to refer to itself has a composite key? Can you use MapsId on multiple columns in the embedded ID? – Tom Anderson Jan 12 '12 at 09:50