2016-11-09

Implementing sortable, filterable, paginated and lazy Primefaces DataTable with Spring Data

Introduction

JSF and Primefaces is a great solution for building web-based user interfaces. It enables you to implement your requirements in a user-friendly way with moderate efforts. Especially, the Primefaces Showcase offers you a decent way to take a peek at all the nice components complemented with numerous useful code examples. 

In this article I will elaborate on the DataTable component and describe how to factilitate Spring Data in order to handle huge datasets. 

The examples for this article will be based on Java 8 and the following libraries:

  • Spring Boot 1.4.1 
  • Spring Data JPA 1.12
  • JSF 2.2
  • Primefaces 6.0

Primefaces DataTable

Primefaces offers an extensive collection of numerous useful components. One of the most frequently used component --especially in CRUD applications-- is the DataTable. With DataTable you are able to design powerful data views with, among others, the following features:

  • Sorting (even multiple rows)
  • Filtering via header input for each column
  • Paging 
  • Selection columns
  • Sticky columns
  • Context menu 
  • and many more
Figure 1: DataTable in action
Figure 1 shows the DataTable component in action. You can sort individual columns by clicking on the header. Furthermore, you can filter the columns by typing within the input fields in the respective column header. On the top and bottom you can navigate through all available pages.

The corresponding xhtml for the user interface is as simple as this:

<h:body styleClass="login">
  <h:form id="form">
    <p:dataTable value="#{userBean.users}" var="user" rows="10"
      paginator="true"  paginatorTemplate="{RowsPerPageDropdown} {FirstPageLink}  
        {PreviousPageLink} {CurrentPageReport} {NextPageLink} {LastPageLink}"
      rowsPerPageTemplate="5,10,15" selectionMode="single" id="userTable">
      <p:column headerText="Id" sortBy="#{user.id}" filterBy="#{user.id}">
        <h:outputText value="#{user.id}" />
      </p:column>
      <p:column headerText="Login" sortBy="#{user.login}" filterBy="#{user.login}">   
        <h:outputText value="#{user.login}" />
      </p:column>
      <p:column headerText="Firstname" sortBy="#{user.firstName}" 
        filterBy="#{user.firstName}">
        <h:outputText value="#{user.firstName}" />
      </p:column>
      <p:column headerText="Lastname" sortBy="#{user.lastName}" 
        filterBy="#{user.lastName}">
        <h:outputText value="#{user.lastName}" />
      </p:column>
      <p:column headerText="DayOfBirth" sortBy="#{user.dayOfBirth}" 
        filterBy="#{user.dayOfBirth}">
        <h:outputText value="#{user.dayOfBirth}" />
      </p:column>
      <p:column headerText="Manager" sortBy="#{user.manager.lastName}" 
        filterBy="#{user.manager.lastName}">
        <h:outputText value="#{user.manager.lastName}" />
      </p:column>
    </p:dataTable>
   </h:form>
</h:body>

The datatable tag renders the aforesaid data table with the given columns Login, Firstname, Lastname, DayOfBirth and Manager. The value attribute points to the managed bean named userBean and the method getUsers will return the list of users to be rendered. The var attribute defines to store each user in a variable named user. Rows defines that the table should render at most 10 rows per page whereas the following attributes paginator, paginatorTemplate define the paging. For each column tag the sortBy respectively filterBy define to sort/filter by the specified attributes of a user.

The referenced managed bean looks like this:

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class UserBean {
  @Autowired
  private UserService userService;
  
  public List<User> getUsers() {
    return userService.findAll()
  }

}

Please notice that this is a Spring bean instead of a JSF ManagedBean. It is implicitly made available by the name userBean. It defines the getUsers() method to return all users from the database.

In my experience, you can really quickly design a user-friendly data view with this set of features. However, once the number of data items you are rendering is getting huge, you will face performance problems because the filtering, sorting and paging actions will always operate on the whole data set, i.e. you must first load all data items from database to do so.

Fortunately, Primefaces offers a solution to this challenge by making a DataTable lazy.

Lazy DataTable

To get a lazy DataTable, you must set the lazy attribute of the dataTable component to true and in your managed bean, instead of returning a list with the data items, you must return an instance of LazyDataModel. The following code snippet demonstrates this:

