--- title: Using JDBC with Lazy Query Container and FilteringTable order: 3 layout: page --- [[using-jdbc-with-lazy-query-container-and-filteringtable]] = 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. [source,java] .... import org.vaadin.addons.lazyquerycontainer.Query; import org.vaadin.addons.lazyquerycontainer.QueryDefinition; import org.vaadin.addons.lazyquerycontainer.QueryFactory; import com.vaadin.data.Container.Filter; import com.vaadin.data.Item; import com.vaadin.data.util.ObjectProperty; import com.vaadin.data.util.PropertysetItem; import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper; import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder; /** * Query for using the database's device-status table as a data source * for a Vaadin container (table). */ public class DeviceStatusQuery implements Query { private static final Logger log = LoggerFactory.getLogger(DeviceStatusQuery.class); /** * The table column names. Use these instead of typo-prone magic strings. */ public static enum Column { hostname, loc_id, update_when, net_ip, lan_ip, lan_mac, hardware, opsys, image, sw_ver, cpu_load, proc_count, mem_usage, disk_usage; public boolean is(Object other) { if (other instanceof String) return this.toString().equals(other); else return (this == other); } }; public static class Factory implements QueryFactory { private int locId; /** * Constructor * @param locId - location ID */ public Factory(int locId) { this.locId = locId; } @Override public Query constructQuery(QueryDefinition def) { return new DeviceStatusQuery(def, locId); } }//class Factory /////// INSTANCE /////// private String countQuery; private String fetchQuery; /** Borrow from SQLContainer to build filter queries */ private StatementHelper stmtHelper = new StatementHelper(); /** * Constructor * @param locId - location ID * @param userId - ID of user viewing the data */ private DeviceStatusQuery(QueryDefinition def, int locId) { Build filters block List filters = def.getFilters(); String filterStr = null; if (filters != null && !filters.isEmpty()) filterStr = QueryBuilder.getJoinedFilterString(filters, "AND", stmtHelper); // Count query StringBuilder query = new StringBuilder( "SELECT COUNT(*) FROM device_status"); query.append(" WHERE loc_id=").append(locId); if (filterStr != null) query.append(" AND ").append(filterStr); this.countQuery = query.toString(); // Fetch query query = new StringBuilder( "SELECT hostname, loc_id, update_when, net_ip, lan_ip, " + "lan_mac, hardware, opsys, image, sw_ver, cpu_load, " + "proc_count, mem_usage, disk_usage FROM device_status"); query.append(" WHERE loc_id=").append(locId); if (filterStr != null) query.append(" AND ").append(filterStr); // Build Order by Object[] sortIds = def.getSortPropertyIds(); if (sortIds != null && sortIds.length > 0) { query.append(" ORDER BY "); boolean[] sortAsc = def.getSortPropertyAscendingStates(); assert sortIds.length == sortAsc.length; for (int si = 0; si < sortIds.length; ++si) { if (si > 0) query.append(','); query.append(sortIds[si]); if (sortAsc[si]) query.append(" ASC"); else query.append(" DESC"); } } else query.append(" ORDER BY hostname"); this.fetchQuery = query.toString(); log.trace("DeviceStatusQuery count: {}", this.countQuery); log.trace("DeviceStatusQuery fetch: {}", this.fetchQuery); }//constructor @Override public int size() { int result = 0; try (Connection conn = Database.getConnection()) { PreparedStatement stmt = conn.prepareStatement(this.countQuery); stmtHelper.setParameterValuesToStatement(stmt); ResultSet rs = stmt.executeQuery(); if (rs.next()) result = rs.getInt(1); stmt.close(); } catch (SQLException ex) { log.error("DB access failure", ex); } log.trace("DeviceStatusQuery size=\{}", result); return result; } @Override public List loadItems(int startIndex, int count) { List items = new ArrayList(); try (Connection conn = Database.getConnection()) { String q = this.fetchQuery + " LIMIT " + count + " OFFSET " + startIndex; PreparedStatement stmt = conn.prepareStatement(q); stmtHelper.setParameterValuesToStatement(stmt); ResultSet rs = stmt.executeQuery(); while (rs.next()) { PropertysetItem item = new PropertysetItem(); // Include the data type parameter on ObjectProperty any time the value could be null item.addItemProperty(Column.hostname, new ObjectProperty(rs.getString(1), String.class)); item.addItemProperty(Column.loc_id, new ObjectProperty(rs.getInt(2), Integer.class)); item.addItemProperty(Column.update_when, new ObjectProperty(rs.getTimestamp(3), Timestamp.class)); item.addItemProperty(Column.net_ip, new ObjectProperty(rs.getString(4))); item.addItemProperty(Column.lan_ip, new ObjectProperty(rs.getString(5))); item.addItemProperty(Column.lan_mac, new ObjectProperty(rs.getString(6))); item.addItemProperty(Column.hardware, new ObjectProperty(rs.getString(7))); item.addItemProperty(Column.opsys, new ObjectProperty(rs.getString(8))); item.addItemProperty(Column.image, new ObjectProperty(rs.getString(9))); item.addItemProperty(Column.sw_ver, new ObjectProperty(rs.getString(10))); item.addItemProperty(Column.cpu_load, new ObjectProperty(rs.getString(11))); item.addItemProperty(Column.proc_count, new ObjectProperty(rs.getInt(12))); item.addItemProperty(Column.mem_usage, new ObjectProperty(rs.getInt(13))); item.addItemProperty(Column.disk_usage, new ObjectProperty(rs.getInt(14))); items.add(item); } rs.close(); stmt.close(); } catch (SQLException ex) { log.error("DB access failure", ex); } log.trace("DeviceStatusQuery load {} items from {}={} found", count, startIndex, items.size()); return items; } //loadItems() /** * Only gets here if loadItems() fails, so return an empty state. * Throwing from here causes an infinite loop. */ @Override public Item constructItem() { PropertysetItem item = new PropertysetItem(); item.addItemProperty(Column.hostname, new ObjectProperty("")); item.addItemProperty(Column.loc_id, new ObjectProperty(-1)); item.addItemProperty(Column.update_when, new ObjectProperty(new Timestamp(System.currentTimeMillis()))); item.addItemProperty(Column.net_ip, new ObjectProperty("")); item.addItemProperty(Column.lan_ip, new ObjectProperty("")); item.addItemProperty(Column.lan_mac, new ObjectProperty("")); item.addItemProperty(Column.hardware, new ObjectProperty("")); item.addItemProperty(Column.opsys, new ObjectProperty("")); item.addItemProperty(Column.image, new ObjectProperty("")); item.addItemProperty(Column.sw_ver, new ObjectProperty("")); item.addItemProperty(Column.cpu_load, new ObjectProperty("")); item.addItemProperty(Column.proc_count, new ObjectProperty(0)); item.addItemProperty(Column.mem_usage, new ObjectProperty(0)); item.addItemProperty(Column.disk_usage, new ObjectProperty(0)); log.warn("Shouldn't be calling DeviceStatusQuery.constructItem()"); return item; } @Override public boolean deleteAllItems() { throw new UnsupportedOperationException(); } @Override public void saveItems(List arg0, List arg1, List arg2) { throw new UnsupportedOperationException(); } } .... 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. [source,java] .... import org.tepi.filtertable.FilterDecorator; import org.tepi.filtertable.numberfilter.NumberFilterPopupConfig; import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; import com.vaadin.data.Property; import com.vaadin.server.Resource; import com.vaadin.shared.ui.datefield.Resolution; import com.vaadin.ui.DateField; import com.vaadin.ui.AbstractTextField.TextChangeEventMode; /** * Filterable table of device statuses. */ public class DeviceStatusTable extends FilterTable { private final String[] columnHeaders = {"Device", "Site", "Last Report", "Report IP", "LAN IP", "MAC Adrs", "Hardware", "O/S", "Image", "Software", "CPU" "Load", "Processes", "Memory Use", "Disk Use"}; /** * Configuration this table for displaying of DeviceStatusQuery data. */ public void configure(LazyQueryContainer dataSource) { super.setFilterGenerator(new LQCFilterGenerator(dataSource)); super.setFilterBarVisible(true); super.setSelectable(true); super.setImmediate(true); super.setColumnReorderingAllowed(true); super.setColumnCollapsingAllowed(true); super.setSortEnabled(true); dataSource.addContainerProperty(Column.hostname, String.class, null, true, true); dataSource.addContainerProperty(Column.loc_id, Integer.class, null, true, false); dataSource.addContainerProperty(Column.update_when, Timestamp.class, null, true, true); dataSource.addContainerProperty(Column.net_ip, String.class, null, true, true); dataSource.addContainerProperty(Column.lan_ip, String.class, null, true, true); dataSource.addContainerProperty(Column.lan_mac, String.class, null, true, true); dataSource.addContainerProperty(Column.hardware, String.class, null, true, true); dataSource.addContainerProperty(Column.opsys, String.class, null, true, true); dataSource.addContainerProperty(Column.image, String.class, null, true, true); dataSource.addContainerProperty(Column.sw_ver, String.class, null, true, true); dataSource.addContainerProperty(Column.cpu_load, String.class, null, true, true); dataSource.addContainerProperty(Column.proc_count, Integer.class, null, true, true); dataSource.addContainerProperty(Column.mem_usage, Integer.class, null, true, true); dataSource.addContainerProperty(Column.disk_usage, Integer.class, null, true, true); super.setContainerDataSource(dataSource); super.setColumnHeaders(columnHeaders); super.setColumnCollapsed(Column.lan_mac, true); super.setColumnCollapsed(Column.opsys, true); super.setColumnCollapsed(Column.image, true); super.setFilterFieldVisible(Column.loc_id, false); } @Override protected String formatPropertyValue(Object rowId, Object colId, Property property) { if (Column.loc_id.is(colId)) { // Example of how to translate a column value return Hierarchy.getLocation(((Integer) property.getValue())).getShortName(); } else if (Column.update_when.is(colId)) { // Example of how to format a value. return ((java.sql.Timestamp) property.getValue()).toString().substring(0, 19); } return super.formatPropertyValue(rowId, colId, property); } /** * Filter generator that triggers a refresh of a LazyQueryContainer * whenever the filters change. */ public class LQCFilterGenerator implements FilterGenerator { private final LazyQueryContainer lqc; public LQCFilterGenerator(LazyQueryContainer lqc) { this.lqc = lqc; } @Override public Filter generateFilter(Object propertyId, Object value) { return null; } @Override public Filter generateFilter(Object propertyId, Field originatingField) { return null; } @Override public AbstractField getCustomFilterComponent(Object propertyId) { return null; } @Override public void filterRemoved(Object propertyId) { this.lqc.refresh(); } @Override public void filterAdded(Object propertyId, Class filterType, Object value) { this.lqc.refresh(); } @Override public Filter filterGeneratorFailed(Exception reason, Object propertyId, Object value) { return null; } } } .... 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: [source,java] .... final DeviceStatusTable table = new DeviceStatusTable(); table.setSizeFull(); DeviceStatusQuery.Factory factory = new DeviceStatusQuery.Factory(locationID); final LazyQueryContainer statusDataContainer = new LazyQueryContainer(factory, /*index*/ null, /*batchSize*/ 50, false); statusDataContainer.getQueryView().setMaxCacheSize(300); table.configure(statusDataContainer); layout.addComponent(table); layout.setHeight(100f, Unit.PERCENTAGE); // no scrollbar // Respond to row click table.addValueChangeListener(new Property.ValueChangeListener() { @Override public void valueChange(ValueChangeEvent event) { Object index = event.getProperty().getValue(); if (index != nulll) { int locId = (Integer) statusDataContainer.getItem(index) .getItemProperty(DeviceStatusQuery.Column.loc_id).getValue(); doSomething(locId); table.setValue(null); //visually deselect } } }); .... And finally, since we're using `SQLContainer`{empty}'s `QueryBuilder`, depending on your database you may need to include something like this once during your application startup: [source,java] .... import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder; import com.vaadin.data.util.sqlcontainer.query.generator.filter.StringDecorator; // Configure Vaadin SQLContainer to work with MySQL QueryBuilder.setStringDecorator(new StringDecorator("`","`")); ....