To implement a robust search feature in a Java-based web application that allows users to filter items (such as products) by multiple optional criteria (e.g., name, category, or status), the most effective approach is to dynamically build the SQL query based on the parameters provided. This ensures that the application only filters by the fields the user has populated.
1. Data Transfer Object (DTO)
Create a POJO to capture the search criteria from the form inputs. This object will hold the filter values submitted by the user.
public class ProductQueryCriteria {
private String productName;
private String categoryId;
private String isFeatured;
// Standard Getters and Setters
public String getProductName() { return productName; }
public void setProductName(String productName) { this.productName = productName; }
public String getCategoryId() { return categoryId; }
public void setCategoryId(String categoryId) { this.categoryId = categoryId; }
public String getIsFeatured() { return isFeatured; }
public void setIsFeatured(String isFeatured) { this.isFeatured = isFeatured; }
}
2. Dynamic DAO Implementation
Using a library like Apache Commmons DbUtils, we can construct the SQL dynamically. The pattern WHERE 1=1 allows us to append additional AND clauses regardless of which fields are present.
public List<Product> findByCriteria(ProductQueryCriteria criteria) throws SQLException {
QueryRunner runner = new QueryRunner(DataSourceUtils.getDataSource());
StringBuilder sql = new StringBuilder("SELECT * FROM product WHERE 1=1 ");
List<Object> parameters = new ArrayList<>();
if (criteria.getProductName() != null && !criteria.getProductName().trim().isEmpty()) {
sql.append(" AND pname LIKE ?");
parameters.add("%" + criteria.getProductName() + "%");
}
if (criteria.getIsFeatured() != null && !criteria.getIsFeatured().isEmpty()) {
sql.append(" AND is_hot = ?");
parameters.add(criteria.getIsFeatured());
}
if (criteria.getCategoryId() != null && !criteria.getCategoryId().isEmpty()) {
sql.append(" AND cid = ?");
parameters.add(criteria.getCategoryId());
}
return runner.query(sql.toString(), new BeanListHandler<>(Product.class), parameters.toArray());
}
3. Controller Logic
In your Servlet, handle the request by mapping the form parameters to your criteria object. Using BeanUtils simplifies this mapping process significantly.
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
ProductQueryCriteria criteria = new ProductQueryCriteria();
try {
BeanUtils.populate(criteria, request.getParameterMap());
} catch (Exception e) {
throw new RuntimeException("Failed to bind parameters", e);
}
List<Product> products = productService.findFilteredProducts(criteria);
request.setAttribute("ProductList", products);
request.setAttribute("criteria", criteria);
request.getRequestDispatcher("/product_list.jsp").forward(request, response);
}
4. Front-End Integration
On the JSP side, ensure the input fields correspond to the property names in your DTO. Using JSTL, you can retain the selected values sothat the search form reflects the active filters after the page reloads.
<form action="ProductSearchServlet" method="post">
<input type="text" name="productName" value="${criteria.productName}">
<select name="categoryId">
<option value="">All Categories</option>
<c:forEach items="${categories}" var="cat">
<option value="${cat.cid}" ${criteria.categoryId == cat.cid ? 'selected' : ''}>
${cat.cname}
</option>
</c:forEach>
</select>
<button type="submit">Search</button>
</form>