<p:dataTable lazy="true" ...>

If you do so you, you will have to, instead of returning a list of your data in the managed bean, return a data model which extends LazyDataModel. Thus, you need to adapt the managed bean as follows:

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class UserBean {
  @Autowired
  private UserService userService;
  private UserLazyDataModel users;
  @PostConstruct
  private void init() {
    this.users = new UserLazyDataModel(userService);
  }

  public UserLazyDataModel getUsers() {
    return users;
  }

}

As you can see I've introduced an attribute of type UserLazyDataModel instead of the List<User>. Let's have a look at this lazy data model class:

public class UserLazyDataModel extends LazyDataModel<User>{
  
  private final transient UserService userService;
  public UserLazyDataModel(UserService userService) {
    this.userService = userService;
  }

  @Override
  public List<User> load(int firstint pageSize, String sortField,
    SortOrder sortOrder, Map<String, Object> filters) {
    //TODO Implement this
  }

  @Override
  public List<User> load(int first, int pageSize, List<SortMeta> multiSortMeta
    Map<String, Object> filters) {
    //TODO Implement this

  }

}

You must override two methods: the first one is required for simple one-row-sorting and the second one is required if our data table implements multi-row-sorting (attribute sortMode="multiple" in dataTable; can be used with Command key + click on column header). The parameters of these methods are:
  • first - the first element to be shown (compare to OFFSET in SQL)
  • pageSize - how many elements shall be shown (compare to LIMIT in SQL)
  • sortField - The name of the field to sort by e.g. firstName
  • sortOrder - The direction of sort (ASCENDING, DESCENDING, UNSORTED
  • filters - A map with key value pairs; key = name of field and value = filter value.
  • multiSortMeta - A list of sort meta (each entry in this list contains sort order and sort field)

The load method will be called by Primefaces whenever the user navigates to another page, sorts or filters the data of the table. To be able to deal with huge data sets this task should be delegated to the underlying database. To achieve this, I will first elaborate on how to write a service that fulfils the given requirements. This is when Spring data comes into play.

Using Spring Data JPA for Paging, Sorting and Filtering 

In the following, I will explain the backend from service to repository. The service must provide an interface serving our requirements from the LazyDataModel.load() methods. The signature can be seen in the next example:

@Service
public class UserService {
  public Page<User> findByFilter(Map<String, String> filters, Pageable pageable) {
    //TODO Implement this
  }
}

This service implementation provides one method to find users by the given filter data. Hence, I've introduced the filters attribute of type Map. So far so good, but what about sorting and paging? This is covered by the second parameter pageable of type org.springframework.data.domain.Pageable. The pageable is a standard spring data container allowing to define multiple sort criteria and paging. The return type Page is also such a Spring data container which encapsulates the actual data enriched with paging meta data (for instance which page has been loaded and how big the page actually is). A clear advantage to rely on these classes is that you can pass them as arguments/return values to standard repository methods. This enables Spring's out-of-the-box paging and sorting.

Spring Repository

To understand paging and sorting you should have a look at the Spring repository, a class which encapsulates all data base operations for a single entity class (in this case User):

public interface UserRepository extends PagingAndSortingRepository<User, Long>, 
  JpaSpecificationExecutor<User>{
  public Page<User> findByLogin(String login, Pageable pageable);
}

Please notice, that the repository is just an interface. You don't have to (although you can indeed, if you need custom code) implement this interface yourself. Spring will do this for you (at runtime). For example, the findByLogin method shown here follows the Spring data naming conventions and thus will produce a database query which will load users by their login. Because this method defines an additional page parameter of type Pageable the result will be paged and returned in the Page container object.

The UserRepository interface extends two Spring data interfaces: PagingAndSortingRepository and JpaSpecificationExecutor. The first one will mark this interface as repository and provide standard methods such as findAll. The second interface will contribute more advanced methods allowing to parameterize methods such as findAll with so called Specifications. Specifications provide an abstraction layer on top of the JPA criteria API and help you to modularise your criteria queries into reusable combinable components. I will demonstrate the reuse of specifications later.

Service Implementation

Let's have an elaborate look on how to use specifications to implement the desired service.

@Service
public class UserService {

  @Autowired
  private UserRepository userRepository;

  public Page<User> findByFilter(Map<String, String> filters, Pageable pageable) {
    return userRepository.findAll(getFilterSpecification(filters), pageable);
  }
  private Specification<User> getFilterSpecification(
    Map<String, String> filterValues) {

    return (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder) -> {
      Optional<Predicate> predicate = filterValues.entrySet().stream()
        .filter(v -> v.getValue() != null && v.getValue().length() > 0)
        .map(entry -> {
          Path<?> path = root;
          String key = entry.getKey();
if (entry.getKey().contains(".")) {
           String[] splitKey = entry.getKey().split("\\.");
            path = root.join(splitKey[0]);
            key = splitKey[1];
          }
          return builder.like(path.get(key).as(String.class),
            "%" + entry.getValue() + "%");
        })
        .collect(Collectors.reducing((a, b) -> builder.and(a, b)));
      return predicate.orElseGet(() -> alwaysTrue(builder));
    };
  }

  private Predicate alwaysTrue(CriteriaBuilder builder) {
    return builder.isTrue(builder.literal(true));
  }

}

Ok stop. This needs quite some explanation. First, you will have noticed that I've injected the UserRepository interface into the service. Second, I've made use of the repository to implement findByFilter. I call the findAll method which takes as first parameter a specification and as second argument a pageable. This is a standard method provided by the JpaSpecificationExecutor interface. The return value matches the required Page<User> type. This looks quite simple. What is still left to do is to instantiate the specification in order to actually do the filtering based on the given filterValues map. The code for defining the specification object has been extracted into the getFilterSpecification method. This method returns, as expected, an instance of Specification. Specification is an interface with just one method:

  • Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
This is beneficial because it adheres to the concept of functional interfaces and consequently you can use a lambda expression instead of defining an anonymous inner class. The main task of the lambda is to operate on the filter values map. To process data in the map, stream() is called on the entry set. Then, filter() is used to remove empty values from the map. After that map() is called to transform the entries in the entry set into a stream of predicates.

Let's have a more elaborate look at what map() is actually doing. In the first part of the code, I deal with attributes that are not directly part of the user entity but come from other associated entities which have been mapped with @OneToMany or @ManyToOne). For example, one could have specified user.manager.name as the filter attribute key. In this case, the code extracts this first attribute after the dot and joins the respective entity.

Then, I build the desired like predicate by using the attribute path of the entity which can point to car.name or just login as a direct attribute of user.  The second argument specifies % + value + % as the value to be compare with the attribute described by path.

After the mapping process you will receive a stream of like predicates. What yo need, however, is a single predicate. Hence you reduce() the stream by combining each predicate in the stream with and (which returns a new predicate with the conjunction of both input arguments). Finally, this will combine all elements of the list into a single predicate.

The result of this stream modification is however not a Predicate but an Optional<Predicate> because our input filterValues map could have been empty or does not contain non-empty values. Similar to an SQL query, if you don't provide any where clause the query should return all elements. because no filtering takes place. Thus, I return the True predicate, if the optional is empty.

The great benefit of the implementation of this filtering in form of a specification is that you could have combined the specification with other specifications that you could have written. Instead of directly passing the specification into the findAll method you could have used org.springframework.data.jpa.domain.Specifications.where as the following snippet demonstrates:


public Page<User> findByFilter(Map<String, String> filters, Pageable pageable) {
  return  userRepository.findAll(
    where(getFilterSpecification(filters))
    .and(anotherSpecification), pageable);
 }

Really powerful, isn't it? Now let's finally have a look on how to integrate our service into the lazy data model.

Integration of Service into LazyDataModel


public class UserLazyDataModel extends LazyDataModel<User>{
  private final transient UserService userService;
  private static final SortOrder DEFAULT_SORT_ORDER = SortOrder.ASCENDING;
  private static final String DEFAULT_SORT_FIELD = "login";
  
  public UserLazyDataModel(UserService userService) {
    this.userService = userService;
  }
  @Override
  public List<User> load(int firstint pageSize, String sortField,
    SortOrder sortOrder,  Map<String, Object> filters) {
      Sort sort = null;
      if (sortField != null) {
        sort = new Sort(getDirection(
          sortOrder != null ? sortOrder : DEFAULT_SORT_ORDER), sortField);
      } else if (DEFAULT_SORT_FIELD != null) {
        sort = new Sort(getDirection(
          sortOrder != null ? sortOrder : DEFAULT_SORT_ORDER), DEFAULT_SORT_FIELD);
      }
      return filterAndSort(firstpageSizefilterssort);
  }

  @Override
  public List<User> load(int first, int pageSize, List<SortMeta> multiSortMeta
    Map<String, Object> filters) {
    
      Sort sort = new Sort(getDirection(DEFAULT_SORT_ORDER), DEFAULT_SORT_FIELD);
      if (multiSortMeta != null) {
        List<Order> orders = multiSortMeta.stream()
          .map(m -> new Order(getDirection(
            m.getSortOrder() != null ? m.getSortOrder() : DEFAULT_SORT_ORDER),
            m.getSortField()))
          .collect(Collectors.toList());
            sort = new Sort(orders);
      }
      return filterAndSort(first, pageSize, filters, sort);
  }

  private List<User> filterAndSort(int first, int pageSize,
    Map<String, Object> filters, Sort sort) {
      Map<String, String> filtersMap = filters.entrySet().stream()
        .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString()));
          Page<User> page = userService.findByFilter(filtersMap,
            new PageRequest(firstpageSize, pageSize, sort));
            this.setRowCount(((Number) page.getTotalElements()).intValue());
            this.setWrappedData(page.getContent());
    return page.getContent();
  }

  private static Direction getDirection(SortOrder order) {
    switch (order) {
    case ASCENDING:
      return Direction.ASC;
    case DESCENDING:
      return Direction.DESC;
    case UNSORTED:
    default:
      return null;
    }
  }

}

