JDAL - Integración con Spring MVC y DisplayTag

Este ejemplo muestra como integrar JDAL en entornos web utilizando Spring MVC y la librería displaytag para mostrar resultados tabulares con paginación filtrado y ordenamiento desde el servidor.

Por defecto displaytag carga todos los resultados en el servidor y realiza la paginación y el ordenado a partir de los datos en la memoria del servidor, lo cual puede llevarnos a problemas serios de rendimiento con tablas no demasiado grandes. Desde la versión 1.1 displaytag se soporta paginación externa mediante la interfaz PaginatedList que facilita considerablemente la integración con el sistema de paginación que proporciona JDAL Core.

Para utilizar displaytag con la paginación que proporciona JDAL partiremos de las capas de integración y servicio servicio del ejemplo de de JDAL. Para crear la capa de presentación necesitamos crear tres clases:


El Adaptador

Obviamente este sólo hay que hacerlo una vez, actualmente está en el nuevo módulo de soporte web jdal-web

/**
 * Adapter from JDAL Page to DisplayTag PaginatedList
 * 
 * @author Jose Luis Martin
 */
@SuppressWarnings("unchecked")
public class PaginatedListAdapter implements PaginatedList {
 
	private Page<?> model;
 
	public PaginatedListAdapter() {
		this(new Page(10));
	}
 
	public PaginatedListAdapter(Page<?> model) {
		this.model = model;
	}
 
	/**
	 * {@inheritDoc}
	 */
	public int getFullListSize() {
		return model.getCount();
	}
 
	/**
	 * {@inheritDoc}
	 */
	public List getList() {
		return model.getData();
	}
 
	/**
	 * {@inheritDoc}
	 */
	public int getObjectsPerPage() {
		return model.getPageSize();
	}
 
	/**
	 * {@inheritDoc}
	 */
	public int getPageNumber() {
		return model.getPage();
	}
 
	/**
	 * {@inheritDoc}
	 */
	public String getSearchId() {
		return null;
	}
 
	/**
	 * {@inheritDoc}
	 */
	public String getSortCriterion() {
		return model.getSortName();
	}
 
	/**
	 * {@inheritDoc}
	 */
	public SortOrderEnum getSortDirection() {
		return model.getOrder() == Page.Order.ASC ? SortOrderEnum.ASCENDING : SortOrderEnum.DESCENDING;
	}
 
	/**
	 * @return the model
	 */
	public Page<?> getModel() {
		return model;
	}
 
	/**
	 * @param model the model to set
	 */
	public void setModel(Page<?> model) {
		this.model = model;
	}
 
	public void setPage(int page) {
		model.setPage(page);
	}
 
	public void setSort(String sort) {
		model.setSortName(sort);
	}
 
	public void setDir(String dir) {
		model.setOrder("asc".equalsIgnoreCase(dir) ? Page.Order.ASC : Page.Order.DESC);
	}
 
}
 

Controlador

En BookController guardamos el filtro en la sessión mediante las anotaciones de código de Spring MVC. También configuramos el WebBinder de Spring con CategoryPropertyEditor que nos permite convertir entre los IDs seleccionados en el combo de categorías a los objetos Category de la base de datos.

Debido a que JDAL Core nos proporciona automáticamente las consultas con orden y paginación a partir de la información suministrada en el objeto Page, la implementación del manejador de las peticiones generadas por DisplayTag, getPage() es realmente sencilla y podría reutilizarse de forma independientemente del modelo que se muestra en la tabla. Basta con obtener el objeto Page del adaptador PaginatedListAdapter y realizar la petición de página al servicio de persistencia.

/**
 * Controller to handle "/book" requests
 * 
 * @author Jose Luis Martin 
 */
@Controller
@SessionAttributes("bookFilter")   // Store filter on Session
@SuppressWarnings("unchecked")
public class BookController  {
 
	/** Persistent service for book categories */
	PersistentService<Category, Long> categoryService;
	/** Persistent Service for Books */
	PersistentService<Book, Long> bookService;
 
	/**
	 * Handle getPage request. Gets the Page from PaginatedListAdapter wrapper
	 * and query service for data.
	 * @param paginatedList the displaytag PaginatedList interface
	 * @param filter filter to apply
	 * @return Model 
	 */
	@RequestMapping()
	public void getPage(@ModelAttribute("paginatedList") PaginatedListAdapter paginatedList, 
				@ModelAttribute("bookFilter") BookFilter filter) {
 
		Page<Book> page = (Page<Book>) paginatedList.getModel();
 
		page.setFilter(filter);
		bookService.getPage(page);
	}
 
