13

I've here a database with a PERSON - ADDRESS - ADDRESS_TYPE relationship maintained by a triple join table PERSON_ADDRESS. The PERSON-ADDRESS relationship is effectively one-to-many.

PERSON

ID FIRSTNAME LASTNAME
-- --------- --------
1  John      Doe
2  Jane      Doe

ADDRESS

ID STREET               CITY
-- -------------------- -------------
1  Home Street 1        Hometown
2  Office Street 1      Officetown
3  Main Street 1        Maintown
4  Business Building 1  Businesstown

ADDRESS_TYPE

ID NAME
-- ---------------
1  Home Address
2  Office Address

PERSON_ADDRESS

PERSON_ID ADDRESS_TYPE_ID ADDRESS_ID
--------- --------------- ----------
1         1               1
1         2               2
2         1               3
2         2               4

For practical reasons, I'd like to have my Person entity to end up like:

public class Person {
    private Address homeAddress; // Insertable/updateable by ADDRESS_TYPE_ID=1
    private Address officeAddress; // Insertable/updateable by ADDRESS_TYPE_ID=2
}

Is this ever possible with JPA 2.0 annotations?

I've read the Map Key Columns chapter of the JPA wikibook and it seems that I have to use a @MapKeyJoinColumn, but it's not entirely clear to me how to successfully use it in this situation. I expected to see a @JoinColumn example along it, but it is absent in the code snippets in the wikibook.

If that's not possible with @MapKeyJoinColumn, then an alternative approach with help of maybe @MapKeyClass on a Map<AddressType, Address> is also welcome, as long as I can end up with a getHomeAddress() and getOfficeAddress() in the Person entity.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Are you using an enum for ADDRESS_TYPE? Because that would make sense, since you want compile-time fixed address types. – Bozho Oct 03 '10 at 18:51
  • @Bozho: I haven't implemented it fully yet, but if it's possible, using an `@Enumerated` would definitely have my preference, yes. – BalusC Oct 03 '10 at 18:57
  • aha. And what's your persistence provider? (Provider-specific annotations are allowed as a last resort, I guess) – Bozho Oct 03 '10 at 19:01
  • @Bozho: I prefer not to be provider dependent, but it's Eclipselink 2.0.1 as shipped with Glassfish 3.0.1. – BalusC Oct 03 '10 at 19:06
  • @BalusC: It's unclear is `PERSON`-`ADDRESS` one-to-many or many-to-many. – axtavt Oct 03 '10 at 19:30

1 Answers1

12

Assuming that you have two predefined AddressTypes when other can be added, the following approach with @MapKeyJoinColumn works:

public class AddressType {
    public static final AddressType HOME = new AddressType(1L, "Home");
    public static final AddressType OFFICE = new AddressType(2L, "Office");
    ...
    ... hashCode, equals based on id ...
}

public class Person {
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinTable(name = "PERSON_ADDRESS",
        joinColumns = @JoinColumn(name = "PERSON_ID"),
        inverseJoinColumns = @JoinColumn(name = "ADDRESS_ID"))
    @MapKeyJoinColumn(name = "ADDRESS_TYPE_ID")
    private Map<AddressType, Address> addresses = new HashMap<AddressType, Address>();
    ...
    public Address getHomeAddress() {
        return getAddress(AddressType.HOME);
    }

    public void setHomeAddress(Address a) {
        setAddress(AddressType.HOME, a);
    }
    ...

    public void setAddress(AddressType type, Address a) {
        if (a == null) {
            addresses.remove(type);
        } else {
            addresses.put(type, a);
        }    
    }

    public Address getAddress(AddressType type) {
        return addresses.get(type);
    }
}

So, you have predefined methods for predefined address types, when other types can be used via direct access to the map. orphanRemoval is used to implement setHomeAddress(null) behaviour. @ElementCollection wouldn't work here because it doesn't support join table.

axtavt
  • 239,438
  • 41
  • 511
  • 482
  • Oh boy, it works :) Thank you so much. I was also fiddling in the meanwhile, but I had the `@JoinTable` completely wrong. It makes also more sense now. I'm however curious if it is also possible with an `enum AddressType`. – BalusC Oct 03 '10 at 20:08
  • @BalusC: I think you can't use `enum` unless you change your DB schema. You can use either `enum` or entity mapped to `ADDRESS_TYPE` table. – axtavt Oct 03 '10 at 20:47
  • The DB schema is out of my control. Thank you anyway :) – BalusC Oct 03 '10 at 20:57