Efficient Paging with Page‑By‑Page Iterator
When a J2EE application serves a catalog that can grow to thousands of entries, returning the full list in a single response places unnecessary pressure on the network and on the client’s memory. The Page‑By‑Page Iterator pattern solves this by letting the server slice the data into manageable pages and hand them to the client on demand. In this pattern, the DAO is responsible for determining the total size of the collection, fetching only the subset that the client requests, and wrapping that subset in an iterator that knows the start index, page size, and the overall count.
In practice, the DAO keeps its responsibilities clear: data access, transformation, and packaging. The method that implements paging follows a simple contract – it accepts a start index and a page size, queries the database once for exactly that range, and returns an iterator instance. The iterator itself carries only the data that was fetched and metadata about the full collection. This design ensures that the client never receives data it will not use, while still being able to navigate the complete dataset if it wants to. The implementation shown below mirrors the logic from the original Code Sample 4 but cleans up the interface and removes extraneous comments for clarity.
Notice that the DAO does not expose the full list or the details of the database schema. The iterator carries only what the client asked for, along with enough information to determine if there are more pages. The client can ask for the next page by simply calling the iterator again with an updated start index, keeping the logic out of the servlet and concentrating it inside a single reusable component. This encapsulation keeps the servlet thin, easier to read, and maintainable.
Because the DAO only returns the requested slice, the number of objects serialized over the network is minimal. The iterator itself does not duplicate the collection; it holds a reference to the underlying data, which saves memory and keeps the payload small. This design pattern shines when the client may need to browse a large catalog page by page – for instance, a web shop that displays products in pages of ten or twenty items, or an administrative dashboard that allows administrators to search through a vast pool of services without loading everything into memory at once.
In addition to the performance benefits, this approach also improves the user experience. Users see content faster because the server returns a compact set of results immediately, and the browser can render the page before additional data arrives. When the client requests the next set, the servlet can serve it quickly because the DAO has already optimized the query to return only the required range. In contrast, pulling the entire dataset into memory or serializing it in a single response can lead to long wait times, memory exhaustion, and a sluggish interface. By breaking the data into pages, the application stays responsive even when the catalog scales.
When designing the DAO, consider the characteristics of your database. For relational systems, use LIMIT/OFFSET or ROWNUM queries to fetch only the needed slice. For NoSQL stores, you might use cursor or range queries that naturally support pagination. In all cases, the DAO should expose the same interface – a method that receives a start index and a page size, and returns an iterator that contains the slice and the total size. This uniform contract lets you swap data sources later without changing the servlet or the rest of the application. The Page‑By‑Page Iterator pattern gives you a clean separation of concerns, efficient data transfer, and a flexible foundation for building high‑performance J2EE services.
Integrating Fast Lane Reader and Pagination in a Servlet
Once the DAO provides paged data, the servlet becomes the thin glue that translates HTTP parameters into DAO calls, serializes the result, and streams it back to the browser. The Fast Lane Reader pattern complements pagination by ensuring that the servlet performs read‑only operations without accidental writes or side effects. In practice, this means the servlet’s doPost (or doGet) method will be straightforward: it reads the request parameters that define the page, delegates to the DAO, and iterates over the returned collection to generate the output.
Below is a refactored servlet that follows these principles. It starts by injecting the DAO in the init method, avoiding repeated construction. The doPost method pulls the StartIndex and ListSize parameters from the request, defaults them if missing, and uses them to fetch a PageByPageIteratorImpl. The iterator’s getCollection method returns the subset of service names that the client requested. Finally, the servlet iterates over this collection and writes each service name to the response. In a real application, you would render a JSP, Thymeleaf template, or build a JSON payload, but the core logic remains the same.
public class DisplayServices extends HttpServlet {
private CatalogDataAccessObject dao;
@Override
public void init() throws ServletException {
super.init();
dao = new CatalogDataAccessObject();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
int startIndex = parseInteger(request.getParameter("StartIndex"), 0);
int listSize = parseInteger(request.getParameter("ListSize"), 10);
PageByPageIteratorImpl page = dao.getServiceNames(startIndex, listSize);
Collection
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("
- ");
for (String name : serviceNames) {
out.println(" <li>" + escapeHtml(name) + "</li>");
}
out.println("");
}
private int parseInteger(String param, int defaultValue) {
try {
return Integer.parseInt(param);
} catch (NumberFormatException | NullPointerException e) {
return defaultValue;
}
}
private String escapeHtml(String input) {
// Simple escape; replace with a proper library in production
return input.replace("&", "&")
.replace("
.replace(">", ">");
}
}
In this example, the servlet stays lightweight. All the heavy lifting – paging logic, database access, and iterator construction – happens inside the DAO. The servlet’s responsibilities are limited to translating HTTP to business calls and rendering the results. This separation makes unit testing trivial: you can mock the DAO and verify that the servlet writes the correct output for a given page. It also keeps the servlet free of SQL, so any changes to the database schema or query optimization can be handled in the DAO without touching servlet code.
When the client submits a form or clicks a link that triggers this servlet, the request will contain StartIndex and ListSize parameters. These can be set via hidden fields in a form, or as query string parameters in a GET request. The servlet then calculates which subset to retrieve and forwards that subset to the view layer. By delegating all data handling to the DAO, you preserve the Fast Lane Reader pattern’s guarantee that the servlet is purely read‑only and safe for concurrent use.
There are a few practical tips to keep in mind. First, always validate and sanitize request parameters to avoid accidental negative indices or overly large page sizes. Second, consider adding caching at the DAO level for frequently requested pages; a simple in‑memory cache or a distributed cache can reduce database load. Third, when rendering the output, remember to escape HTML or encode JSON appropriately to avoid injection attacks. By adhering to these practices, the servlet will remain robust, maintainable, and performant, even under heavy traffic.
Ultimately, the combination of Fast Lane Reader and Page‑By‑Page Iterator creates a clean, modular architecture. The servlet becomes a simple coordinator, the DAO encapsulates all data logic, and the iterator guarantees efficient transfer of only the data the client wants. This pattern is especially useful for catalog browsers, administrative panels, and any service that needs to paginate large datasets without compromising response time.
Practical Tips for Using the Page‑By‑Page Pattern
Adopting the Page‑By‑Page Iterator pattern brings clear advantages, but there are practical considerations to ensure it delivers the expected benefits. Below are key points to keep in mind when deciding whether to employ this pattern in your J2EE application.
First, let’s examine the flexibility it offers to clients. By exposing startIndex and size parameters, the client dictates how much data it receives in a single round‑trip. This is ideal when the front end needs to display a limited number of items, such as ten or twenty services at a time. It gives the UI layer control over pagination depth, which can be tied to user preferences or device constraints. Because the server never sends more data than requested, the network overhead remains low, and the client can quickly render the response.
Second, consider the impact on server hit frequency. With paging, each page request is a separate round‑trip. While this spreads the load over time, it also means that an end user who scrolls through many pages will generate many requests. In scenarios where users typically view only a few pages, the pattern shines. If you anticipate heavy scrolling, you might implement a “prefetch” strategy or maintain a sliding window cache on the client. This can reduce the number of server hits while still benefiting from the compact payloads.
Third, think about the iterator’s behavior regarding concurrent modifications. The iterator holds a reference to the original collection without making a copy. If the underlying data source changes (insertions or deletions) while the iterator is in use, the traversal may encounter unexpected items or miss some. In a read‑only context - like the Fast Lane Reader pattern - this risk is minimal, because the data does not change between requests. Still, if the application allows concurrent updates, you might need to snapshot the data or use a read‑only transaction to guard against inconsistencies.
Fourth, weigh bandwidth savings against request frequency. Sending only the requested slice reduces the size of each response, which is valuable on slow networks or mobile devices. However, the trade‑off is an increased number of round‑trips. If you are in a bandwidth‑constrained environment but can afford more latency, you may prefer to fetch a larger page size to reduce total requests. Tuning the page size is a balancing act: a small size favors quick initial load and reduces latency, while a larger size reduces round‑trip count at the cost of bigger payloads.
Fifth, remember that the iterator pattern can be combined with other performance enhancements. For example, you can add database-level caching or a distributed cache layer so that frequently accessed pages are served from memory instead of hitting the database. You can also apply compression (GZIP) to the HTTP response to further reduce bandwidth usage, especially when the page size is larger. All these techniques stack together, making the paging approach even more efficient.
Finally, consider the developer ergonomics. By isolating paging logic in a reusable DAO method, you reduce duplication across servlets or EJBs. New endpoints that need paginated data can simply call the same DAO method without rewriting query logic. The PageByPageIteratorImpl also serves as a self‑documenting object: its fields clearly convey start index, size, and total count, making debugging and logging straightforward. This modularity also improves testability: you can unit test the DAO method in isolation, mock the database, and assert that the iterator returns the correct slice.
In sum, the Page‑By‑Page Iterator pattern offers a clean, client‑controlled approach to large data sets. By carefully tuning page sizes, handling concurrency, and layering complementary performance techniques, you can harness the pattern to deliver a responsive, scalable application that respects both server resources and client bandwidth.
Handling Rich Domain Objects with Value Objects
Paging works great when the data you send back is simple – a list of names or identifiers. In many real‑world applications, however, each item in the list carries a wealth of information. For instance, a catalog service might expose a service name, cost, description, terms, and other metadata. Transferring each of these attributes in separate calls would create a flood of network traffic and increase latency. The Value Object pattern resolves this dilemma by bundling all related attributes into a single serializable object that can be transmitted in one go.
In the context of a J2EE catalog, the DAO aggregates all fields of a service into a ServiceInformation value object. Each ServiceInformation instance holds the name, cost, terms, and any other relevant data. By serializing a collection of these value objects and sending them to the client, you avoid the round‑trip overhead of requesting details for each service individually. The client receives a self‑contained representation of each service, ready for immediate display or further processing.
Below is a straightforward implementation of the ServiceInformation value object. It implements java.io.Serializable so that the object can be marshalled over the network. The constructor accepts the key attributes, and accessor methods provide read‑only access. In practice, you would probably add more fields, validation logic, or conversion methods as needed.
public class ServiceInformation implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private final String serviceName;
private final double serviceCost;
private final String[] serviceTerms;
public ServiceInformation(String name, double cost, String[] terms) {
this.serviceName = name;
this.serviceCost = cost;
this.serviceTerms = terms != null ? terms.clone() : new String[0];
}
public String getServiceName() {
return serviceName;
}
public double getServiceCost() {
return serviceCost;
}
public String[] getServiceTerms() {
return serviceTerms.clone();
}
// Optional: override toString for debugging
@Override
public String toString() {
return "ServiceInformation{name='" + serviceName +
"', cost=" + serviceCost +
", terms=" + java.util.Arrays.toString(serviceTerms) + '}';
}
}
The DAO now constructs a collection of ServiceInformation objects when fetching a page. Instead of returning a list of strings, it returns a list of fully populated value objects. The servlet’s doPost method can remain unchanged, except that it now iterates over ServiceInformation instances instead of simple strings. Rendering logic can display the name, cost, and terms together, giving the user a richer view without additional calls.
Using value objects also simplifies caching. A collection of ServiceInformation can be cached as a whole, and subsequent requests for the same page can be served from memory. Because the object is immutable (all fields are final or cloned), you avoid accidental side effects when the cache is shared across threads. Moreover, by keeping the value object serializable, you can transport it to other components - such as a client desktop app or a mobile device - without rewriting serialization logic.
When designing value objects, consider the following best practices: keep them immutable to avoid thread‑safety issues; implement equals and hashCode if you plan to store them in sets or use them as map keys; use defensive copying for mutable arrays or collections; and document the intended lifecycle. If your domain objects grow large, you can also use lazy loading or partial serialization, but for most catalog services, a lightweight value object is sufficient.
In summary, pairing the Page‑By‑Page Iterator pattern with the Value Object pattern lets you deliver complex, attribute‑rich data efficiently. The DAO packages each service into a single transferable object, the iterator controls how many of those objects the client receives, and the servlet hands the data to the view layer. Together, these patterns create a cohesive, high‑performance pipeline that scales gracefully as your catalog grows.
Mr. Vijay S. Ramachandran is the author of this article.





No comments yet. Be the first to comment!