Close
Back

Lazy loading with Vaadin 8

One of my favorite new features in Vaadin 8 is the Grid::setDataProvider method which makes it remarkably easy to implement lazy loading in Grids. 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):

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:

  1. A lambda expression to return a “slice” of the data and,

  2. 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()
);

Learn more about Vaadin 8

Comments
Trackback URL:

Add Comment
You really know what your customers want. Exactly what I was planning for this week for our Vaadin 8 migration. Thanks a lot! :-)
Posted on 3/20/17 1:26 PM.
Hi, I am glad that Vaadin team simplify the coding for developpers. I tried lazy loading with grid and it works well except one point.

It seems that the data provider load all data without the need of scrolling the page. I don't know why but when my view is entered, I see traces from eclipseLync with two queries loading all data.

[EL Fine]: sql: 2017-03-20 12:05:31.982--ServerSession(6167002)--Connection(16516457)--Thread(Thread[http-nio-8080-exec-2,5,main])--SELECT id AS a1, name AS a2, description AS a3, version AS a4, created AS a5, updated AS a6 FROM Region ORDER BY name ASC LIMIT ?, ?
bind => [0, 40]
[EL Fine]: sql: 2017-03-20 12:05:32.607--ServerSession(6167002)--Connection(16516457)--Thread(Thread[http-nio-8080-exec-11,5,main])--SELECT id AS a1, name AS a2, description AS a3, version AS a4, created AS a5, updated AS a6 FROM Region ORDER BY name ASC LIMIT ?, ?
bind => [40, 3]

As you can see, the lazy loading is in place but all data is loaded without scrolling.

Is this a bug?
Posted on 3/22/17 12:46 PM.
Hi J-C,

How many entries there are in you DB? 42? To me it looks like there are two queries done against backend 0-39, 40-42. That might be normal if your grid is large, it then goes again to the backend to fetch some more rows for smooth UX.

cheers,
matti
Posted on 3/23/17 8:46 AM in reply to Jean-Christophe Fortier.
My grid is only displaying 10 rows at the screen. It occupies the half of the screen because there are some other components in my vertical layout. So only one query is supposed to be executed.

I compared the bahavior with lazy loading from viritin and Vaadin 7.7 and I only got 1 query at the beginning. Only when I scrolled down, the second query is executing.

Now with Vaadin 8 the two queries are executing at the beginning, which is strange to me beause lazy loading is not supposed to load all data at once. I only want to know if I have more entries, does data provider with load all data at the beginning again?
Posted on 3/23/17 12:19 PM in reply to Matti Tahvonen.
Hi , why service.count() should be returning a Integer and not Long?

Cheers
Fernando
Posted on 4/18/17 9:22 PM in reply to Jean-Christophe Fortier.
It's required by the Grid::setDataProvider method. I guess Grid is not designed to handle more than 2,147,483,647 rows (or even less???). Anyhow... that sounds like enough for many (or most???) use cases emoticon Cheers!
Posted on 4/19/17 2:53 PM in reply to Fernando Hasenclever.
You might want to follow and up-vote this issue: https://github.com/vaadin/framework/issues/8982
Posted on 4/19/17 2:54 PM in reply to Jean-Christophe Fortier.