Saturday, June 29, 2013

Pagination Utility




Contents



1           Scope

This document specifies,
1.       How the PaginationHelper works to fetch paginated result set
2.       How it can be used in an application to avail pagination functionality.

2           Why Pagination

In a Web Application, if the search returns a huge result set (thousands..), then it can cause:
UI/Client:
1.       Need to display all the results at once.
a.        It is difficult for the user to go through all the records by scrolling through the results.
2.       It will take more time to get all the results rendered.
Server/DB:
1.       DB Query will take a lot of time to fetch all the results at once – Affects the performance
2.       Sometimes Java will throw Memory Exception, if the number of records to handle is very huge.
This is not acceptable in today’s high responsive web applications, where performance is the critical aspect.
To resolve this we can have pagination implementation for the search pages/huge result sets.

3           Pagination Helper Functionality

Pagination Helper is implemented using formal type parameter using <E>. This type will be resolved during runtime based on the actual implementation of the type. 
It accepts the following arguments to fetch paginated result set.
Ø  Data fetch query & its arguments array
Ø  Count fetch query & its arguments array
Ø  PageFilter
Ø  RowMapper instance for mapping the page result

This helper class fetches the data for pagination using properties of PageFilter.
Ø  PageIndex
Ø  PageSize
Ø  Order By Column
Ø  Order By

4           Sorting the Result

If the PageFilter’s orderByColumn and orderBy properties are populated with some values, the helper class adds a wrapper query as below for fetching the results in a sorted manner.
sqlFetch ="SELECT * FROM (" + sqlFetchRows + ") ORDER BY "
                                                                + orderByCol + " " + orderBy

5           FetchingPage Result

Based on PageFilter’s pageSize & pageIndex properties, start row & end row values are calculated by PaginationHelper and use it for fetching specific set of records.

The helper class adds a wrapper query as below for fetching specific set of result.
fetchQuery = "SELECT * FROM (SELECT ROW_NUMBER() OVER() RNUM, T.*
FROM ("+ sqlFetch + ") T)
WHERE RNUM <=? AND RNUM >=?"

6           Database Access

The helper class extends BaseDAO for availing access on datasource and jdbctemplate objects. The Pagination Helper uses Spring DAO’s JdbcTemplate to execute the query and map result to a given row mapper.

The fetched data is stored on PageDTO and returned to the caller. 

7           Implementation

7.1          Client

Below is the sample code that uses Pagination Helper for fetching paginated result set.
// Set the Page Filter Details using the values from the HTTPRequest
PageDTO<PendingComponentDTO> pageFilter = new PageFilterDTO();
pageFilter. setPageIndex (pageIndex);
pageFilter. setPageSize(pageSize)

// Pagination Helper Instance
IPaginationHelperDAO<UserDTO> iPaginationHelperDAO = null;
iPaginationHelperDAO = new PaginationHelperDAO<UserDTO>();

// Pagination Arguments
Object[] countArgs = new Object[] { roleId };
Object[] queryArgs = new Object[] { roleId };

// Pagination queries
String sqlFetchUserDetails = "SELECT U.USER_NAME, R.ROLE_NAME, U.ADDRESS FROM USER U, ROLE R, USER_ROLE_MAP URM WHERE URM.USER_ID=URM.USER_ID AND URM.ROLE_ID = R.ROLE_ID AND R.ROLE_ID=?";  
String sqlCountUserRows = "SELECT COUNT(*) FROM("+ sqlFetchUserDetails +);"


//Paginated Result
PageDTO<PendingComponentDTO> pageDTO = null;

    
// Invoke the pagination helper for the paginated result
pageDTO = iPaginationHelperDAO.fetchPage(sqlCountUserRows,
                                                                countArgs, sqlFetchUserDetails, queryArgs, pageFilter, new UserRowMapper());                           
                                 

7.2          UserRowMapper

/**
 * The Class UserRowMapper maps the result set of the query used to
 * fetch user details
 */
public class UserRowMapper implements
                                ParameterizedRowMapper<UserDTO> {

               
                // fetching and mapping User Details from USER and ROLE table to
                // UserDTO
                public UserDTO mapRow(ResultSet rs, int row)
                                                throws SQLException {
                               
                                UserDTO userDTO = new UserDTO();
                                user.setUserName(rs.getString("USER_NAME"));
                                user.setAddress(rs.gtString("ADDRESS"));
            user.setRoleName(rs.getString("ROLE_NAME"));
           
            return userDTO;
                }
}

 

7.3          PaginationHelperDAO

