Using JDBC with Lazy Query Container and FilteringTable

Introduction

Populating display tables from a database is a deceptively complicated operation, especially when mixing multiple techniques together. This page provides an example of one way to efficiently load data from a SQL database table into a filterable UI, using the Lazy Query Container and FilteringTable add-ons.

Note: Do not use the SQLContainer package. This is buggy and will have your database and garbage collector crunching in loops.

Query and QueryFactory implementation

The place to start is the Lazy Query Container’s (LQC) Query interface. This is where the interface with your database happens. This example access a database table with computer statistics. It’s read-only. How to log and access your JDBC connection differs in each environment; they are treated generically here. Only select imports are included.

Java

  1. import org.vaadin.addons.lazyquerycontainer.Query;
  2. import org.vaadin.addons.lazyquerycontainer.QueryDefinition;
  3. import org.vaadin.addons.lazyquerycontainer.QueryFactory;
  4. import com.vaadin.data.Container.Filter;
  5. import com.vaadin.data.Item;
  6. import com.vaadin.data.util.ObjectProperty;
  7. import com.vaadin.data.util.PropertysetItem;
  8. import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
  9. import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder;
  10. /**
  11. * Query for using the database's device-status table as a data source
  12. * for a Vaadin container (table).
  13. */
  14. public class DeviceStatusQuery implements Query {
  15. private static final Logger log = LoggerFactory.getLogger(DeviceStatusQuery.class);
  16. /**
  17. * The table column names. Use these instead of typo-prone magic strings.
  18. */
  19. public static enum Column {
  20. hostname, loc_id, update_when, net_ip, lan_ip, lan_mac, hardware,
  21. opsys, image, sw_ver, cpu_load, proc_count, mem_usage, disk_usage;
  22. public boolean is(Object other) {
  23. if (other instanceof String)
  24. return this.toString().equals(other);
  25. else
  26. return (this == other);
  27. }
  28. };
  29. public static class Factory implements QueryFactory {
  30. private int locId;
  31. /**
  32. * Constructor
  33. * @param locId - location ID
  34. */
  35. public Factory(int locId) {
  36. this.locId = locId;
  37. }
  38. @Override
  39. public Query constructQuery(QueryDefinition def) {
  40. return new DeviceStatusQuery(def, locId);
  41. }
  42. }//class Factory
  43. /////// INSTANCE ///////
  44. private String countQuery;
  45. private String fetchQuery;
  46. /** Borrow from SQLContainer to build filter queries */
  47. private StatementHelper stmtHelper = new StatementHelper();
  48. /**
  49. * Constructor
  50. * @param locId - location ID
  51. * @param userId - ID of user viewing the data
  52. */
  53. private DeviceStatusQuery(QueryDefinition def, int locId) {
  54. Build filters block List<Filter> filters = def.getFilters();
  55. String filterStr = null;
  56. if (filters != null && !filters.isEmpty())
  57. filterStr = QueryBuilder.getJoinedFilterString(filters, "AND", stmtHelper);
  58. // Count query
  59. StringBuilder query = new StringBuilder( "SELECT COUNT(*) FROM device_status");
  60. query.append(" WHERE loc_id=").append(locId);
  61. if (filterStr != null)
  62. query.append(" AND ").append(filterStr);
  63. this.countQuery = query.toString();
  64. // Fetch query
  65. query = new StringBuilder(
  66. "SELECT hostname, loc_id, update_when, net_ip, lan_ip, " +
  67. "lan_mac, hardware, opsys, image, sw_ver, cpu_load, " +
  68. "proc_count, mem_usage, disk_usage FROM device_status");
  69. query.append(" WHERE loc_id=").append(locId);
  70. if (filterStr != null)
  71. query.append(" AND ").append(filterStr);
  72. // Build Order by
  73. Object[] sortIds = def.getSortPropertyIds();
  74. if (sortIds != null && sortIds.length > 0) {
  75. query.append(" ORDER BY ");
  76. boolean[] sortAsc = def.getSortPropertyAscendingStates();
  77. assert sortIds.length == sortAsc.length;
  78. for (int si = 0; si < sortIds.length; ++si) {
  79. if (si > 0) query.append(',');
  80. query.append(sortIds[si]);
  81. if (sortAsc[si]) query.append(" ASC");
  82. else query.append(" DESC");
  83. }
  84. }
  85. else query.append(" ORDER BY hostname");
  86. this.fetchQuery = query.toString();
  87. log.trace("DeviceStatusQuery count: {}", this.countQuery);
  88. log.trace("DeviceStatusQuery fetch: {}", this.fetchQuery);
  89. }//constructor
  90. @Override
  91. public int size() {
  92. int result = 0;
  93. try (Connection conn = Database.getConnection()) {
  94. PreparedStatement stmt = conn.prepareStatement(this.countQuery);
  95. stmtHelper.setParameterValuesToStatement(stmt);
  96. ResultSet rs = stmt.executeQuery();
  97. if (rs.next()) result = rs.getInt(1);
  98. stmt.close();
  99. }
  100. catch (SQLException ex) {
  101. log.error("DB access failure", ex);
  102. }
  103. log.trace("DeviceStatusQuery size=\{}", result);
  104. return result;
  105. }
  106. @Override
  107. public List<Item> loadItems(int startIndex, int count) {
  108. List<Item> items = new ArrayList<Item>();
  109. try (Connection conn = Database.getConnection()) {
  110. String q = this.fetchQuery + " LIMIT " + count + " OFFSET " + startIndex;
  111. PreparedStatement stmt = conn.prepareStatement(q);
  112. stmtHelper.setParameterValuesToStatement(stmt);
  113. ResultSet rs = stmt.executeQuery();
  114. while (rs.next()) {
  115. PropertysetItem item = new PropertysetItem();
  116. // Include the data type parameter on ObjectProperty any time the value could be null
  117. item.addItemProperty(Column.hostname,
  118. new ObjectProperty<String>(rs.getString(1), String.class));
  119. item.addItemProperty(Column.loc_id,
  120. new ObjectProperty<Integer>(rs.getInt(2), Integer.class));
  121. item.addItemProperty(Column.update_when,
  122. new ObjectProperty<Timestamp>(rs.getTimestamp(3), Timestamp.class));
  123. item.addItemProperty(Column.net_ip,
  124. new ObjectProperty<String>(rs.getString(4)));
  125. item.addItemProperty(Column.lan_ip,
  126. new ObjectProperty<String>(rs.getString(5)));
  127. item.addItemProperty(Column.lan_mac,
  128. new ObjectProperty<String>(rs.getString(6)));
  129. item.addItemProperty(Column.hardware,
  130. new ObjectProperty<String>(rs.getString(7)));
  131. item.addItemProperty(Column.opsys,
  132. new ObjectProperty<String>(rs.getString(8)));
  133. item.addItemProperty(Column.image,
  134. new ObjectProperty<String>(rs.getString(9)));
  135. item.addItemProperty(Column.sw_ver,
  136. new ObjectProperty<String>(rs.getString(10)));
  137. item.addItemProperty(Column.cpu_load,
  138. new ObjectProperty<String>(rs.getString(11)));
  139. item.addItemProperty(Column.proc_count,
  140. new ObjectProperty<Integer>(rs.getInt(12)));
  141. item.addItemProperty(Column.mem_usage,
  142. new ObjectProperty<Integer>(rs.getInt(13)));
  143. item.addItemProperty(Column.disk_usage,
  144. new ObjectProperty<Integer>(rs.getInt(14)));
  145. items.add(item);
  146. }
  147. rs.close();
  148. stmt.close();
  149. }
  150. catch (SQLException ex) {
  151. log.error("DB access failure", ex);
  152. }
  153. log.trace("DeviceStatusQuery load {} items from {}={} found", count,
  154. startIndex, items.size());
  155. return items;
  156. } //loadItems()
  157. /**
  158. * Only gets here if loadItems() fails, so return an empty state.
  159. * Throwing from here causes an infinite loop.
  160. */
  161. @Override
  162. public Item constructItem() {
  163. PropertysetItem item = new PropertysetItem();
  164. item.addItemProperty(Column.hostname, new ObjectProperty<String>(""));
  165. item.addItemProperty(Column.loc_id, new ObjectProperty<Integer>(-1));
  166. item.addItemProperty(Column.update_when,
  167. new ObjectProperty<Timestamp>(new Timestamp(System.currentTimeMillis())));
  168. item.addItemProperty(Column.net_ip, new ObjectProperty<String>(""));
  169. item.addItemProperty(Column.lan_ip, new ObjectProperty<String>(""));
  170. item.addItemProperty(Column.lan_mac, new ObjectProperty<String>(""));
  171. item.addItemProperty(Column.hardware, new ObjectProperty<String>(""));
  172. item.addItemProperty(Column.opsys, new ObjectProperty<String>(""));
  173. item.addItemProperty(Column.image, new ObjectProperty<String>(""));
  174. item.addItemProperty(Column.sw_ver, new ObjectProperty<String>(""));
  175. item.addItemProperty(Column.cpu_load, new ObjectProperty<String>(""));
  176. item.addItemProperty(Column.proc_count, new ObjectProperty<Integer>(0));
  177. item.addItemProperty(Column.mem_usage, new ObjectProperty<Integer>(0));
  178. item.addItemProperty(Column.disk_usage, new ObjectProperty<Integer>(0));
  179. log.warn("Shouldn't be calling DeviceStatusQuery.constructItem()");
  180. return item;
  181. }
  182. @Override
  183. public boolean deleteAllItems() {
  184. throw new UnsupportedOperationException();
  185. }
  186. @Override
  187. public void saveItems(List<Item> arg0, List<Item> arg1, List<Item> arg2) {
  188. throw new UnsupportedOperationException();
  189. }
  190. }

