One of my favorite new features in Vaadin 8 is the Grid::setDataProvider
method which makes it remarkably easy to implement lazy loading in Grid
s. In earlier versions of Vaadin, you had to implement a rather complex Container
interface. Vaadin 8 not only removes this interface, but also provides a modern API that takes advantage of many Java 8 features.
In this blog post, you will learn how to implement lazy loading to show a sortable list of people in a Grid
component by simply providing two lambda expressions. You can find two “flavors” of the example application: One for people who use Spring (Spring Boot), and one for people who use Java EE (CDI and WildFly Swarm):
- Spring example: https://github.com/alejandro-du/lazy-loading-spring-demo
- Java EE example: https://github.com/alejandro-du/lazy-loading-cdi-demo
What is lazy loading?
Lazy loading is a technique used to delay the loading of resources to the point where it’s actually needed. Say you have a database table with 1000 rows, and you want to show this data in a UI with space for 20 rows at a time. You can query just the first 20 rows and when the user scrolls down the list, then you query the next 20 rows, and so forth. If you fetch all the data from your data source, your app will consume more memory and will take longer to show something on the screen.
When you want to display tons of data, you would probably use Vaadin’s Grid
. By default, Grid
performs lazy loading between the client and the server, which already improves performance a lot. This means that you have lazy loading out-of-the-box between the Grid
component (in the browser) and the web server. How you query your data source is entirely up to you. If you try to fetch all the data at once, then the Grid
will only send what’s needed when it’s needed to the client. However, all the data will stay in the memory on the server-side. Fortunately, the Grid
class provides the mechanisms to allow you to query your data source in a lazy way. Let’s see how to do it!
The domain model
Suppose you have a domain class like the following:
public class Person { private Long id; private String firstName; private String lastName; private String email; ... getter and setters ... }
And a service class like this:
public class PersonService { public List<Person> findAll(int offset, int limit) { ... } public int count() { ... } }
The actual implementation of the methods depends on your specific persistence technologies. You can find a full implementation of the Person
and PersonService
classes for both Spring and Java EE applications using the links provided in the introduction.
Lazy loading functionality with Grid
You obviously need an instance of the PersonService
class and a new Grid
:
PersonService service = new PersonService(); Grid<Person> grid = new Grid<>(Person.class);
Notice how, in Vaadin 8, Grid
is parameterized with the domain type (Person
). Now, instead of using the infamous grid.setItems(service.findAll())
, you can use the setDataProvider
method and pass:
-
A lambda expression to return a “slice” of the data and,
-
Another lambda expression to return the total count of people in the data source.
You can delegate these operations to the service class:
grid.setDataProvider( (sortOrders, offset, limit) -> service.findAll(offset, limit).stream(), () -> service.count() );
Ordering functionality
If you read the previous snippet of code, you might have noticed the sortOrders
parameter in the first lambda expression. sortOrder
is a List
of QuerySortOrder
objects that you can use to tell your service how to order the data.
The PersonService::findAll
method accepts a Map
with String
keys representing the name of a Java property in the Person
class, and a Boolean
value telling whether to sort the property in ascending order. So, for this example, you have to translate between a List<QuerySortOrder>
and a Map<String, Boolean>
). Some Java should do the job:
Map<String, Boolean> sortOrder = new LinkedHashMap<>(); for (QuerySortOrder order : sortOrders) { sortOrder.put(order.getSorted(), SortDirection.ASCENDING.equals(order.getDirection())); }
As you can see, the QuerySortOrder::getSorted
method returns a string with the name of the Java property, and the SortOrder::getDirection
method (QuerySortOrder
extends SortOrder
) returns a value from the SortDirection
enum.
And that’s it! You can pass this map to the service, so the complete call to the setDataProvider
method would look like this:
grid.setDataProvider( (sortOrders, offset, limit) -> { Map<String, Boolean> sortOrder = sortOrders.stream() .collect(Collectors.toMap( sort -> sort.getSorted(), sort -> SortDirection.ASCENDING.equals( sort.getDirection()))); return service.findAll(offset, limit, sortOrder).stream(); }, () -> service.count() );