The code above mainly implements a mapping from Primefaces sorting and paging model into the Spring data one. The getDirection method on the bottom transforms Primefaces SortOrder into Spring Direction. The onLoad methods transform the given sort criteria into Spring Sort interface. The filterAndSort method finally calls the service method I have written and injects the Sort object and the paging data into the PageRequest object which implements Pageable. To match the List<User> return type the result, which is a Page, must be transformed. The list can easily be extracted from the content attribute of page and the row count is given by the totalElements meta data from Page.

Conclusion & Limitations

In this article I've demonstrated how to use the Primefaces DataTable component to render huge data sets by delegating sorting, filtering and paging to the database. I've used Spring data to implement this at the database level by creating a dynamic query with JPA Criteria API. An interesting aspect in the course of this implementation is the use of specifications because they facilitate the modularisation of your dynamic Criteria-API-based queries.

Please notice that in this example only one level of attribute navigation in filter values is supported. In order to support arbitrary levels of navigation one has to extend the dynamic attribute join in the filter UserService.getFilterSpecification to be applied recursively. 

I hope you found this article helpful. I am looking forward to your feedback! The code examples can be found here https://github.com/Javatar81/code-examples/tree/master/lazydatatable.

2016-06-20

Strategies for loading object graphs with JPA

In this article, I will describe and discuss the different strategies for loading object graphs with JPA 2.1. This should help you to choose the right strategy for your application. Firstly, I will explain how you can load your object graphs with JPA. Secondly, I will describe the pros and cons of the different approaches and when to use them.