Using the Query with FilteringTable

Now that we have our Query, we need to create a table to hold it. Here’s one of many ways to do it with FilteringTable.

Java

  1. import org.tepi.filtertable.FilterDecorator;
  2. import org.tepi.filtertable.numberfilter.NumberFilterPopupConfig;
  3. import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer;
  4. import com.vaadin.data.Property;
  5. import com.vaadin.server.Resource;
  6. import com.vaadin.shared.ui.datefield.Resolution;
  7. import com.vaadin.ui.DateField;
  8. import com.vaadin.ui.AbstractTextField.TextChangeEventMode;
  9. /**
  10. * Filterable table of device statuses.
  11. */
  12. public class DeviceStatusTable extends FilterTable {
  13. private final
  14. String[] columnHeaders = {"Device", "Site", "Last Report", "Report IP",
  15. "LAN IP", "MAC Adrs", "Hardware", "O/S", "Image", "Software", "CPU"
  16. "Load", "Processes", "Memory Use", "Disk Use"};
  17. /**
  18. * Configuration this table for displaying of DeviceStatusQuery data.
  19. */
  20. public void configure(LazyQueryContainer dataSource) {
  21. super.setFilterGenerator(new LQCFilterGenerator(dataSource));
  22. super.setFilterBarVisible(true);
  23. super.setSelectable(true);
  24. super.setImmediate(true);
  25. super.setColumnReorderingAllowed(true);
  26. super.setColumnCollapsingAllowed(true);
  27. super.setSortEnabled(true);
  28. dataSource.addContainerProperty(Column.hostname, String.class, null, true, true);
  29. dataSource.addContainerProperty(Column.loc_id, Integer.class, null, true, false);
  30. dataSource.addContainerProperty(Column.update_when, Timestamp.class, null, true, true);
  31. dataSource.addContainerProperty(Column.net_ip, String.class, null, true, true);
  32. dataSource.addContainerProperty(Column.lan_ip, String.class, null, true, true);
  33. dataSource.addContainerProperty(Column.lan_mac, String.class, null, true, true);
  34. dataSource.addContainerProperty(Column.hardware, String.class, null, true, true);
  35. dataSource.addContainerProperty(Column.opsys, String.class, null, true, true);
  36. dataSource.addContainerProperty(Column.image, String.class, null, true, true);
  37. dataSource.addContainerProperty(Column.sw_ver, String.class, null, true, true);
  38. dataSource.addContainerProperty(Column.cpu_load, String.class, null, true, true);
  39. dataSource.addContainerProperty(Column.proc_count, Integer.class, null, true, true);
  40. dataSource.addContainerProperty(Column.mem_usage, Integer.class, null, true, true);
  41. dataSource.addContainerProperty(Column.disk_usage, Integer.class, null, true, true);
  42. super.setContainerDataSource(dataSource);
  43. super.setColumnHeaders(columnHeaders);
  44. super.setColumnCollapsed(Column.lan_mac, true);
  45. super.setColumnCollapsed(Column.opsys, true);
  46. super.setColumnCollapsed(Column.image, true);
  47. super.setFilterFieldVisible(Column.loc_id, false);
  48. }
  49. @Override
  50. protected String formatPropertyValue(Object rowId, Object colId, Property<?> property) {
  51. if (Column.loc_id.is(colId)) {
  52. // Example of how to translate a column value
  53. return Hierarchy.getLocation(((Integer) property.getValue())).getShortName();
  54. } else if (Column.update_when.is(colId)) {
  55. // Example of how to format a value.
  56. return ((java.sql.Timestamp) property.getValue()).toString().substring(0, 19);
  57. }
  58. return super.formatPropertyValue(rowId, colId, property);
  59. }
  60. /**
  61. * Filter generator that triggers a refresh of a LazyQueryContainer
  62. * whenever the filters change.
  63. */
  64. public class LQCFilterGenerator implements FilterGenerator {
  65. private final LazyQueryContainer lqc;
  66. public LQCFilterGenerator(LazyQueryContainer lqc) {
  67. this.lqc = lqc;
  68. }
  69. @Override
  70. public Filter generateFilter(Object propertyId, Object value) {
  71. return null;
  72. }
  73. @Override
  74. public Filter generateFilter(Object propertyId, Field<?> originatingField) {
  75. return null;
  76. }
  77. @Override
  78. public AbstractField<?> getCustomFilterComponent(Object propertyId) {
  79. return null;
  80. }
  81. @Override
  82. public void filterRemoved(Object propertyId) {
  83. this.lqc.refresh();
  84. }
  85. @Override
  86. public void filterAdded(Object propertyId, Class<? extends Filter> filterType, Object value) {
  87. this.lqc.refresh();
  88. }
  89. @Override
  90. public Filter filterGeneratorFailed(Exception reason, Object propertyId, Object value) {
  91. return null;
  92. }
  93. }
  94. }

