Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jpa children size performance

Tags:

hibernate

jpa

In my web application I have an Object with a OneToMany relationship with Child. When selecting my Objects I execute the following query:

from Object o

and then i should print for every object how many children it has with

// Foreach
object.children.size()

Assuming the object has a lot of children (let's say 30'000); Is it a waste of resources calling size() or the ORM framework (in my case Hibernate) will take care of that without loading all the children?

like image 309
ianaz Avatar asked Feb 18 '23 15:02

ianaz


1 Answers

Using JPA (the standard):

  • Your @OneToMany relationship is by default lazy-loaded (i.e. default value for fetch=FetchType.LAZY). But calling Entity.getCollection().size() would trigger lazy loading to retrieve all of the child collection - so yes, it would be fairly slow, unless you needed to operate on all/most elements anyway. Note: for all (sane) implementations of JPA, this will NOT issue 30,000 seperate queries - it will issue one query that returns 30,000 rows in the result set.
  • If you need most elements or you wanted to cache in advance change to @OneToMany(fetch=FetchType.EAGER)
  • The common way do obtain data statistics without retrieving every single object is simply to use JPQL via EntityManager.getQuery()/getTypedQuery()/getNamedQuery() (or even SQL via getNativeQUery()). This is quite simple and highly performant:
int empSize = em.createQuery("SELECT SIZE(d.employees) FROM Department d")     
                 .getSingleResult();    
OR ALTERNATIVELY

// Pre-compiled query placed against entity class for highest performance   
@NamedQueries({
    @NamedQuery(name="Department.EmployeeSize",
                query="SELECT SIZE(d.employees) FROM Department"),
    ... // other named queries
})
@Entity
public class Department {

    ...

}

// Then to use the query:    
int empSize = em.createNamedQuery("Department.EmployeeSize", Integer.class)     
                 .getSingleResult();    
  • You can enable level 2 caching. JPA Caching Summary

    To statically configure level 2 caching:

Map propertiesMap = new HashMap();
// Valid values are ALL, NONE, ENABLE_SELECTIVE, DISABLE_SELECTIVE
propertiesMap.add("javax.persistence.sharedCache.mode", "ENABLE_SELECTIVE");
EntityManagerFactory = Persistence.createEntityManagerFactory("myPUName", propertiesMap);
ALTERNATIVELY use persistence.xml:

<persistence-unit name="EmployeeService">
    ...
    <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>

</persistence-unit>

Then mark which entities should be automatically cached in the level 2 cache:

@Cacheable(true)
@Entity
public class Employee {
    ...
}
  • Can also configure caching dynamically, as part of a particular query

Using Proprietary approach (e.g. Hibernate "Extra-Lazy" collections):

  • Same performance as simply issuing JPQL/SQL query
  • Save a couple of lines of code (@org.hibernate.annotations.LazyCollection( EXTRA ) annotation v @NamedQuery annotation and execution)
  • Not standard - JPA trained developers won't know about it
  • Not portable - can only be used with that vendor. There is an industry trend towards standard JPA, away from proprietary features. Many different JPA implementations are out there.
like image 56
Glen Best Avatar answered Feb 28 '23 10:02

Glen Best