As I've already mentioned, this article is all about object graphs, but before we delve into the topic I will briefly introduce JPA, explain what an object graph actually is and why it is important to choose the right strategy to load it. 

Java Persistence API (JPA)

Whenever you use such things as Java Persistence API with its most popular implementations Hibernate, EclipseLink or OpenJPA you probably try to solve the following problem. You have written an object-oriented application with Java and you want to store your data into a database. In Java, data is always represented by objects and their attributes, in contrast, databases (at least the relational ones) define concepts such as tables with rows and columns. The problem that must be solved is to map the objects to database tables and vice versa. This includes questions such as how to map inheritance, how to map primitive Java types to SQL data types and how to deal with identity of objects. This problem is exactly what an Object-Relational-Mapping (ORM) tool is solving for you and JPA provides a standardized API to access it. 

Object graphs 

As I've already mentioned, in Java, data will be represented by objects. Most probably your application will have something like a domain model, representing all the domain objects of the application. Domain object define attributes, getter- and setter methods to access these attributes and probably other more high-level methods to operate on the data stored in the attributes. Attributes may be of different data types. Primitive types such as int, long or double (of course also the wrapper types of the same) or more complex types such as address objects or other domain object types.

Example:

@Entity
@Table(name = "usr")
public class User {

   @Id
   private Long id;