Put them together on the UI

Now we have our Container that reads from the database, and a Table for displaying them, lets put the final pieces together somewhere in some UI code:

Java

  1. final DeviceStatusTable table = new DeviceStatusTable();
  2. table.setSizeFull();
  3. DeviceStatusQuery.Factory factory = new DeviceStatusQuery.Factory(locationID);
  4. final LazyQueryContainer statusDataContainer = new LazyQueryContainer(factory,
  5. /*index*/ null, /*batchSize*/ 50, false);
  6. statusDataContainer.getQueryView().setMaxCacheSize(300);
  7. table.configure(statusDataContainer);
  8. layout.addComponent(table);
  9. layout.setHeight(100f, Unit.PERCENTAGE); // no scrollbar
  10. // Respond to row click
  11. table.addValueChangeListener(new Property.ValueChangeListener() {
  12. @Override
  13. public void valueChange(ValueChangeEvent event) {
  14. Object index = event.getProperty().getValue();
  15. if (index != nulll) {
  16. int locId = (Integer) statusDataContainer.getItem(index)
  17. .getItemProperty(DeviceStatusQuery.Column.loc_id).getValue();
  18. doSomething(locId);
  19. table.setValue(null); //visually deselect
  20. }
  21. }
  22. });

And finally, since we’re using SQLContainer‘s QueryBuilder, depending on your database you may need to include something like this once during your application startup:

Java

  1. import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder;
  2. import com.vaadin.data.util.sqlcontainer.query.generator.filter.StringDecorator;
  3. // Configure Vaadin SQLContainer to work with MySQL
  4. QueryBuilder.setStringDecorator(new StringDecorator("`","`"));