	/**
	 * Add the category list to model 
	 * @return List with all categories
	 */
	@ModelAttribute("categoryList")
	public List<Category> getCategories() {
		return categoryService.getAll();
	}
 
	/**
	 * Need to register the CategoryPropertyEditor for mapping ids of 
	 * category select to Category objects in bd.
	 * @param binder
	 */
	@InitBinder
	public void initBinder(WebDataBinder binder) {
		binder.registerCustomEditor(Category.class, new CategoryPropertyEditor());
 
	}
 
 
	@ModelAttribute
	public BookFilter getBookFilter() {
		return new BookFilter();
	}
 
	/**
	 * @return the categoryService
	 */
	public PersistentService<Category, Long> getCategoryService() {
		return categoryService;
	}
 
	/**
	 * @param categoryService the categoryService to set
	 */
	public void setCategoryService(PersistentService<Category, Long> categoryService) {
		this.categoryService = categoryService;
	}
 
	/**
	 * @return the bookService
	 */
	public PersistentService<Book, Long> getBookService() {
		return bookService;
	}
 
	/**
	 * @param bookService the bookService to set
	 */
	public void setBookService(PersistentService<Book, Long> bookService) {
		this.bookService = bookService;
	}
 
	/**
	 * Property editor to map selected category from id string to Category.
	 */
	class CategoryPropertyEditor extends PropertyEditorSupport {
 
		/**
		 * {@inheritDoc}
		 */
		@Override
		public void setAsText(String text) throws IllegalArgumentException {
			Long id = Long.parseLong(text);
			Category value = id == 0 ? null : categoryService.get(id);
			setValue(value);
		}
 
		/**
		 * {@inheritDoc}
		 */
		@Override
		public String getAsText() {
			Category c = (Category) getValue();
			return c != null ? String.valueOf(c.getId()) : "";
		}
	}
}

JSP


Finalmente escribimos la página JSP. Para que el orden funcione correctamente en las categorías y el autor, debemos especificar la propiedad por la que deseamos ordenar estos objetos ya que si no se ordenarán mediante la clave primaria que no es normalmente lo apropiado. Para ello especificamos el atributo sortProperty a "category.name" en las categorías y "author.surname" en el autor.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>    
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib uri="http://displaytag.sf.net" prefix="display" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="<c:out value="${pageContext.request.contextPath}"/>/styles/displaytag.css"/>
<title>JDAL Spring MVC and DisplayTag Sample</title>
</head>
<body>
<H1>JDAL Spring MVC and DisplayTag Sample</H1>
 
<form:form commandName="bookFilter" action="getPage" method="POST">
      <table>
          <tr>
              <td>Book Title:</td>
              <td><form:input path="name" /></td>
          </tr>
          <tr>
              <td>Category:</td>
              <td><form:select path="category">
                <form:option value="0">--Please Select</form:option>
                <form:options items="${categoryList}" itemValue="id" itemLabel="name" /><
                </form:select>
              </td>
          </tr>
           <tr>
              <td>ISBN:</td>
              <td><form:input path="isbn" /></td>
          </tr>
           <tr>
              <td>Author Name:</td>
              <td><form:input path="authorName" /></td>
          </tr>
           <tr>
              <td>Author Surname:</td>
              <td><form:input path="authorSurname" /></td>
          </tr>
          <tr>
              <td colspan="2">
                  <input type="submit" value="Apply Filter" />
              </td>
          </tr>
      </table>
  </form:form>
 
  <br>
 
  <display:table name="paginatedList" requestURI="getPage" >
    <display:column property="id" title="ID" sortable="true" style="width:80px" />
    <display:column property="name" title="Title" sortable="true" style="width:300px "/>
    <display:column property="category" title="Category" sortable="true" sortProperty="category.name" style="width:200px"/>
    <display:column property="author" title="Author" sortable="true" sortProperty="author.surname" style="width:200px" />
    <display:column property="isbn" title="ISBN" sortable="true" style="width:180px"/>
</display:table>
 
</body>
</html>

Ejemplo

puede descargar el código del ejemplo de la página de JDAL. Para este ejemplo he utilizado el soporte de JPA CriteriaQueries recientemente añadido a la versión de desarrollo de JDAL en lugar de Criterias de Hibernate.

Si tiene alguna pregunta o comentario, por favor escríbala en los foros de soporte de JDAL. Trataré de responderle lo antes que me sea posible.

Más Información


Puede obtener más información sobre JDAL en http://www.jdal.org (inglés) o consultar la documentación de JDAL en esta misma web que describe la aplicación de ejemplo.

Si lo desea, puede realizar sus preguntas o comentarios en foro de soporte de JDAL, trataré de responderle tan pronto como me sea posible.