Wednesday, 22 August 2012

JPA @ElementCollection for Embeddables

The @ElementCollection entity is a mean to create one-to-many relationships between entities and embeddables, using Collection, List, Set and Map. This annotation must also be used for basic types.

For example:
@AutoProperty
@Entity
public class ReferringItem implements Serializable {

    @Id
    private long id;

    // Element collection is required for basic types
    // and embeddables. Such elements are stored in
    // a separate table.
    @ElementCollection
    private Collection<AnEmbeddable> myCollection
        = new ArrayList<AnEmbeddable>();

    // By default, data is fetched lazily (when
    // used for the first time). Making it eager
    // loads the data as soon as possible.
    @ElementCollection(fetch=FetchType.EAGER)
    private Set<Long> mySet = new HashSet<Long>();

    // When not using generics, one must specify
    // the collection type (i.e., target class)
    @ElementCollection(targetClass=String.class)
    private List myList = new ArrayList();

    // Element collection is required when the map
    // value is a basic type or an embeddable
    @ElementCollection
    private Map<String,AnEmbeddable> map
        = new HashMap<String,AnEmbeddable>();

    // Setter & Getter, Constructor... 

}
The following:
@AutoProperty
JPA.INSTANCE.clear();

ReferringItem ri = new ReferringItem();

ArrayList<AnEmbeddable> coll
    = new ArrayList<AnEmbeddable>();
coll.add(new AnEmbeddable("Coll1"));
coll.add(new AnEmbeddable("Coll2"));
ri.setMyCollection(coll);

Set<Long> set = new HashSet<Long>();
set.add(Long.MIN_VALUE);
set.add(33l);
ri.setMySet(set);

List list = new ArrayList();
list.add("aaa");
list.add("bbb");
ri.setMyList(list);

Map<String,AnEmbeddable> map
    = new HashMap<String,AnEmbeddable>();
map.put("prt", new AnEmbeddable("Map1"));
map.put("frd", new AnEmbeddable("Map2"));
ri.setMap(map);

JPA.INSTANCE.save(ri);
JPA.INSTANCE.clear();

ReferringItem retr = JPA.INSTANCE.get(
ReferringItem.class, ri.getId());
System.out.println("Source == Retrieved: " + (ri==retr));

System.out.println("Retrieved Collection:");
for (AnEmbeddable ae : retr.getMyCollection()) {
    System.out.println(ae);
}

System.out.println("Retrieved Set:");
for (Long l : retr.getMySet()) {
    System.out.println(l);
}

System.out.println("Retrieved List:");
for (Iterator it = retr.getMyList().iterator(); it.hasNext();) {
    System.out.println(it.next());
}

System.out.println("Retrieved Map:");
for (Map.Entry<String,AnEmbeddable> e : map.entrySet()) {
    System.out.println(e.getKey() + " - " + e.getValue());
}
Generates:
Source == Retrieved: false
Retrieved Collection:
AnEmbeddable{s: {Coll1}}
AnEmbeddable{s: {Coll2}}
Retrieved Set:
-9223372036854775808
33
Retrieved List:
aaa
bbb
Retrieved Map:
prt - AnEmbeddable{s: {Map1}}
frd - AnEmbeddable{s: {Map2}}
We make sure the retrieved object instance is not identical to the persisted source object. The retrieved object is retrieved from the database.

The above example is available from Github in the JPA directory. It relies on Pojomatic too. Some errors messages will be displayed because of a known and harmless issue.