diff options
author | Erik Lumme <erik@vaadin.com> | 2017-09-14 10:23:24 +0300 |
---|---|---|
committer | Erik Lumme <erik@vaadin.com> | 2017-09-14 10:23:24 +0300 |
commit | 0673bffb99a874f10455257701f8ca11dc241bc5 (patch) | |
tree | cec2b8748705c4ddec7a118736c501dc27145cb5 | |
parent | 872d489506c1b1108c4e99134f0473c4b4b987fa (diff) | |
download | vaadin-framework-0673bffb99a874f10455257701f8ca11dc241bc5.tar.gz vaadin-framework-0673bffb99a874f10455257701f8ca11dc241bc5.zip |
Migrate LazyQueryContainer
-rw-r--r-- | documentation/articles/LazyQueryContainer.asciidoc | 462 | ||||
-rw-r--r-- | documentation/articles/contents.asciidoc | 5 |
2 files changed, 467 insertions, 0 deletions
diff --git a/documentation/articles/LazyQueryContainer.asciidoc b/documentation/articles/LazyQueryContainer.asciidoc new file mode 100644 index 0000000000..54e561fa50 --- /dev/null +++ b/documentation/articles/LazyQueryContainer.asciidoc @@ -0,0 +1,462 @@ +[[lazy-query-container]] +Lazy query container +-------------------- + +[[when-to-use-lazy-query-container]] +When to Use Lazy Query Container? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Typical usage scenario is browsing a large persistent data set in Vaadin +Table. LQC minimizes the complexity of the required custom +implementation while retaining all table features like sorting and lazy +loading. LQC delegates sorting of the data set to the backend data store +instead of sorting in memory. Sorting in memory would require entire +data set to be loaded to application server. + +[[what-is-lazy-loading]] +What is Lazy Loading? +~~~~~~~~~~~~~~~~~~~~~ + +In this context lazy loading refers to loading items to table on demand +in batches instead of loading the entire data set to memory at once. +This is useful in most business applications as row counts often range +from thousands to millions. Loading more than few hundred rows to memory +often causes considerable delay in page response. + +[[getting-started]] +Getting Started +~~~~~~~~~~~~~~~ + +To use LQC you need to get the add-on from add-ons page and drop it to +your projects WEB-INF/lib directory. After this you can use existing +query factory (`JpaQueryFactory`), extend `AbstractBeanQuery` or proceed to +implement custom `Query` and `QueryFactory`. Finally you need to instantiate +`LazyQueryContainer` and give your query factory as constructor parameter. + +[[how-to-use-lazy-query-container-with-table]] +How to Use Lazy Query Container with Table? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +LQC is implementation of Vaadin Container interface. Please refer to +Book of Vaadin for usage details of Table and Container implementations +generally. + +[[how-to-use-entitycontainer]] +How to Use EntityContainer +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`EntityContainer` is specialized version `LazyQueryContainer` allowing easy +use of JPA as persistence layer and supports defining where criteria and +corresponding parameter map in addition to normal `LazyQueryContainer` +features: + +[source,java] +.... +entityContainer = new EntityContainer<Task>(entityManager, true, Task.class, 100, + new Object[] { "name" }, new boolean[] { true }); +entityContainer.addContainerProperty("name", String.class, "", true, true); +entityContainer.addContainerProperty("reporter", String.class, "", true, true); +entityContainer.addContainerProperty("assignee", String.class, "", true, true); +whereParameters = new HashMap<String, Object>(); +whereParameters.put("name", nameFilter); +entityContainer.filter("e.name=:name", whereParameters); + +table.setContainerDataSource(entityContainer); +.... + +[[how-to-use-beanqueryfactory]] +How to Use BeanQueryFactory +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`BeanQueryFactory` and `AbstractBeanQuery` are used to implement queries +saving and loading JavaBeans. + +The `BeanQueryFactory` is used as follows with the Vaadin table. Usage of +`queryConfiguration` is optional and enables passing objects to the +constructed queries: + +[source,java] +.... +Table table = new Table(); +BeanQueryFactory<TaskBeanQuery> queryFactory = new + BeanQueryFactory<TaskBeanQuery>(TaskBeanQuery.class); + +Map<String,Object> queryConfiguration = new HashMap<String,Object>(); +queryConfiguration.put("taskService",new TaskService()); +queryFactory.setQueryConfiguration(queryConfiguration); + +LazyQueryContainer container = new LazyQueryContainer(queryFactory,50); +table.setContainerDataSource(container); +.... + +Here is a simple example of `AbstractBeanQuery` implementation: + +[source,java] +.... +public class TaskBeanQuery extends AbstractBeanQuery<Task> { + + public TaskBeanQuery(QueryDefinition definition, + Map<String, Object> queryConfiguration, Object[] sortPropertyIds, + boolean[] sortStates) { + super(definition, queryConfiguration, sortPropertyIds, sortStates); + } + + @Override + protected Task constructBean() { + return new Task(); + } + + @Override + public int size() { + TaskService taskService = + (TaskService)queryConfiguration.get("taskService"); + return taskService.countTasks(); + } + + @Override + protected List<Task> loadBeans(int startIndex, int count) { + TaskService taskService = + (TaskService)queryConfiguration.get("taskService"); + return taskService.loadTasks(startIndex, count, sortPropertyIds, sortStates); + } + + @Override + protected void saveBeans(List<Task> addedTasks, List<Task> modifiedTasks, + List<Task> removedTasks) { + TaskService taskService = + (TaskService)queryConfiguration.get("taskService"); + taskService.saveTasks(addedTasks, modifiedTasks, removedTasks); + } +} +.... + +[[how-to-implement-custom-query-and-queryfactory]] +How to Implement Custom Query and QueryFactory? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`QueryFactory` instantiates new query whenever sort state changes or +refresh is requested. Query can construct for example named JPA query in +constructor. Data loading starts by invocation of `Query.size()` method +and after this data is loaded in batches by invocations of +`Query.loadItems()`. + +Please remember that the idea is to load data in batches. You do not +need to load the entire data set to memory. If you do that you are +better of with some other container implementation like +`BeanItemContainer`. To be able to load database in batches you need your +storage to provide you with the result set size and ability to load rows +in batches as illustrated by the following pseudo code: + +[source,java] +.... +int countObjects(SearchCriteria searchCriteria); +List<Object> getObjects(SearchCriteria searchCriteria, int startIndex, int batchSize); +.... + +Here is simple read only JPA example to illustrate the idea. You can +find further examples from add-on page. + +[source,java] +.... +package com.logica.portlet.example; + +import javax.persistence.EntityManager; + +import org.vaadin.addons.lazyquerycontainer.Query; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; +import org.vaadin.addons.lazyquerycontainer.QueryFactory; + +public class MovieQueryFactory implements QueryFactory { + + private EntityManager entityManager; + private QueryDefinition definition; + + public MovieQueryFactory(EntityManager entityManager) { + super(); + this.entityManager = entityManager; + } + + @Override + public void setQueryDefinition(QueryDefinition definition) { + this.definition = definition; + } + + @Override + public Query constructQuery(Object[] sortPropertyIds, boolean[] sortStates) { + return new MovieQuery(entityManager,definition,sortPropertyIds,sortStates); + } +} +.... + +[source,java] +.... +package com.logica.portlet.example; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.EntityManager; + +import org.vaadin.addons.lazyquerycontainer.Query; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; + +import com.logica.example.jpa.Movie; +import com.vaadin.data.Item; +import com.vaadin.data.util.BeanItem; + +public class MovieQuery implements Query { + + private EntityManager entityManager; + private QueryDefinition definition; + private String criteria = ""; + + public MovieQuery(EntityManager entityManager, + QueryDefinition definition, + Object[] sortPropertyIds, + boolean[] sortStates) { + super(); + this.entityManager = entityManager; + this.definition = definition; + + for(int i=0;i<sortPropertyIds.length;i++) { + if(i==0) { + criteria = " ORDER BY"; + } else { + criteria+ = ","; + } + criteria += " m." + sortPropertyIds[i]; + if(sortStates[i]) { + criteria += " ASC"; + } + else { + criteria += " DESC"; + } + } + } + + @Override + public Item constructItem() { + return new BeanItem<Movie>(new Movie()); + } + + @Override + public int size() { + javax.persistence.Query query = entityManager. + createQuery("SELECT count(m) from Movie as m"); + return (int)((Long) query.getSingleResult()).longValue(); + } + + @Override + public List<Item> loadItems(int startIndex, int count) { + javax.persistence.Query query = entityManager. + createQuery("SELECT m from Movie as m" + criteria); + query.setFirstResult(startIndex); + query.setMaxResults(count); + + List<Movie> movies=query.getResultList(); + List<Item> items=new ArrayList<Item>(); + for(Movie movie : movies) { + items.add(new BeanItem<Movie>(movie)); + } + + return items; + } + + @Override + public void saveItems(List<Item> addedItems, List<Item> modifiedItems, + List<Item> removedItems) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean deleteAllItems() { + throw new UnsupportedOperationException(); + } +} +.... + +[[how-to-implement-editable-table]] +How to Implement Editable Table? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First you need to implement the `Query.saveItems()` method. After this you +need to set some of the properties editable in your items and set table +in editable mode as well. After user has made changes you need to call +`container.commit()` or `container.discard()` to commit or rollback +respectively. Please find complete examples of table handing and +editable JPA query from add-on page. + +[[how-to-use-debug-properties]] +How to Use Debug Properties? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +LQC provides set of debug properties which give information about +response times, number of queries constructed and data batches loaded. +To use these properties the items used need to contain these properties +with correct ids and types. If you use dynamic items you can defined +them in the query definition and add them on demand in the query +implementation. + +[source,java] +.... +container.addContainerProperty(LazyQueryView.DEBUG_PROPERTY_ID_QUERY_INDEX, Integer.class, 0, true, false); +container.addContainerProperty(LazyQueryView.DEBUG_PROPERTY_ID_BATCH_INDEX, Integer.class, 0, true, false); +container.addContainerProperty(LazyQueryView.DEBUG_PROPERTY_ID_BATCH_QUERY_TIME, Integer.class, 0, true, false); +.... + +[[how-to-use-row-status-indicator-column-in-table]] +How to Use Row Status Indicator Column in Table? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When creating editable tables LCQ provides +`QueryItemStatusColumnGenerator` which can be used to generate the status +column cells to the table. In addition you need to have the status +property in your items. If your items respect the query definition you +can implement this as follows: + +[source,java] +.... +container.addContainerProperty(LazyQueryView.PROPERTY_ID_ITEM_STATUS, + QueryItemStatus.class, QueryItemStatus.None, true, false); +.... + +[[how-to-use-status-column-and-debug-columns-with-beans]] +How to Use Status Column and Debug Columns with Beans +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Here is example query implementation which shows how JPA and beans can +be used together with status and debug properties: + +[source,java] +.... +package org.vaadin.addons.lazyquerycontainer.example; + +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.EntityManager; + +import org.vaadin.addons.lazyquerycontainer.CompositeItem; +import org.vaadin.addons.lazyquerycontainer.Query; +import org.vaadin.addons.lazyquerycontainer.QueryDefinition; + +import com.vaadin.data.Item; +import com.vaadin.data.util.BeanItem; +import com.vaadin.data.util.ObjectProperty; + +public class TaskQuery implements Query { + + private EntityManager entityManager; + private QueryDefinition definition; + private String criteria=" ORDER BY t.name ASC"; + + public TaskQuery(EntityManager entityManager, QueryDefinition definition, + Object[] sortPropertyIds, boolean[] sortStates) { + super(); + this.entityManager = entityManager; + this.definition = definition; + + for(int i=0; i<sortPropertyIds.length; i++) { + if(i==0) { + criteria = " ORDER BY"; + } else { + criteria+ = ","; + } + criteria += " t." + sortPropertyIds[i]; + if(sortStates[i]) { + criteria += " ASC"; + } + else { + criteria += " DESC"; + } + } + } + + @Override + public Item constructItem() { + Task task=new Task(); + try { + BeanInfo info = Introspector.getBeanInfo( Task.class ); + for ( PropertyDescriptor pd : info.getPropertyDescriptors() ) { + for(Object propertyId : definition.getPropertyIds()) { + if(pd.getName().equals(propertyId)) { + pd.getWriteMethod().invoke(task, + definition.getPropertyDefaultValue(propertyId)); + } + } + } + } catch(Exception e) { + throw new RuntimeException("Error in bean property population"); + } + return toItem(task); + } + + @Override + public int size() { + javax.persistence.Query query = entityManager.createQuery( + "SELECT count(t) from Task as t"); + return (int)((Long) query.getSingleResult()).longValue(); + } + + @Override + public List<Item> loadItems(int startIndex, int count) { + javax.persistence.Query query = entityManager.createQuery( + "SELECT t from Task as t" + criteria); + query.setFirstResult(startIndex); + query.setMaxResults(count); + + List<Task> tasks=query.getResultList(); + List<Item> items=new ArrayList<Item>(); + for(Task task : tasks) { + items.add(toItem(task)); + } + return items; + } + + @Override + public void saveItems(List<Item> addedItems, List<Item> modifiedItems, + List<Item> removedItems) { + entityManager.getTransaction().begin(); + for(Item item : addedItems) { + entityManager.persist(fromItem(item)); + } + for(Item item : modifiedItems) { + entityManager.persist(fromItem(item)); + } + for(Item item : removedItems) { + entityManager.remove(fromItem(item)); + } + entityManager.getTransaction().commit(); + } + + @Override + public boolean deleteAllItems() { + throw new UnsupportedOperationException(); + } + + private Item toItem(Task task) { + BeanItem<Task> beanItem= new BeanItem<Task>(task); + + CompositeItem compositeItem=new CompositeItem(); + + compositeItem.addItem("task", beanItem); + + for(Object propertyId : definition.getPropertyIds()) { + if(compositeItem.getItemProperty(propertyId)==null) { + compositeItem.addItemProperty(propertyId, new ObjectProperty( + definition.getPropertyDefaultValue(propertyId), + definition.getPropertyType(propertyId), + definition.isPropertyReadOnly(propertyId))); + } + } + return compositeItem; + } + + private Task fromItem(Item item) { + return (Task)((BeanItem)(((CompositeItem)item).getItem("task"))).getBean(); + } +} +.... diff --git a/documentation/articles/contents.asciidoc b/documentation/articles/contents.asciidoc new file mode 100644 index 0000000000..02bddf5beb --- /dev/null +++ b/documentation/articles/contents.asciidoc @@ -0,0 +1,5 @@ += Community articles for Vaadin 7 + +[discrete] +== Articles +- link:LazyQueryContainer.asciidoc[Lazy query container] |