   @OneToMany
   private Set<Car> cars;

   @OneToMany
   private Set<UserLogins> logins;
}

The class User has been mapped to the table named usr. It defines an attribute id of primitive (wrapper) type Long and two attributes of complex type Car as well as UserLogins. One User may have several Cars and several UserLogins. Both target types are domain objects mapped to the database (defining an identifier and at least the annotation @Entity). The classes UserLogins and Car will probably define their own attributes and some of them are pointing to other domain objects, too. As a consequence, when you have several domain objects / entities, you might be faced with a huge object graph and each object of a particular type is stored in a separate table: User objects are stored in the table usr, Car objects in the table car and so on. You can see this in the following UML class diagram:


In this example you can navigate from a User to a Car to its Axis and it Wheels. This is a navigation path of length 3. You can see that most of the UML associations between classes have multiplicity * and thus a User could have several Cars a Car could have several Axis and an Axis several Wheels. In this example the * will probably represent small numbers between 2-4. But imagine you had objects such as a calculation with several positions and a much longer navigation path. The number of objects will explode. Thus it is very important to load only the relevant parts of the object graph from database.

Static loading strategies

Static loading strategies define the to be loaded object graphs already at compile time via mappings (XML or annotations), specific queries or so called NamedEntityGraphs. I will distinguish application-wide strategies such as lazy loading or eager loading and operation-specific strategies such as explicit joins.

Lazy loading

JPA's default behaviour is that only the root object which is explicitly requested from database will be loaded. In contrast, all other objects in the graph will be lazy. They will be represented by a proxy and loaded whenever you access them. However, the prerequisite is that the entity manager session is still open. When is this session open? By default the session is bound to the transaction life cycle. That means if you don't have any transaction, the session will be closed after you've performed your database operation. The following test will exemplify this behaviour. It expects a LazyInitializationException.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { Config.class })
public class StaticFetchTest {

   @PersistenceContext
   private EntityManager entityManager;

   @Test(expected = LazyInitializationException.class)
   public void testFailLazyAccess() {
      User user = entityManager.find(User.class, 1L);
      // Lazy init exception cars is lazy but session closed
      user.getCars().stream().forEach(System.out::println);
   }
}

The reason for raising the exception is that the @PersistenceContext is by default set to type =  PersistenceContextType.TRANSACTION. In order to extend the lifetime of the session, you could put all the database operations which shall operate in this very same session into one common transaction. Of course you will hesitate to use a transaction although there is no write to the database which might cause performance penalties. And you are right. If you just use the transaction as session demarcation, you should set the attribute read-only to true. The next test method demonstrates this:

@Test
@Transactional(readOnly = true)
public void testLoadInTx() {
   User user = entityManager.find(User.class, 1L);
   // Cars are loaded on demand because entity manager is still open
   // because of the transaction
   user.getCars().stream().forEach(System.out::println);

}

The session is open as long as you don't leave the test method and thus JPA can load the objects you access on demand (proxied objects will get initialized). 

You are able to control the lifetime of the session. Let's assume you had an application with a controller layer, a service layer and a repository layer. You decide where to put your transaction: to the controller? to the service ? or to the repository? What strategy you choose depends on your application, but in general I think it is advantageous to put the transactions to the controller level. There is one exception from this rule: If your application runs controllers and services on separate machines and the service are consumed remotely via REST or SOAP, for example, then it makes no sense to put the transaction annotations to the controller because from there it won't be possible to initialise the proxies, because they run in different JVMs. In this case I would advise you to put the @Transaction annotation only to your services. Btw. you could also add transactions to controllers AND services (for example if the controllers are not the only consumer of the services). In this case, the transaction will be started in the controller and all subsequent service calls will join the ongoing transaction. If you call the service from a non-transactional application part the service will automatically start a new transaction.

