Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA query for getting the whole tree

I have a class which models all categories and they can be ordered hierarchically.

@Entity
@Table(name="categories")
public class Category {
    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="sequence")
    @SequenceGenerator(name="sequence", sequenceName="categories_pk_seq", allocationSize=1)
    @Column(name="id")
    private Long id;

    @Column
    private String name;

    @OneToOne
    @JoinColumn(name="idfather")
    private Category father;

}

I need to get all categories ordered hierarchically (I mean every father followed by its children and fathers ordered alphabetically on each level) as they could be made for example with PRIOR in oracle. Is it possible to do this with a JPA Query (not a SQL one)?

Thanks.

like image 562
Javi Avatar asked Apr 08 '10 08:04

Javi


2 Answers

The short answer is; no there isn't a standard way to do this.

You have to use native sql.

You may be able to extend the Oracle Hibernate Dialect and add some user function/extension to get hibernate to generate PRIOR or CONNECT BY clauses, but this will prevent your app from being strict JPA and database independent.

like image 161
Gareth Davis Avatar answered Oct 18 '22 23:10

Gareth Davis


First of all, assuming in this hierarchy a "father" can have more than one child, then the father field should be annotated as @ManyToOne.

If you have a field that all the members of a tree share, or if the tree contains the entire table, then it is possible to do it with JPA in an efficient way, though not through a single JPA query.

You simply need to prefetch all the members of the tree, and then traverse the tree:

@Entity
@Table(name="categories")
public class Category {
    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="sequence")
    @SequenceGenerator(name="sequence", sequenceName="categories_pk_seq", allocationSize=1)
    @Column(name="id")
    private Long id;

    @Column
    private String name;

    @ManyToOne
    @JoinColumn(name="idfather")
    private Category father;

    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, 
               fetch = FetchType.LAZY, 
               mappedBy = "idfather")
    @OrderBy("name")
    private List<Category> subCategories;
}

Notice the @OrderedBy annotation on the subCategories field.

Now you can get the entire tree by first loading all the categories into a jumbled list, just so that they'd all be in memory, and then traverse the tree.

public List<Category> getTree() {
    List<Category> jumbled = 
        entityManager.createQuery("from Category", Category.class).getResultList();

    Category root = null;
    for(Category category : jumbled) {
        if(category.getFather() == null) {
            root = category;
            break;
        }
    }

    List<Category> ordered = new ArratList<Category>();
    ordered.add(root);
    getTreeInner(root, ordered);
}

private void getTreeInner(Category father, List<Category> ordered) {
    for(Category child : father.getSubCategories()) {
        ordered.add(child);
        getTreeInner(child, ordered);
    }
}

I'm only learning JPA myself right now, so I may be missing something crucial, but this approach seems to work for me.

like image 35
itsadok Avatar answered Oct 18 '22 21:10

itsadok