ORM, JPA & Entity 3.0 - mapping complex recursive relationships to composite entities, with ease

This post shows a test of some JPA - Entity 3.0 features.

It is based on a typical scenario of recursive relationships, managing hierarchical product categories, but with an additional feature, the possibility to connect different categories and nodes from different categories into a network of "related product" categories.

Just to avoid any ambiguity, the "categories" I describe in this example must not be confused with product inventories, taxonomies, nor any canonical form of classification. They are just hierarchical/connectable breadcrumb categories that guide the users into their paths across different products. Their goal is to ease and at the same time maximize products accessibility, nothing else. I' ll probably write something about the differences between classification systems and facet/category/tag systems in a future post as its misunderstanding is a common source of severe modeling errors.

IMPORTANT: this is not a blueprint nor a "design solution", it's just a full working example of several JPA and Entity 3.0 features, for example I would think it twice before using many to many bidirectional relationship in a real world scenario and I wouldn't implement an API that returns an @Entity exposing any kind of bidirectional relationship to the outside, even if it is safely detached from its persistence context :).

The ease of use is impressive, the full hierarchies of catalogs and their connection networks (2 bidirectional relationships!) can be loaded with just one very simple JPQL query; products also could be loaded by the same query if I didn't set the Fetch.LAZY attribute to the catalogs-products relationships. Of course so much power is also so much danger, mapping becomes so easy and transparent that any JPQL query can become dangerous in terms of memory consumption, performances, etc. etc. There will be a lot of new work to do for DBA ;).

Anyway the only certain thing about JPA is that with very few lines of code and no boring XML it will let you manage very complex entity relationships that otherwise would have required thousands lines of code or probably would not have been feasible at all in practice.
I have always been sceptik about ORM but this time I was impressed by the power and simplicity of Entity 3.0 annotations. A technology like this could be very welcome to me because it's not object centric at all, it's neutral, it let me just map my data model into composite beans while an object centric ORM technology (like earlier EJB was) would force me to focus on too many details about object and application server technologies and about damn platform dependent enterprise patterns, completely missing the point of data quality and business rules.
On the other side the performance and scalability of this technology are all to be verified of course.
I had a lot of troubles with OpenJPA (version 1.0.1), it had bugs, it didn't work as I expected with recursive/reflexive relationships while TopLink (preview version 11.1.1.0.0) had no issue and worked just the way I was expecting but probably it will have its own bugs too, the technology is still young and the standard doesn't sound yet stable (for example some important default configuration values are still left arbitrary to proprietary choices so there is no portability).
The worst issue of JPA is that there are big missing points in its spec, the biggest one being the persistence context caching layer, how can we design portable systems if the entire implementation of the persistence context is left to providers and the only exposed API concerning persistence context caching is a clear() method that clears the entire context and is totally unsafe? How do we know what to load EAGER and what LAZY if the 'in memory' management of entities can completely change between different implementations? The idea that the developer can define the fetch strategy at development time without any knowledge and control of the persistency context memory model is kind weak, probably the fetching strategy should be dynamically managed by the persistency provider and should be transparent to the developer, but that would require a completely different architecture. Last but not least in my opinion JPA can be used only if the developer is left free to use all the other JEE technology too, for example the new entity model in some situations will require stateful session beans to achieve an "extended (stateful) persistence context"; if you think to use JPA (but also any other ORM technology!) like a standard 'framework' -one size fits all- ...well you are going to miss a lot and to risk high also. OK, I had enough comments for now...

The model diagrams:

The main test class, it prints out the category hierarchies and the network of related categories:

public class TestCategory {
    public static void main(String [] args){
        EntityManagerFactory emf=Persistence.createEntityManagerFactory("categories");
        EntityManager em=emf.createEntityManager();
        List <Category>parents=(List<Category>)em.createNamedQuery("getAllParentCategories").getResultList();
        em.close();
        for(Category c: categories){
            System.out.println(c);
            System.out.println("related categories: "+c.getRelatedCategories());
            System.out.println("relating categories: "+c.getRelatingCategories());
        }
    }
}

Executing the main:

[TopLink Info]: 2007.11.18 01:00:55.468--ServerSession(19551481)--TopLink, version: Oracle TopLink Essentials - 2.0 (Build b41-beta2 (03/30/2007))
[TopLink Info]: 2007.11.18 01:00:58.281--ServerSession(19551481)--file:/D:/Workspace_eclipse/Categories/build/classes/-categories login successful
Category: games[bicycles, dolls[barbies, cicciobello], videogames[consoles, portables, accessories[nintendo]]]
related categories: [Category: sport[cyiclism[bicycles, wearables[gloves, hats]]]]
relating categories: []
Category: sport[cyiclism[bicycles, wearables[gloves, hats]]]]
related categories: [Category: clothes[jackets, pants]]
relating categories: [Category: games[bicycles, dolls[barbies, cicciobello], videogames[consoles, portables, accessories[nintendo]]]
Category: clothes[jackets, pants]]
related categories: []
relating categories: [Category: sport[cyiclism[bicycles, wearables[gloves, hats]]]]

The Entity classes:

@NamedQueries({
    @NamedQuery(name="getAllParentCategories",
                        query="SELECT c FROM Category c WHERE c.parentCategory IS NULL"),
})

@Entity
@Table(name="CATEGORY")
public class Category {
    @Id 
    private int id; 
	
    @Column(name="parent_id")
    private int parentId;
	
    private String name;

    private String description;
	
    @ManyToMany(fetch=FetchType.EAGER)
    @JoinTable(name="related_category", joinColumns=@JoinColumn(name="id_efferent"),
                    inverseJoinColumns=@JoinColumn(name="id_afferent"))
    private List efferentRelatedCategories;
	
    @ManyToMany(mappedBy="efferentRelatedCategories",fetch=FetchType.EAGER)
    private List afferentRelatedCategories;
	
    @ManyToOne
    @JoinColumn(name="parent_id", insertable=false,updatable=false)
    private Category parentCategory;
	
    @OneToMany(mappedBy="parentCategory", fetch=FetchType.EAGER)
    private List subCategories;
	
    @ManyToMany(fetch=FetchType.LAZY) 
    @JoinTable(name="product_category", joinColumns=@JoinColumn(name="id_category"),
                    inverseJoinColumns=@JoinColumn(name="id_product"))
    private List products;
	
    public String getName(){
        return name;
    }
    public String getDescription(){
        return description;
    }
    public List getRelatedCategories(){
        return efferentRelatedCategories;
    }
    public List getRelatingCategories(){
        return afferentRelatedCategories;
    }
    public List getSubCategories(){
        return subCategories;
    }
    public Category getParentCategory(){
        return parentCategory;
    }

    //Products are Lazy Fetched so this method would return 
    //unexpected empty products if invoked when the Category instance is detached
    public List getProducts(){
        return products;
    }
	
    public String toString(){
        return 	(parentCategory==null?"Category: ":"")+this.name+(subCategories.size()>0? subCategories:"");
	}

    /*@PostLoad
    protected void prova(){
        for (Category c:subCategories)
            System.out.println(c.toString());
    }*/
}

@Entity
public class Product {
    @Id
    private int id;
    private String name;
    private String description;
    @ManyToMany(mappedBy="products", fetch=FetchType.LAZY) 
    private List category;
    public String getName(){
        return name;
    }
    public String getDescription(){
        return name;
    }
    public String getCategories(){
    	return name;
    }
}