A completely different strategy is to use extended sessions. With this strategy, sessions are open as long as possible and thus independent from the transaction's life time. What does as long as possible mean? To manage an open session you need to maintain state and how long this state can be maintained depends on the lifecycle of the object instance with the reference to the EntityManager instance. In a JEE application with Session Beans, for instance, you could use a Stateful Session Bean to manage this state. Let's have a look on how our test would look like with extended session:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { Config.class })
public class StaticFetchTest {

   @PersistenceContext(type = PersistenceContextType.EXTENDED)
   private EntityManager entityManagerExtended;

   @Test
   public void testLoadExtended() {
      User user = entityManagerExtended.find(User.class, 1L);
      // Cars are loaded on demand because entity manager is extended
      user.getCars().stream().forEach(System.out::println);
}

In this example the attribute entityManagerExtended is annotated with @PersistenceContext and its attribute type set to PersistenceContextType.EXTENDED. This enables extended session and although there is no transaction this test will run successfully.

Eager loading

The opposite to lazy loading is loading parts of the object graph instantly.  This is called eager loading and can be specified via  the object mapping. For each attribute of an entity pointing to another entity (and thus @OneToOne, @ManyToOne, @OneToMany or @ManyToMany) you can decide to load the target entities together with the source entity. If you want to load UserLogins whenever you load a User object, you would specify the following mapping:

@Entity
@Table(name = "usr")
public class User {

