Serverside Pagination with ZK, Spring Data MongoDB and Google Maps
Join the DZone community and get the full member experience.
Join For FreeIntroduction
Handling large data sets efficiently can be critical in creating a responsive application. An standard strategy is to offload the pagination of the data to the database known as "serverside pagination". Traditionally this would require writing a significant amount of boilerplate code. This article will combine ZK MVVM, Spring Data MongoDB and Google Maps to browse a zipcode dataset efficiently with very little code.
The sample sourcecode is available on Github at zkmongomaps and the application can be viewed running on the openshift cloud.
The Screen
The screen is a Google Maps component driven by a paginated listbox:
The screen renders simple Zipcode entities which are defined within the file Zipcode.java outlined below:
@Document public class Zipcode { @Id private String _id; private int pop = 0; private String state = null; private double[] loc; private String city; // getters, setters, constructors // expose mongo geospacial pair as simple properties for screen public double[] getLoc() { return loc; } public float getLng() { return (float) loc[0]; } public float getLat() { return (float) loc[1]; } public void setLat(float lat) { loc[1] = lat; }
The entity class is marked with Spring Data persistence annotations @Document and @Id. This marks the entity for mapping into the MongoDB document database and defines the document primary key. The remainder of the class defines simple properties that Spring Data MongoDb will map to documents using reflection.
Data Access
The data access layer is provided by a single file which defines the ZipcodeRepository interface. No implementation of the class is given within the sample sourcecode; the Spring Data MongoDB framework generates an implementation at runtime:
@Repository public interface ZipcodeRepository extends MongoRepository<Zipcode, String> { }
MongoRepository extends both CrudRepository and PagingAndSortingRepository for basic data access and paging behaviour. It is the generated class which provides all the CRUD and pagination logic used in the sample sourcecode including the findAll method used by the screen:
Page<T> findAll(Pageable pageable);
Better yet the Spring Data MongoDB framework can generate custom query logic based on carefully named method signatures. Within the sample sourcecode are junit tests which exercise basic and geospatial queries not used by the screen. For example adding the following methods signatures to the ZipcodeRepository interface will generate a query by state and a geospatial distance query on the loc attribute:
List<Zipcode> findByState(String state); List<Zipcode> findByLocNear(Point p, Distance d);
The geospatial capabilities of Spring Data Mongodb are covered in detail by Tobias Trelle's blog Spring Data Part 4: Geospatial Queries with MongoDB.
The Screen File
The screen is defined within a single ZUL page within the file index.zul. Here is the entire ZUL screen:
<?xml version="1.0" encoding="UTF-8"?> <?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?> <zk xmlns:n="native"> <window border="none" width="600px" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('org.github.simbo1905.zkmongogmaps.view.ZipcodeViewModel')"> <gmaps id="mymap" version="3.5" width="600px" height="450px" showSmallCtrl="true" lat="@load(vm.pickedZipcode ne null ? vm.pickedZipcode.lat : 51)" lng="@load(vm.pickedZipcode ne null ? vm.pickedZipcode.lng : 0)"> </gmaps> <vbox width="600px"> <listbox height="330px" model="@load(vm.zipcodes)" selectedItem="@save(vm.pickedZipcode)"> <listhead> <listheader label="Id" /> <listheader label="Name" /> <listheader label="State" /> <listheader label="Population" /> </listhead> <template name="model" var="zipcode"> <listitem> <listcell label="@load(zipcode._id)" /> <listcell label="@load(zipcode.city)" /> <listcell label="@load(zipcode.state)" /> <listcell label="@load(zipcode.pop)" /> </listitem> </template> </listbox> <paging pageSize="@load(vm.pageSize)" totalSize="@load(vm.totalSize)" activePage="@save(vm.activePage)" /> </vbox> </window> Fork this code at <n:a href="https://github.com/simbo1905/zkmongomaps">github.com</n:a> </zk>
The ZUL file makes use of the framework support for the MVVM pattern using ZK Bind. The framework binder org.zkoss.bind.BindComposer is applied to the window component at line 4. This activates databindings within the XML attributes which bind screen properties onto the serverside java code. The viewmodel of the page is set as an instance of the ZipCodeViewModel class and assigned the variable name vm at line 5. The details of the viewmodel and the databinding that drive the screen behaviours are detailed in the following sections.
The ViewModel
Here is the entire viewmodel class:
public class ZipcodeViewModel { @WireVariable protected ZipcodeRepository zipcodeRepository = null; int pageSize = 10; int activePage = 0; public long getTotalSize() { return zipcodeRepository.count(); } public Integer getPageSize() { return pageSize; } @NotifyChange("zipcodes") public void setActivePage(int activePage) { this.activePage = activePage; } public List<Zipcode> getZipcodes() { Pageable pageable = new PageRequest(activePage,pageSize); Page<Zipcode> page = zipcodeRepository.findAll(pageable); return Lists.newArrayList(page.iterator()); } protected Zipcode pickedZipcode = null; public Zipcode getPickedZipcode() { return pickedZipcode; } public void setPickedZipcode(Zipcode pickedZipcode) { this.pickedZipcode = pickedZipcode; } }
The viewmodel is the Presentation Model of the screen in the style of the MVVM design pattern. A detailed discussion of the design pattern is covered in the paper Implementing event-driven GUI patterns using the ZK Java AJAX framework. Within the sample sourcecode the viewmodel mediates the screens interaction with the zipcodeRepository and holds the domain state of the screen. The zipcodeRepository is injected by ZK in response to the @WireVariable annotation. The ZUL file configures the ZK Spring DelegatingVariableResolver as the variable resolver. This causes ZK to request the corresponding spring bean which is an instance of the generated data access class.
The XML contains @load databindings for the lat and lng properties of the gmaps component as highlighted below:
<gmaps lat="@load(vm.pickedZipcode ne null ? vm.pickedZipcode.lat : 51)" lng="@load(vm.pickedZipcode ne null ? vm.pickedZipcode.lng : 0)"> </gmaps>
These databindings are EL expression defining null-safe bindings onto the corresponding properties of the vm.pickedZipcode. This is the entity within the pickedZipcode attribute of ZipcodeViewModel. Whenever the pickedZipcode attribute of the viewmodel is updated to be a new entity the lat and lng properties will be loaded into the gmaps component. This causes the google maps component to show the location of the zipcode.
The Listbox within the screen has a @save databinding applied to the selectedItem attribute. This causes the listbox to write to the pickedZipcode attribute of the viewmodel:
<listbox selectedItem="@save(vm.pickedZipcode)">
The listbox selectedItem databinding logically binds a user clicking in the listbox to select a new item with an update of the viewmodel. As the same viewmodel attribute is bound to the gmaps component this has the side effect of updating the location shown by the map.
The details of the listbox and the paging controls are highlighted below:
<listbox model="@load(vm.zipcodes)" selectedItem="@save(vm.pickedZipcode)"> <listhead> <listheader label="Id" /> <listheader label="Name" /> <listheader label="State" /> <listheader label="Population" /> </listhead> <template name="model" var="zipcode"> <listitem> <listcell label="@load(zipcode._id)" /> <listcell label="@load(zipcode.city)" /> <listcell label="@load(zipcode.state)" /> <listcell label="@load(zipcode.pop)" /> </listitem> </template> </listbox> <paging pageSize="@load(vm.pageSize)" totalSize="@load(vm.totalSize)" activePage="@save(vm.activePage)" />
The model attribute of the listbox is bound onto the zipcodes attribute of the viewmodel at line 13 of the XML. This binds the listbox contents to the data returned by the viewmodel getZipcodes() accessor.
The rendering of the entities is driven by the template element at line 21. This creates a loop variable zipcode over the model list returned from the viewmodel. Each cell of the listitem is then rendered using @load databindings onto the loop variable entity.
The viewmodel delegates the pagination logic to the zipcodeRepository using the attributes activePage and pageSize:
public List<Zipcode> getZipcodes() { Pageable pageable = new PageRequest(activePage,pageSize); Page<Zipcode> page = zipcodeRepository.findAll(pageable); return Lists.newArrayList(page.iterator()); }
The actual paging behaviour is driven by the @save databinding of the activePage paging attribute:
<paging pageSize="@load(vm.pageSize)" totalSize="@load(vm.totalSize)" activePage="@save(vm.activePage)" />
Updating the vm.activePage attribute has the effect of changing the page of data which will be returned by the getZipcodes() accessor. The behaviour for reloading the listbox when the activePage is updated is created with the @NotifyChange("zipcodes") annotation on the viewmodel setActivePage method:
@NotifyChange("zipcodes") public void setActivePage(int activePage) { this.activePage = activePage; }
The @NotifyChange annotation associates a write to one viewmodel attribute with a read from another. This causes the framework binder to re-render the listbox due to an implied change of the vm.zipcodes with any write to vm.activePage.
Summary
This article has taken a rapid run through of implementing efficient serverside pagination with ZK, Spring and MongoDB. The screen is a small Java class of around 40 lines backing a small XML file of around 30 lines. The database logic used by the screen was generated by a single line of Java. The logic to link screen controls to render the paginated data and the selected item was configured rather than coded. It is surprising just how little code is required to implement efficient
serverside pagination with the ZK MVVM and Spring Data frameworks.
Opinions expressed by DZone contributors are their own.
Trending
-
Implementing RBAC in Quarkus
-
Develop Hands-Free Weather Alerts To Ensure Safe Backpacking
-
Creating Scalable OpenAI GPT Applications in Java
-
Real-Time Made Easy: An Introduction to SignalR
Comments