public class PaginationHelperDAO<E> extends BaseDAO implements
                                IPaginationHelperDAO<E> {
      
       public PageDTO<E> fetchPage(final String sqlCountRows,
                                                final Object countArgs[], final String sqlFetchRows,
                                                final Object queryArgs[], final PageFilterDTO pageFilter,
                                                final ParameterizedRowMapper<E> rowMapper) {

                                // create the page object.
                                final PageDTO<E> pageDTO = new PageDTO<E>();
                                final List<E> pageItems = new ArrayList<E>();

                                int startRow = PaginationUtil.getStartRow(pageFilter.getPageIndex(), pageFilter
                                                                .getPageSize());
                                int endRow = PaginationUtil.getEndRow(pageFilter.getPageIndex(), pageFilter
                                                                .getPageSize());

                                String orderByCol = pageFilter.getOrderByColumn();
                                String orderBy = pageFilter.getOrderBy();
                                String sqlFetch = sqlFetchRows;

                                // Append Order BY clause if required
                                if (!orderByCol.isEmpty() && !orderBy.isEmpty()) {
                                                sqlFetch = "SELECT * FROM (" + sqlFetchRows + ") ORDER BY "
                                                                                + orderByCol + " " + orderBy;
                                }

                                int length = 0;
                                if (null != queryArgs) {
                                                length = queryArgs.length;
                                }

                                // Append Start Row & End Row Arguments to fetch the results
                                Object fetchQueryArgs[] = Arrays.copyOf(queryArgs, length + 2);
                                fetchQueryArgs[length] = endRow;
                                fetchQueryArgs[length + 1] = startRow;

String fetchQuery = "SELECT * FROM (SELECT ROW_NUMBER() OVER() RNUM, T.*   FROM ("             + sqlFetch + ") T) WHERE RNUM <=? AND RNUM >=?";

                               
                                // Execute the pagination query and get the page items
                                getJdbcTemplate().query(fetchQuery, fetchQueryArgs,
                                                new ResultSetExtractor() {
                                                public Object extractData(ResultSet rs)
                                                                                throws SQLException, DataAccessException {
                                                                                int currentRow = 0;
                                                                                while (rs.next()) {
                                                                                pageItems.add(rowMapper.mapRow(rs, currentRow));
                                                                                                                currentRow++;
                                                                                                }
                                                                                                // set the page items in the page object
                                                                                                pageDTO.setPageItems(pageItems);
                                                                                                return pageDTO;
                                                                                }
                                                                });

                                int totalRows = pageItems.size();
                                // determine how many more rows are available
                                if (null != sqlCountRows) {
                                                int rowsRequested = (endRow - startRow) + 1;
                                               
                                                // We need not query for the count if,
                                                // the resulted no. of records is less than the requested.
                                               
                                                if ((totalRows == rowsRequested)
                                                                                || ((totalRows < rowsRequested) && startRow != 1)) {
                                                                totalRows = getJdbcTemplate().queryForInt(sqlCountRows,
                                                                                                countArgs);
                                                }
                                }

                                // Set page counters
                                pageDTO.setTotalRows(totalRows);
                                pageDTO.setPageIndex(pageFilter.getPageIndex());
pageDTO.setPageCount(PaginationUtil.getPageCount(totalRows, pageFilter.getPageSize()));

                                return pageDTO;
                }
}

7.4          PaginationUtil

public class PaginationUtil {

public static int getPageCount(int recordsCount, int pageSize) {

                                int pageCount = (recordsCount / pageSize)
                                                                + (recordsCount % pageSize == 0 ? 0 : 1);
                                return pageCount;
                }


public static int getStartRow(int pageIndex, int pageSize) {
                                return (pageIndex - 1) * pageSize + 1;
                }

public static int getEndRow(int pageIndex, int pageSize) {
                                return pageIndex * pageSize;
                }
}

7.5          PageFilterDTO

public class PageFilterDTO implements Serializable {

                /**
                 * The Serial Version Id
                 */
                private static final long serialVersionUID = -7709063021259627638L;

                /** The Page Index */
                private int pageIndex;

                /** The Page Size */
                private int pageSize;

                /** The Order By Column */
                private String orderByColumn = "";

                /** The Order (asc/desc) */
                private String orderBy = "";

//Have the getter/setter methods for the properties



7.6          PageDTO

public class PageDTO<E> implements Serializable {

                /**
                 * The Serial Version Id
                 */
                private static final long serialVersionUID = -5027820698650544274L;

                // The Page Index to store the current page number
                private Integer pageIndex;

                // The page count for the grid
                private Integer pageCount;

                // Total rows from the query
                private Integer totalRows;
               
                // List of page items
                private List<E> pageItems = new ArrayList<E>();
           
 //Have the getter/setter methods for the properties