   @OneToMany(fetch = FetchType.EAGER)
   private Set<UserLogins> logins;
}

You set the attribute fetch to FetchType.EAGER which tells JPA to load the UserLogins instantly together with the User. So you can run the following test now:

@Test
public void testLoadEager() {
   User user = entityManager.find(User.class, 1L);
   // Logins are loaded because attribute is mapped eager
   user.getLogins().stream().forEach(System.out::println);
}

The advantage of this approach is that it is easy to configure and its impact is application-wide. However, this is also a drawback because you might not always want to load User objects with UserLogins. Still, if the probability is high you can define it as the default strategy for your application. Candidates for eager loading are in general objects which can be represented by UML composite associations. They are parent-child connections where the child lifecycle depends on the parent lifecycle.

In contrast to the application-wide configuration of the loading behaviour, JPA allows also to define the loading strategy on a per-operation-basis.

Explicit join

If you are using queries written in JPAQL (an object-oriented extension to SQL) you can join parts of the object graph via JPAQL's join keyword. But be careful, there are two join variants in JPAQL. The standard join keyword should be used when you would like to constrain the results by attributes of other objects from the graph than your root object. In this example we load only users who own a car with a particular license plate:

@Test(expected = LazyInitializationException.class)
public void testFailLazyAccessByExplicitJoinInJPAQL() {
   TypedQuery<User> loadUserQuery = entityManager
      .createQuery("select usr from User usr left outer join usr.cars cars where    
         cars.licensePlate = :licensePlate", User.class);
   loadUserQuery.setParameter("licensePlate", "HIBERNATE");
   User user = loadUserQuery.getSingleResult();
   user.getCars().stream().forEach(System.out::println);
}

As you can see this test will result in a LazyInitializationException although we have joined cars. The right keyword for using the explicit join loading strategy is to use fetch join keyword instead.

@Test
public void testInitByExplicitFetchJoinInJPAQL() {
   TypedQuery<User> loadUserQuery = entityManager
      .createQuery("select usr from User usr left outer join fetch usr.cars where usr.id =   
         :id", User.class);
   loadUserQuery.setParameter("id", 1L);
   User user = loadUserQuery.getSingleResult();
   user.getCars().stream().forEach(System.out::println);
}

Dynamic loading strategies

Dynamic loading strategies vary in the way how dynamic they actually are. In general, all those strategies allow you to decide at runtime which parts of the graph you want to load, whereas the aforementioned strategies do not support this because they are configured via mappings at compile time or via constant query strings.

Named Entity Graphs

One way to define several dynamic loading strategies is to use a NamedEntityGraph which can be configured as an annotation on top of an entity as follows:

@NamedEntityGraph(name = "user.cars", attributeNodes = @NamedAttributeNode("cars")
@Entity
@Table(name = "usr")
public class User {
}

An entity graph has two important attributes: a uniquely identifiable name and the attributeNodes which described the parts of the object graph to be loaded. The given graph defines that whenever a user is loaded its cars shall be fetched eagerly. But why is this dynamic? I have to define the graph as annotation and it cannot be changed at runtime. You should see how to use the graph. You can use it on a per-operation basis:

@Test
public void testInitCarsByEntityGraph() {
   User user = entityManager.find(User.class, 1L,    
      Collections.singletonMap("javax.persistence.fetchgraph",
         entityManager.getEntityGraph("user.cars")));
   user.getCars().stream().forEach(System.out::println);

}

There is a property called javax.persistence.fetchgraph allowing you to pass in a given entity graph to be used for the find operation. In this example we have used the find operation of the EntityManager. You can also set an entity graph when you are using a JPAQL query:

@Test
public void testInitByNamedEntityGraphInJPAQL() {
   TypedQuery<User> loadUserQuery = entityManager.createQuery("select usr from User usr 
      where usr.id = :id"User.class);
   loadUserQuery.setParameter("id", 1L);
   loadUserQuery.setHint("javax.persistence.fetchgraph",    
      entityManager.getEntityGraph("user.cars"));
   User user = loadUserQuery.getSingleResult();
   user.getCars().stream().forEach(System.out::println);

}

The entity graph in this example is passed in via the setHint method which is defined on TypedQuery.

You could also have defined several different entity graphs and choose the entity graph depending on some condition, or you could even allow to pass the name of the entity graph as a parameter for your repository or data access object methods. That is why I categorize entity graphs as dynamic.

However, named entity graphs are still limited because I can only use one entity graph per operation at the same time. It is not allowed to mix them. Hence, you could potentially face a huge number of different graphs representing all combinations of object paths to be loaded. If that is the case you should rather use dynamic entity graphs.

Dynamic Entity Graphs

Dynamic entity graphs can be instantiated at runtime. If your application must be very flexible in loading different graphs you could choose this concept. The next test case shows how you use it:

@Test
public void testInitDynamicEntityGraph() {
   EntityGraph<User> graph = entityManager.createEntityGraph(User.class);
   graph.addAttributeNodes("cars");
   User user = entityManager.find(User.class, 1L,     
      Collections.singletonMap("javax.persistence.fetchgraph"graph));
   user.getCars().stream().forEach(System.out::println);

}

In this example we create a new EntityGraph<User> via EntityManager.createEntityGraph. To this entity graph we add the attributes nodes cars. The instance is then passed in as javax.persistence.fetchgraph property again, similar to the named entity graph example.

Criteria API

The most powerful and flexible way to define queries with JPA is to use the JPA Criteria API. The criteria API allows you to define the whole query dynamically at runtime. A typical use case would be to define a search filter. Imagine you had different filter attributes and whenever you put a value into the filter form you want to add a test whether this attribute equals the given value to the where clause conjunction. The most obvious approach would be to heavily rely on String concatenation. But this is not type safe and most probably inefficient. Hence, you would rather make use of the JPA Criteria API as follows:


@Test
public void testInitByExplicitFetchJoinInJPACriteria() {
   CriteriaBuilder builder = entityManager.getCriteriaBuilder();
   CriteriaQuery<User> query = builder.createQuery(User.class);
   Root<User> root = query.from(User.class);
   root.fetch("cars", JoinType.LEFT);
   CriteriaQuery<User> criteriaQuery =  
      query.select(root).where(builder.and(builder.equal(root.get("id"), 1L)));
   User user = entityManager.createQuery(criteriaQuery).getSingleResult();
   user.getCars().stream().forEach(System.out::println);

}

In this example, you first create a CriteriaBuilder object. This object allows you to create a new CriteriaQuery object. Next, you create the query root. On this root object you call fetch on cars. Then you define what to select and how the where clause looks like. To make the query type safe you could also use a canonical meta model. This would allow you to replace the Strings "cars" and "id" with type safe expression such as root.get(User_.id) or root.get(User_.cars). In order to dynamically load certain objects from the graph you can add further parts of the graph for example root.fetch("cars", JoinType.LEFT).fetch("axis", JoinType.LEFT) by concatenating the fetch method calls.

Alternatively you can even combine named entity graphs or dynamic entity graphs with criteria API as follows:

@Test
public void testInitByNamedEntityGraphInJPACriteria() {
   CriteriaBuilder builder = entityManager.getCriteriaBuilder();
   CriteriaQuery<User> query = builder.createQuery(User.class);
   Root<User> root = query.from(User.class);
   CriteriaQuery<User> criteriaQuery
      query.select(root).where(builder.and(builder.equal(root.get("id"), 1L)));
   User user = entityManager.createQuery(criteriaQuery)
      .setHint("javax.persistence.fetchgraph"
         entityManager.getEntityGraph("user.cars")).getSingleResult();
   user.getCars().stream().forEach(System.out::println);

}

When to use which strategy

To sum up, I will briefly discuss the pros, cons of the presented strategies and a guideline to choose the appropriate strategy.

Lazy Loading

Pros:
  • You will only load objects that you access
  • You don't have to think about a concrete loading strategy
  • Your root object loads fast
Cons:
  • Might produce higher delay when you access objects which a long navigation path
  • You can't use SQL joins to load parts of your object graph, you will always produce several select statements 
  • You are not flexible because your strategy is global on mapping level
You should use it if
  • your frontend is running in the same JVM as your Hibernate backend
  • you can't anticipate which parts of your object graphs are needed in which situation
  • you want your application start quickly and distribute load time among subsequent user interactions
You should not use it if
  • your frontend calls your Hibernate backend remotely
  • it is clear that certain parts of the object graph must always be loaded
  • you can neglect application start up time and preload most of your data

Eager Loading

Pros: 
  • You load all data that you need instantly
  • You allow JPA to use the optimal fetching strategy (batch, joins, select)
  • You don't have to deal with closed sessions
Cons:
  • You need to think about which parts should be eager loaded and which not
  • You will have higher delay to load the root object from the graph because also other parts of the graph must be loaded
  • You will probably load unnecessary parts of the object graph
  • You are not flexible because your strategy is global on mapping level
You should use it if
  • it is clear which objects in the graph always belong together
  • you want to preload data to have quick access later on
You should not use it if
  • it is not clear which parts of the object graph are required 
  • your fetch graph is getting too big

Explicit join

Pros: 
  • You decide on operation level what to load, you probably won't load unnecessary objects
  • You can offer different variants of your operation (e.g. a lazy and an eager one) by using different join strategies for the same operation
Cons:
  • It is more complex to define what to load on query level
  • No separation of the loading strategy from the query making it less reusable because you have to define what to load for each query again and again, although you might have the same strategy you need to redefine it for the next query
You should use it if
  • for a certain operation, always the same data must be loaded
You should not use it if
  • the objects to be loaded vary strongly by the context from where your operation is called

Named Entity Graphs

Pros: 
  • You decide on operation level what to load, you probably won't load unnecessary objects
  • You can offer different variants of your operation by allowing to use different named entity graphs
  • Increases reusability because you can use the same graph for different operations
Cons:
  • Syntax is verbose if the graph gets complex
  • Named entity graphs cannot be combined, number of defined graphs may explode 
You should use it if
  • you have a few different loading strategies to be supported which can be reused for different operations in your repository/dao
You should not use it if
  • there are too many strategies or combinations of strategies

Dynamic Entity Graphs 

Pros: 
  • You decide on operation level what to load, you probably won't load unnecessary objects
  • The consumer of your repository/dao operation may decide completely on his/her own what to load, this will probably reduce the number of operations you need to offer in your repository/dao
Cons:
  • Complex to define
  • Difficult to reuse because they are only valid for a particular use case
You should use it if
  • there are many different loading strategies or combinations of strategies
You should not use it if
  • you only need a few strategies to offer in your dao/repositories

Criteria API

Pros: 
  • Allows you to write completely dynamic queries including dynamic loading strategies
  • Can be combined with entity graphs
Cons:
  • More verbose, difficult to read and complex to define
You should use it if
  • you have many variants of the same query
  • you start to build your queries using String concatenations
You should not use it if
  • you can implement the same with one static query

All examples used in this article can be found on GitHub https://github.com/Javatar81/code-examples/tree/master/jpa-object-graphs