Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map custom collection in JPA?

I have problems in mapping custom collection with JPA (Hiberante provider). For example when I am using object with attribute

List<Match> matches;

with

<one-to-many name="matches">
    <cascade>
        <cascade-all />
    </cascade>
</one-to-many>

in my ORM file, it is allright; But if I replace "List matches;" by

private Matches matches;

,where "Matches" is defined like:

public class Matches extends ArrayList<Match> {

    private static final long serialVersionUID = 1L;
}

It produces following error:

Caused by: org.hibernate.AnnotationException: Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements: by.sokol.labs.jpa.MatchBox.matches

Thanks for your attention!

like image 483
Denis Avatar asked Jul 04 '10 10:07

Denis


2 Answers

You can, but you have to refer to it as one of the common collections - List or Set.

so:

private List matches = new Matches();

Why? Because Hibernate makes proxies to your collections to enable lazy loading, for example. So it creates PersistentList, PersistentSet and PersistentBag, which are List but aren't Matches. So, if you want to add additional methods to that collection - well, you can't.

Check this article for more details.

You have a solution, however. Don't use inheritance, use composition. You can, for example, add a method to your entity called getMatchesCollection() (in addition to the traditional getter), which looks like:

 public Matches getMatchesCollection() {
    return new Matches(matches);
 }

And your Matches class would look like (using google-collections' ForwardingList):

public class Matches extends ForwardingList {
    private List<Match> matches;
    public Matches(List<Match> matches) { this.matches = matches; }
    public List<Match> delegate() { return matches; }
    // define your additional methods
}

If you can't use google collections, simply define the ForwardingList yourself - it's calling all the methods of the underlying List

If you don't need any additional methods to operate on the structure, then don't define a custom collection.

like image 129
Bozho Avatar answered Nov 13 '22 15:11

Bozho


Hibernate requires persistent collection-valued fields to be declared as an interface type (because they will be replaced with Hibernate's implementation for lazy loading purposes). From the reference documentation:

6.1. Persistent collections

Hibernate requires that persistent collection-valued fields be declared as an interface type. For example:

public class Product {
    private String serialNumber;
    private Set parts = new HashSet();

    public Set getParts() { return parts; }
    void setParts(Set parts) { this.parts = parts; }
    public String getSerialNumber() { return serialNumber; }
    void setSerialNumber(String sn) { serialNumber = sn; }
}

The actual interface might be java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap or anything you like ("anything you like" means you will have to write an implementation of org.hibernate.usertype.UserCollectionType.)

Notice how the instance variable was initialized with an instance of HashSet. This is the best way to initialize collection valued properties of newly instantiated (non-persistent) instances. When you make the instance persistent, by calling persist() for example, Hibernate will actually replace the HashSet with an instance of Hibernate's own implementation of Set.

So your second approach is not possible, at least not the way you declared it. But to be honest, I don't really see the point.

like image 21
Pascal Thivent Avatar answered Nov 13 '22 16:11

Pascal Thivent