Reply to comment

JPA Tutorial: Maps and Composite Keys

I wrestled with JPA again today, and came away with some hard earned knowledge, so I want to put this one up on the interwebs.

I am working with a Database that was created by, effectively, another product. It has a relationship of Contact -<> ContactItem where contact item has a PK of [ContactID, TypeID]. What I really wanted was for Contact to have a map on it of ContactItems index by the Integer typeID.

First off, Composite keys are a PITA in JPA anyway, and doing this with a Map collection type is even worse. So lets go over the important parts:

First, ContactItem. Here you need to use an @EmbeddedId with your two key fields. To get the select to work properly, though, you still need to @ManyToOne field. What you need to do is create the dup fields and make them read only on the parent object:

class ContactItem {

   @ManyToOne
    @JoinColumn(name = "contact", insertable=false, updatable=false) //NOTE THIS!
    private Contact contact;
    @EmbeddedId
    PK pk = new PK();
    private String value;
    @Column(name="type",insertable=false, updatable=false) //NB!
    private int type;

Next the nested inner type:

 @Embeddable
    public static class PK implements IsSerializable, Serializable
    {
        private int type;
        private long contact;

        //...
    }
}

Next, you need to make sure the relationships are maintained:

    public void setContact(Contact contact)
    {
        this.contact = contact;
        this.pk.contact = (contact == null || contact.getId() == null )? -1L : contact.getId();
    }

   
    public void setType(int type)
    {
        int type = pk.type;
        this.pk.type = type;
        this.type = type;
    }

Finally, make sure your parent class is using method level annotations (I didn't know you couldn't mix them until just today) and be sure to fix the relationships there -- this makes sure that after the phase 1 (insert of the parent object) runs, the FK part of the PK is updated:

   @OneToMany(mappedBy = "contact",   // this is the field on the ContactItem object
                       fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @MapKey(name = "type") // field on  ContactItem
    public Map getContactItemMap()
    {
        for (Entry entry : this.contactItemMap.entrySet())
        {
            entry.getValue().setContact(this);
            entry.getValue().setContactItemTypeId(entry.getKey());
        }
        return this.contactItemMap;
    }

Reply

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <img> <a> <em> <strong> <cite> <code> <ul> <ol> <hr> <li> <dl> <dt> <dd> <pre> <b> <h1> <h2> <h3> <blockquote>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
1 + 3 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.