Ir al contenido principal

Spring + Primefaces + JDBC

Por Qué Spring

Uno de sus propósitos principales es permitir a los programadores centrarse en la parte más importante de todas: modelar e implementar requerimientos de negocio. Otro de sus propósitos es hacer seguir las mejores prácticas de programación de una manera sencilla.

Spring se acopla a tu aplicación sin obligarte a modificar tu código para utilizar las funcionalidades y beneficios que ofrece. Para usar Spring no es imprescindible implementar un interfaz propia de Spring o heredar de una clase propia, lo que significa que Spring estará ahí pero tus clases serán Java puras. Como mucho, tus clases tendrán anotaciones propias de Spring, pero no dejan de ser anotaciones por lo que tu clase será reutilizable en cualquier momento, sin cambios.


Gracias a Spring, nuestro código Java puede ser más limpio, elegante y reutilizable. Elegante y reutilizable ya que la filosofía de Spring nos guía a programar orientado a interfaces, de modo que toda nuestra aplicación sea altamente modular y posea bajo acoplamiento. 

Entorno de desarrollo


Utilizaremos Spring Tool Suite. Es un entorno de desarrollo basado en Eclipse, listo para usar. Nos permite implementar, depurar, ejecutar y desplegar las aplicaciones de Spring.

Podemos descargarnos el entorno STS de http://spring.io/tools. En este momento tenemos la versión es la 3.6 que está basada en Eclipse Luna 4.4

Maven

Maven es una herramienta Open Source para gestionar el ciclo de vida de proyectos software. Cuando hablamos de gestionar el ciclo de vida, nos referimos desde la creación de un proyecto en un lenguaje dado, hasta la generación de un binario que pueda distribuirse con el proyecto.

Maven nació dentro de la fundación Apache para complementar a Apache Ant, la herramienta de compilación más usada en el mundo Java. Ant es una herramienta que permite crear scripts que indican cómo compilar un proyecto Java y generar un binario. Maven nos brinda una mejora respecto a Apache Ant, ya que nos proporciona una estructura homogénea para cualquier proyecto y nos permite, además, una gestión avanzada de dependencias, informes sobre testing automático y extensibilidad vía plugins que Ant no proporcionaba.

Sin embargo, hay que destacar que, en segundo plano, Maven sigue utilizando internamente Ant para manejar la compilación (e incluso se pueden lanzar tareas de Ant desde Maven). Si somos correctos a la hora de presentar Maven no diremos que es un sustituto de Ant ni una mejora de éste, sino un complemento de este último que nos brinda importantes mejoras y estándares de modelado de proyecto.

Un Proyecto en Maven se define mediante un archivo POM llamado pom.xml.

Maven proporciona arquetipos para ayudar a la creación de proyectos de cualquier tipo.

Para este manual utilizaremos el arquetipo maven-archetipe-webapp.

El elemento groupId

Representa un grupo, empresa, equipo u organización. Generalmente se nombra empezando con un dominio y la organización que crea el proyecto ej: org.miempresa.

Con groupID establecemos el conjunto de proyectos al que pertenece nuestro proyecto. Este nombre va a servir de paquete inicial para todas las clases del proyecto. Todos los proyectos maven deben pertenecer a un grupo, aunque sea único para él.

El elemento artifactId

Representa de manera única el nombre ya sea del proyecto completo o de un módulo de este.

Con artifacId establecemos el nombre que queremos dar al proyecto. Maven creará un directorio con este nombre.

El elemento version

Representa la versión de este proyecto o de este módulo. Bajo desarrollo suele usarse un número más la palabra SNAPSHOT.

Creación de un nuevo proyecto

Para estos desarrollos vamos a utilizar maven, aunque no sería necesario, lo usamos para resolver problemas de dependencias y para que nos ayude en todas las fases del ciclo de vida de los proyectos, incluyendo validación, generación de código, compilación, pruebas, empaquetamiento, pruebas de integración, verificación, instalación, despliegue y creación e implementación de sitios.

Creamos un nuevo proyecto maven


Lo almacenamos en el espacio de trabajo por defecto


Seleccionamos el arquetipo WebApp


Introducimos el Group Id y el Artifact Id


Y finalizamos

Si nos diera un problema de que no encuentra el arquetipo, realizamos estos pasos previos:
  1. Open Window > Preferences
  2. Open Maven > Archetypes
  3. Click 'Add Remote Catalog' and add the following:
Y seleccionamos el arquetipo del catálogo que hemos creado


Configuración dependencias pom.xml

Añadimos estas dependencias al pom.xml

<!-- Faces Implementation -->
<dependency>
       <groupId>com.sun.faces</groupId>
       <artifactId>jsf-impl</artifactId>
       <version>2.2.4</version>
</dependency>

<!-- Faces Library -->
<dependency>
       <groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
       <version>2.2.4</version>
</dependency>

<dependency>
       <groupId>javax.servlet</groupId>
       <artifactId>jstl</artifactId>
       <version>1.1.2</version>
</dependency>

<dependency>
       <groupId>javax.servlet</groupId>
       <artifactId>javax.servlet-api</artifactId>
       <version>3.1.0</version>
       <scope>provided</scope>
</dependency>

<!-- Primefaces dependency -->
<dependency>
       <groupId>org.primefaces</groupId>
       <artifactId>primefaces</artifactId>
       <version>4.0</version>
</dependency>

<!-- Spring dependencies -->           
<dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-orm</artifactId>
       <version>3.0.5.RELEASE</version>
</dependency>

<dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-web</artifactId>
       <version>3.0.5.RELEASE</version>
</dependency>

<dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-webmvc</artifactId>
       <version>3.0.5.RELEASE</version>
</dependency>

<dependency>
       <groupId>org.springframework.webflow</groupId>
       <artifactId>spring-faces</artifactId>
       <version>2.3.0.RELEASE</version>
</dependency>

<!-- MySQL Java Connector dependency -->
<dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>5.1.9</version>
</dependency>

<!-- JUnit -->
<dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.11</version>
       <scope>test</scope>
</dependency>

Añadir el repositorio para PrimeFaces

En el caso de que no pueda bajarse la librería de PrimeFaces añadimos este repositorio

<repositories>
    <repository>
        <id>prime-repo</id>
        <name>PrimeFaces Maven Repository</name>
        <url>http://repository.primefaces.org</url>
        <layout>default</layout>
    </repository>
</repositories>

Configuración WEB-INF/web.xml

El fichero web.xml es el descriptor de despliegue para una aplicación Web Java. Este fichero ya está creado, lo modificamos con estos parámetros dentro de los tags <web-app></web-app>:

<display-name>Biblio</display-name>
   
<!-- Change to "Production" when you are ready to deploy -->
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>

<!-- Spring Context Configuration' s Path definition -->
<context-param>
       <param-name>
contextConfigLocation
</param-name>
       <param-value>
             classpath:applicationContext.xml
       </param-value>
</context-param>

<!-- Listeners -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
   
<!-- JSF mapping -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<welcome-file-list>
       <welcome-file>index.xhtml</welcome-file>
</welcome-file-list>

<!-- Mapping with servlet and url for the http requests. -->
<!-- Servlet Mappings -->
<servlet-mapping>
       <servlet-name>Faces Servlet</servlet-name>
       <url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
       <servlet-name>Faces Servlet</servlet-name>
       <url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<servlet-mapping>
       <servlet-name>Faces Servlet</servlet-name>
       <url-pattern>*.faces</url-pattern>
</servlet-mapping>
<servlet-mapping>
       <servlet-name>Faces Servlet</servlet-name>
       <url-pattern>*.xhtml</url-pattern>
</servlet-mapping> 

Configuración WEB-INF/faces-config.xml

Creamos el fichero de configuración de JSF faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
             http://java.sun.com/xml/ns/javaee
             http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">

       <!-- JSF and Spring are integrated -->
<application>
<el-resolver>
org.springframework.web.jsf.el.SpringBeanFacesELResolver
</el-resolver>
</application>
</faces-config>

De esta manera estamos activando el soporte de Spring para la inyección de dependencias de tal forma que JSF sabrá que si no encuentra un bean bajo su contexto debe ir a buscarlo al contexto de Spring.

Configuración /src/main/resources/applicationContext.xml

Este es el fichero de configuración de Spring. Puede estar ubicado en muchos sitios pero se debe mapear en el web.xml. Nuestra configuración de ubicación ha sido classpath:applicationContext.xml, con lo que lo metemos dentro de los recursos en el classpath, concretamente en src/main/resources/applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"

       xsi:schemaLocation="
 http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context-3.0.xsd
 http://www.springframework.org/schema/tx
 http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
 http://www.springframework.org/schema/aop
 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
   
<!-- Scans the classpath of this application for @Components -->
       <context:component-scan base-package="es.ocascal.biblio" />

      
</beans>

Con esta anotación realiza un escaneo dentro de es.ocascal.biblio e inyectará todas las clases anotadas, veremos esto un poco mas adelante.

Aplicación Biblio 

Estructura del proyecto

Creamos la estructura de paquetes, quedando nuestro proyecto de la siguiente forma:


BBDD Mysql biblio


Creamos las tablas de Mysql que vamos a utilizar y las rellenamos con algunos datos.

CREATE TABLE IF NOT EXISTS `categoria` (
  `id` varchar(10) NOT NULL,
  `descripcion` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `categoria` (`id`, `descripcion`) VALUES
('1', 'Java'),
('2', 'Net'),
('3', 'Web');

CREATE TABLE IF NOT EXISTS `libros` (
  `Isbn` varchar(10) NOT NULL,
  `Titulo` varchar(30) NOT NULL,
  `Categoria` varchar(30) NOT NULL,
  PRIMARY KEY (`Isbn`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `libros` (`Isbn`, `Titulo`, `Categoria`) VALUES
('1', 'Programacion', '1'),
('2', 'Programacion', '2'),
('3', 'CSS', '3');

Clases de Modelo de Negocio


Vamos a crear una clase por cada tabla de nuestra base de datos, dentro del paquete bo que será donde se alojen nuestras clases que modelan el negocio. Estas clases implementan la interfaz serializable.

Clase Categoria

package es.ocascal.biblio.bo;
import java.io.Serializable;

public class Categoria implements Serializable{
      
       private static final long serialVersionUID = 1L;     
       private String id
       private String descripcion;
      
       public void setId(String id){
             this.id = id;      
       }
      
       public void setDescripcion(String descripcion){
             this.descripcion = descripcion;        
       }
      
       public String getId(){
             return this.id;           
       }
      
       public String getDescripcion(){
             return this.descripcion;         
       }
}

Clase Libro

package es.ocascal.biblio.bo;
import java.io.Serializable;

public class Libro implements Serializable{
      
       private static final long serialVersionUID = 1L;     
       private String isbn;
       private String titulo;
       private String categoria;
      
       public void setIsbn(String isbn){
             this.isbn = isbn;         
       }
      
       public void setTitulo(String titulo){
             this.titulo = titulo;            
       }
      
       public void setCategoria(String categoria){
             this.categoria = categoria;            
       }
      
       public String getIsbn(){
             return this.isbn;
       }
      
       public String getTitulo(){
             return this.titulo;
       }
      
       public String getCategoria(){
             return this.categoria;
       }
}

DAO

Creamos un DAO genérico con las operaciones básicas:

package es.ocascal.biblio.dao;

import java.io.Serializable;
import java.util.List;

public interface GenericDAO<T,Id extends Serializable> {
      
       T buscarPorClave (Id id);
       List<T>buscarTodos();
       void insertar(T objeto);
       void actualizar(T objeto);
       void borrar(T objeto);    

}

Deberíamos crear un DAO por cada entidad de negocio que tengamos, en este caso uno para libro y otro para categoría, haciendo que extiendan a nuestro DAO genérico, pero de categoría solo nos interesa el listado a la hora de dar de insertar/actualizar libros, por lo que solo vamos a crear una DAO para la biblioteca donde los métodos heredados serán para operaciones sobre libros y añadiremos un método a su definición para  obtener el listado de categorías.

package es.ocascal.biblio.dao;

import java.util.List;

import es.ocascal.biblio.bo.Categoria;
import es.ocascal.biblio.bo.Libro;

public interface BiblioDAO extends GenericDAO<Libro,String> {
      
       List<Categoria>buscarCategoriasTodos();
      
}

Ahora vamos a realizar la implementación del DAO mediante la plantilla JDBC.

Creamos el paquete es.ocascal.biblio.dao.jdbc y añadimos la clase que implementa las operaciones de nuestro DAO mediante JDBC. El proyecto debe quedar así:


Esta clase, JDBCBiblioDAO, la marcaremos con la anotación @Repository y la implementamos de esta forma:

package es.ocascal.biblio.dao.jdbc;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.stereotype.Repository;

import es.ocascal.biblio.bo.Categoria;
import es.ocascal.biblio.bo.Libro;
import es.ocascal.biblio.dao.BiblioDAO;

@Repository(value = "biblioDAO")
public class JDBCBiblioDAO implements BiblioDAO {
      
       private SimpleJdbcTemplate jdbcTemplate;
      
private static final String SQL_INSERT_LIBRO = "INSERT INTO libros (ISBN, TITULO, CATEGORIA) VALUES (:isbn, :titulo, :categoria)";
private static final String SQL_UPDATE_LIBRO = "UPDATE libros SET TITULO = :titulo, CATEGORIA= :categoria WHERE ISBN = :isbn";
private static final String SQL_DELETE_LIBRO = "DELETE FROM libros WHERE ISBN = :isbn";
private static final String SQL_ALL_LIBRO = "SELECT LIBROS.ISBN AS ISBN, LIBROS.TITULO AS TITULO, CATEGORIA.DESCRIPCION AS CATEGORIA FROM LIBROS, CATEGORIA WHERE LIBROS.CATEGORIA=CATEGORIA.ID";      
private static final String SQL_ONE_LIBRO ="SELECT ISBN, TITULO, CATEGORIA FROM LIBROS WHERE ISBN = :isbn";
private static final String SQL_ALL_CATEGORIA = "SELECT ID, DESCRIPCION FROM CATEGORIA";
      
       public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate) {
             this.jdbcTemplate = jdbcTemplate;
       }

       @SuppressWarnings("unchecked")
       public Libro buscarPorClave(String id) {
             try {
                    Map<String, Object> params =
new HashMap<String, Object>();
                    params.put("isbn", id);

Libro libro = (Libro) jdbcTemplate.queryForObject(SQL_ONE_LIBRO, new BeanPropertyRowMapper(Libro.class), params);

                    return libro;

               } catch (DataAccessException ex) {
                 return null;
               }
       }

       @SuppressWarnings("unchecked")
       public List<Libro> buscarTodos() {
List<Libro> libros = jdbcTemplate.query(SQL_ALL_LIBRO, new BeanPropertyRowMapper(Libro.class));
             return libros;
       }

       public void actualizar(Libro libro) {
             Map<String, Object> params = new HashMap<String, Object>();
             params.put("isbn", libro.getIsbn());
             params.put("titulo", libro.getTitulo());
             params.put("categoria", libro.getCategoria());       
             jdbcTemplate.update(SQL_UPDATE_LIBRO, params);
       }

       public void borrar(Libro libro) {
             Map<String, Object> params = new HashMap<String, Object>();
             params.put("isbn", libro.getIsbn());
             jdbcTemplate.update(SQL_DELETE_LIBRO, params);
       }

       public void insertar(Libro libro) {           
             Map<String, Object> params = new HashMap<String, Object>();
             params.put("isbn", libro.getIsbn());
             params.put("titulo", libro.getTitulo());
             params.put("categoria", libro.getCategoria());       
             jdbcTemplate.update(SQL_INSERT_LIBRO, params);
       }

       @SuppressWarnings("unchecked")
       public List<Categoria> buscarCategoriasTodos() {
List<Categoria> categorias = jdbcTemplate.query(SQL_ALL_CATEGORIA, new BeanPropertyRowMapper(Categoria.class));
             return categorias;
       }

}

Con la anotación @Repository(value = "biblioDAO") le indicamos a Spring que es del tipo persistencia, automáticamente Spring lo pondrá a disposición como Singleton.

Nuestro SimpleJdbcTemplate jdbcTemplate es la plantilla que nos proporciona Spring y que vamos a usar con inyección de dependencias. Lo haremos por setter, así que debemos crear un setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate).

Definiremos las órdenes que vamos a ejecutar en variables estáticas y las utilizáramos en los distintos métodos de nuestro DAO.

Modificación para DAO del fichero applicationContext.xml

Añadimos al fichero de configuración el datasouce que usaremos para conectarnos a la base de datos y la plantilla JDBC, a la que le suministramos el datasource definido anteriormente.

<!-- DataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
       <property name="driverClassName" value="com.mysql.jdbc.Driver" />
       <property name="url" value="jdbc:mysql://localhost:3306/biblio" />
       <property name="username" value="java" />
       <property name="password" value="java" />
</bean>
      
<!--  Plantilla JDBC -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
       <constructor-arg ref="dataSource" />
</bean>

<!-- DAO Biblioteca -->
<bean id="biblioDAO" class=" es.ocascal.biblio.dao.jdbc.JDBCBiblioDAO">
       <property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>

A la plantilla JDBC le pasamos el datasource con constructor-arg

Realizar un Test de JDBCBiblioDAO

Vamos a realizar un test para ver como funciona nuestro DAO.


Creamos la clase JDBCBiblioDAOTest dentro de la carpeta de test que nos proporciona maven. Vamos a crear un variable context donde cargaremos nuestro fichero applicationContext.xml de configuración de Spring. Una vez cargado, obtenemos  nuestro bean biblioDAO y realizamos las pruebas. A simple vista se ve que el testCrudLibro() crea un libro, lo modifica y lo elimina, realizando las operaciones crud y asegurándonos que funciona correctamente.

package es.ocascal.biblio.dao;

import static org.junit.Assert.*;

import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import es.ocascal.biblio.bo.Libro;
import es.ocascal.biblio.dao.jdbc.JDBCBiblioDAO;

public class JDBCBiblioDAOTest {
      
    private ApplicationContext context;
    private BiblioDAO biblioDAO;

    @Before
    public void setUp() throws Exception {
context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
       biblioDAO = (JDBCBiblioDAO) context.getBean("biblioDAO");
    }

    @Test
    public void testGetLibroList() {
        List<Libro> libros = biblioDAO.buscarTodos();
        assertEquals(libros.size(), 3, 0);       
    }

    @Test
    public void testCrudLibro() {
       
       Libro libroTest = new Libro();
       libroTest.setIsbn("ZZZZ");
       libroTest.setTitulo("Test Libro");
       libroTest.setCategoria("Test Categoria");
      
       biblioDAO.insertar(libroTest);
      
       Libro libroTestAux = biblioDAO.buscarPorClave("ZZZZ");
      
       assertEquals(libroTest.getTitulo(), libroTestAux.getTitulo());
       assertEquals(libroTest.getCategoria(), libroTestAux.getCategoria());
      
       libroTest.setTitulo("Test Libro Update");
   
       biblioDAO.actualizar(libroTest);
      
       libroTestAux = biblioDAO.buscarPorClave("ZZZZ");
      
       assertEquals(libroTest.getTitulo(), libroTestAux.getTitulo());
      
       biblioDAO.borrar(libroTest);
    }

}

Capa de Servicio

La capa de servicio es una capa que se encuentra por encima de las capas DAOs y será utilizada por los beans que usaremos para Primefaces.

¿Por qué se usa esta capa en vez de acceder directamente al DAO? Pues, a esta pregunta se contesta con otra pregunta, ¿Qué pasa si necesitamos consumir un servicio Web donde se encuentre el manejo de toda nuestra persistencia? Esto simplifica mucho el problema y nos permite más abstracción. También facilita sacar nuestros servicio locales a servicios Web para futuras necesidades que pueda tener nuestro sistema.

Creamos la interfaz GestorBiblio y su implementación, dentro del paquete service.


package es.ocascal.biblio.service;

import java.io.Serializable;
import java.util.List;

import es.ocascal.biblio.bo.Categoria;
import es.ocascal.biblio.bo.Libro;

public interface GestorBiblio extends Serializable{
      
       public List<Libro> getLibros();  
       public List<Categoria> getCategorias();

       public Libro buscarLibro(String isbn);
       public void insertLibro(Libro libro);
       public void updateLibro(Libro libro);
       public void deleteLibro(Libro libro);  

}


package es.ocascal.biblio.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import es.ocascal.biblio.bo.Categoria;
import es.ocascal.biblio.bo.Libro;
import es.ocascal.biblio.dao.BiblioDAO;

@Service
public class GestorBiblioImpl implements GestorBiblio{

private static final long serialVersionUID = 1L;

       @Autowired
private BiblioDAO biblioDAO;

public void setBiblioDAO(BiblioDAO biblioDAO) {
             this.biblioDAO = biblioDAO;
}
   
public List<Libro> getLibros() {
              return this.biblioDAO.buscarTodos();          
}
      
public Libro buscarLibro(String isbn) {
              return this.biblioDAO.buscarPorClave(isbn);
}
      
       public List<Categoria> getCategorias() {
             return this.biblioDAO.buscarCategoriasTodos();
       }

       public void insertLibro(Libro libro) {
             this.biblioDAO.insertar(libro);
       }

       public void updateLibro(Libro libro) {
             this.biblioDAO.actualizar(libro);      
       }

       public void deleteLibro(Libro libro) {
             this.biblioDAO.borrar(libro);
       }

}

Con la anotación @Service indicamos que es un componente de servicios y Spring al detectarlo lo tratará automáticamente.

Test sobre el servicio

Para hacer el test sobre el servicio debemos definir el applicationContext el servicio para así poder leerlo desde el test.

<!-- Servicio declarado explicitamente -->
<bean id="gestorBiblio" class="es.ocascal.biblio.service.GestorBiblioImpl">
       <property name="biblioDAO" ref="biblioDAO" /> 
</bean>

Y creamos el test

package es.ocascal.biblio.service;

import static org.junit.Assert.*;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import es.ocascal.biblio.bo.Libro;

public class GestorBiblioTest {
      
    private ApplicationContext context;
    private GestorBiblio gestorBiblio;

    @Before
    public void setUp() throws Exception {
context = new             ClassPathXmlApplicationContext("classpath:applicationContext.xml");
       gestorBiblio = (GestorBiblio) context.getBean("gestorBiblio");
    }

    @Test
    public void testGetLibroList() {
        List<Libro> libros = gestorBiblio.getLibros();
        assertEquals(libros.size(), 3, 0);       
    }

    @Test
    public void testCrudLibro() {
       
       Libro libroTest = new Libro();
       libroTest.setIsbn("ZZZZ");
       libroTest.setTitulo("Test Libro");
       libroTest.setCategoria("Test Categoria");
      
       gestorBiblio.insertLibro(libroTest);
      
       Libro libroTestAux = gestorBiblio.buscarLibro("ZZZZ");
      
       assertEquals(libroTest.getTitulo(), libroTestAux.getTitulo());
       assertEquals(libroTest.getCategoria(), libroTestAux.getCategoria());
      
       libroTest.setTitulo("Test Libro Update");
   
       gestorBiblio.updateLibro(libroTest);
      
       libroTestAux = gestorBiblio.buscarLibro("ZZZZ");
      
       assertEquals(libroTest.getTitulo(), libroTestAux.getTitulo());
      
       gestorBiblio.deleteLibro(libroTest);
    }

}

Los Beans y PrimeFaces

Vamos a crear el bean que será utilizado por las vistas. Es una aplicación simple y vamos a utilizar un solo bean en el que vamos a inyectar el servicio GestorBiblio.

package es.ocascal.biblio.bean;

import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import es.ocascal.biblio.bo.Libro;
import es.ocascal.biblio.service.GestorBiblio;
      
@Component
@ManagedBean
@RequestScoped
public class LibroBean {
      
      
       @Autowired
       private GestorBiblio gestorBiblio;    
      
       public void setGestorBiblio(GestorBiblio gestorBiblio) {
             this.gestorBiblio = gestorBiblio;
       }
      
       public List<Libro> getListaLibros(){
             return this.gestorBiblio.getLibros();
       }           

}

Creamos la vista para el listado y probamos

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:p="http://primefaces.org/ui"
       xmlns:ui="http://java.sun.com/jsf/facelets">

<h:head>
       <title>Biblio</title>
</h:head>
<h:body>

       <h1>Listado Libros</h1>
       <hr />

       <p:dataTable var="libro" value="#{libroBean.listaLibros}">
             <p:column headerText="ISBN">
                    <h:outputText value="#{libro.isbn}" />
             </p:column>

             <p:column headerText="Titulo">
                    <h:outputText value="#{libro.titulo}" />
             </p:column>

             <p:column headerText="Categoria">
                    <h:outputText value="#{libro.categoria}" />
             </p:column>

       </p:dataTable>

</h:body>
</html>

Y probamos:

Antes de continuar, vamos a cambiar el aspecto de nuestra aplicación.

En el pom.xml añadimos la referencia al theme bluesky, ahora si deberemos añadir el repositorio para Primefaces.

<!-- Tema PF -->
<dependency>
       <groupId>org.primefaces.themes</groupId>
       <artifactId>bluesky</artifactId>
       <version>1.0.10</version>
</dependency>

En el web.xml añadimos el theme bluesky

<!-- Tema PF -->
<context-param>
<param-name>primefaces.THEME</param-name>
<param-value>bluesky</param-value>
</context-param>

Y nos queda una vista más elegante:

Versión final de LibroBean

package es.ocascal.biblio.bean;

import java.util.List;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import es.ocascal.biblio.bo.Categoria;
import es.ocascal.biblio.bo.Libro;
import es.ocascal.biblio.service.GestorBiblio;

@Component
@ManagedBean
@ViewScoped
public class LibroBean{

       private String isbn;
       private String titulo;
       private String categoria;
      
private Libro selectedLibro; // Libro seleccionado para modificación
   
       @Autowired
private GestorBiblio gestorBiblio;    
      
       public void setGestorBiblio(GestorBiblio gestorBiblio) {
             this.gestorBiblio = gestorBiblio;
       }
      
       public LibroBean(){ }
      
public Libro getSelectedLibro() {
return selectedLibro;
}

public void setSelectedLibro(Libro selectedLibro) {
this.selectedLibro = selectedLibro;
}
      
       public List<Categoria> getListaCategorias(){
             return this.gestorBiblio.getCategorias();            
       }
      
       public List<Libro> getListaLibros(){
             return this.gestorBiblio.getLibros();
       }
      
       public String getIsbn() {
              return this.isbn;
}
   
public void setIsbn(String isbn) {
             this.isbn = isbn;
}     
   
public String getTitulo() {
       return this.titulo;
}
   
public void setTitulo(String titulo) {
             this.titulo = titulo;
       }
   
       public String getCategoria() {
              return this.categoria;
}
   
public void setCategoria(String categoria) {
             this.categoria = categoria;
}
   
   
public void saveLibro() {
              if(gestorBiblio.buscarLibro(isbn)==null){
                    Libro l = new Libro();
             l.setIsbn(this.isbn);
             l.setTitulo(this.titulo);
                    l.setCategoria(this.categoria);
                    gestorBiblio.insertLibro(l);     
mostrarMensaje("Tenemos un nuevo libro en la biblioteca (" + this.titulo + ")");
       }else{
mostrarMensaje("Ya existe un Libro con ISBN=" + this.isbn);
       }       
}
 
   
public void updateLibro() {
       this.gestorBiblio.updateLibro(this.selectedLibro);
mostrarMensaje("Libro con ISBN=" + selectedLibro.getIsbn() + " actualizado");
}
   
public void deleteLibro() {
this.gestorBiblio.deleteLibro(this.selectedLibro);
mostrarMensaje("Libro con ISBN=" + selectedLibro.getIsbn() + " dado de baja");
}
   
public void mostrarMensaje(String mensaje) {
             FacesContext context = FacesContext.getCurrentInstance();        
context.addMessage(null, new FacesMessage("BIBLIO",   mensaje) );              
}
   
   
}

Anotaciones:
  • @Component: Con esta anotación le indicamos a Spring que es un componente y que trate al objeto automáticamente.
  • @ManagedBean: Con esta anotación le indicamos a Primefaces que es un componente Bean.
  • @ViewScoped: Esta anotación sirve para especificar a Primefaces que el bean tendrá alcance de vista.
  • @Autowired: inyección automática.

Vistas

Para implementar las vistas usamos Primefaces como siempre salvo por un problema que he detectado con los formularios. Debemos usar <form></form> en lugar de <h:form></h:form>. Este problema es por alguna de las librerías pero por más que he cambiado las versiones de jsf-impl y jsf-api no lo he podido resolver.

Esto provoca que salgan estos mensajes


Pero si cambiamos a “Producction” en el web.xml, se dejan de mostrar
<!-- Change to "Production" when you are ready to deploy -->
<context-param>
       <param-name>javax.faces.PROJECT_STAGE</param-name>
       <param-value>Development</param-value>
</context-param>

En cualquier caso, voy a intentar encontrar una solución.

Respecto al código de las vistas, es Primefaces 4 y existe mucha documentación sobre ello, no voy a profundiza, ya que este manual trata sobre la integración de Spring+Primefaces

index.xhtml

La versión final de esta vista es esta:



Y el código:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:p="http://primefaces.org/ui">

<h:head>
       <title>Biblio</title>
</h:head>
<h:body>

       <h1>Listado Libros</h1>
       <hr />


       <form id="form">

             <p:growl id="growl" showDetail="true" sticky="true" />

             <p:dataTable id="tablaLibros" var="libro"
                    value="#{libroBean.listaLibros}">
                    <p:column headerText="ISBN" style="width:64px;text-align: center">
                           <h:outputText value="#{libro.isbn}" />
                    </p:column>

                    <p:column headerText="Titulo">
                           <h:outputText value="#{libro.titulo}" />
                    </p:column>

                    <p:column headerText="Categoria">
                           <h:outputText value="#{libro.categoria}" />
                    </p:column>

                    <p:column style="width:32px;text-align: center">
                           <p:commandButton update=":libroDetail"
                                  oncomplete="PF('libroDialog').show()" icon="ui-icon-disk"
                                  title="Modificar">
                                  <f:setPropertyActionListener value="#{libro}"
                                        target="#{libroBean.selectedLibro}" />
                           </p:commandButton>
                    </p:column>

                    <p:column style="width:32px;text-align: center">
                           <p:commandButton icon="ui-icon-trash" title="Eliminar"
                                  actionListener="#{libroBean.deleteLibro}" update=":tablaLibros :growl">
                                  <f:setPropertyActionListener value="#{libro}"
                                        target="#{libroBean.selectedLibro}" />
                           </p:commandButton>
                    </p:column>

             </p:dataTable>


             <p:dialog header="Actualizar Libro " widgetVar="libroDialog"
                    modal="true" showEffect="fade" hideEffect="fade" resizable="false">

                    <p:outputPanel id="libroDetail" style="text-align:center;">
                           <p:panelGrid columns="2"
                                  rendered="#{not empty libroBean.selectedLibro}"
                                  columnClasses="label,value">

                                  <h:outputText value="ISBN:" />
                                  <h:outputText id="isbn" value="#{libroBean.selectedLibro.isbn}" />

                                  <p:outputLabel for="titulo" value="Titulo:" />
                                  <p:inputText id="titulo" value="#{libroBean.selectedLibro.titulo}"
                                        required="true" />

                                  <h:outputText value="Categoria:" />
                                  <h:selectOneMenu id="categoria"
                                        value="#{libroBean.selectedLibro.categoria}">                                  
                                        <f:selectItem itemLabel="#{libroBean.selectedLibro.categoria}" />                                       
                                        <f:selectItems value="#{libroBean.listaCategorias}" var="cat"
                                               itemValue="#{cat.id}" itemLabel="#{cat.descripcion}" />
                                  </h:selectOneMenu>
                           </p:panelGrid>

                           <p:commandButton value="Guardar" icon="ui-icon-disk"
                                  actionListener="#{libroBean.updateLibro}" update=":tablaLibros :growl" />
                    </p:outputPanel>
             </p:dialog>
       </form>
       <p:separator />

       <form>
             <p:commandButton value="Nuevo" icon="ui-icon-document" process="@this"
                    action="add.xhtml?faces-redirect=true" />
       </form>

</h:body>
</html>

add.xhtml

La pantalla final es:
Y el código:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:p="http://primefaces.org/ui">

<h:head>
       <title>Biblio</title>
</h:head>
<h:body>

       <h1>Alta</h1>

       <form>
             <p:growl id="growl" showDetail="true" sticky="true" />
             <p:panel header="Datos del Libro">

                    <h:panelGrid columns="2" cellpadding="5">
                           <p:outputLabel for="isbn" value="ISBN:" />
                           <p:inputText id="isbn" value="#{libroBean.isbn}" required="true" />

                           <p:outputLabel for="titulo" value="Titulo:" />
                           <p:inputText id="titulo" value="#{libroBean.titulo}" required="true" />

                           <p:outputLabel for="categoria" value="Categoria:" />
                           <h:selectOneMenu id="categoria" value="#{libroBean.categoria}">
                                  <f:selectItems value="#{libroBean.listaCategorias}" var="cat"
                                        itemValue="#{cat.id}" itemLabel="#{cat.descripcion}" />
                           </h:selectOneMenu>
                    </h:panelGrid>

                    <p:commandButton value="Volver" icon="ui-icon-refresh"
                           process="@this" action="index.xhtml?faces-redirect=true" />

                    <p:commandButton value="Guardar" icon="ui-icon-disk"
                           actionListener="#{libroBean.saveLibro}" update="growl" />

             </p:panel>
       </form>
</h:body>
</html>

EDITADO - Solución librerías pom.xml

Modificamos el fichero para que use las versiones adecuadas y solucionamos el problema de los mensajes...

<properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <java.version>1.6</java.version>
       <junit.version>4.9</junit.version>
       <slf4j.version>1.6.4</slf4j.version>
       <logback.version>1.0.1</logback.version>
       <log4j.version>1.2.14</log4j.version>
       <spring.version>3.1.0.RELEASE</spring.version>
       <spring.data.jpa.version>1.1.0.RELEASE</spring.data.jpa.version>
       <validation.version>1.0.0.GA</validation.version>
       <hibernate.version>4.1.7.Final</hibernate.version>
       <hibernate-validator.version>4.2.0.Final</hibernate-validator.version>
       <aspectj.version>1.6.8</aspectj.version>
       <cglib.version>2.2</cglib.version>
       <mysql.version>5.1.19</mysql.version>
       <commons.dbcp.version>1.4</commons.dbcp.version>
       <commons.pool.version>1.6</commons.pool.version>
       <commons.lang.version>2.5</commons.lang.version>
       <jsf.version>2.1.10</jsf.version>
       <primefaces.version>4.0</primefaces.version>
       <primefaces-theme.version>1.0.10</primefaces-theme.version>
       <servlet.version>2.5</servlet.version>
       <jsp.version>2.1</jsp.version>
       <jstl.version>1.2</jstl.version>
       <taglibs-standard.version>1.1.2</taglibs-standard.version>
       <maven.compiler.plugin>2.3.2</maven.compiler.plugin>
       <maven.failsafe.plugin>2.4.3-alpha-1</maven.failsafe.plugin>
</properties>

<dependencies>

       <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>${junit.version}</version>
             <scope>test</scope>
       </dependency>
       <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <version>${slf4j.version}</version>
       </dependency>
       <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>jcl-over-slf4j</artifactId>
             <version>${slf4j.version}</version>
             <scope>runtime</scope>
       </dependency>

       <!-- Spring -->
       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-core</artifactId>
              <version>${spring.version}</version>
              <exclusions>
                    <exclusion>
                           <groupId>commons-logging</groupId>
                           <artifactId>commons-logging</artifactId>
                    </exclusion>
              </exclusions>
       </dependency>

       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-beans</artifactId>
              <version>${spring.version}</version>
       </dependency>
       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-context</artifactId>
              <version>${spring.version}</version>
       </dependency>

       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-aop</artifactId>
              <version>${spring.version}</version>
       </dependency>

       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-expression</artifactId>
              <version>${spring.version}</version>
       </dependency>
       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-tx</artifactId>
              <version>${spring.version}</version>
       </dependency>

       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-asm</artifactId>
              <version>${spring.version}</version>
       </dependency>
       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-aspects</artifactId>
              <version>${spring.version}</version>
       </dependency>
       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-context-support</artifactId>
              <version>${spring.version}</version>
       </dependency>
       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-jdbc</artifactId>
              <version>${spring.version}</version>
       </dependency>
       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-orm</artifactId>
              <version>${spring.version}</version>
       </dependency>

       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-web</artifactId>
              <version>${spring.version}</version>
       </dependency>

       <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-test</artifactId>
              <version>${spring.version}</version>
              <scope>test</scope>
       </dependency>
       <dependency>
             <groupId>org.springframework.data</groupId>
             <artifactId>spring-data-jpa</artifactId>
             <version>${spring.data.jpa.version}</version>
             <exclusions>
                    <exclusion>
                           <artifactId>junit-dep</artifactId>
                           <groupId>junit</groupId>
                    </exclusion>
             </exclusions>
       </dependency>
       <dependency>
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjrt</artifactId>
             <version>${aspectj.version}</version>
       </dependency>
       <dependency>
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjweaver</artifactId>
             <version>${aspectj.version}</version>
       </dependency>
             <dependency>
             <groupId>cglib</groupId>
             <artifactId>cglib-nodep</artifactId>
             <version>${cglib.version}</version>
       </dependency>

       <dependency>
             <groupId>javax.validation</groupId>
             <artifactId>validation-api</artifactId>
             <version>${validation.version}</version>
       </dependency>
             <dependency>
             <groupId>commons-dbcp</groupId>
             <artifactId>commons-dbcp</artifactId>
             <version>${commons.dbcp.version}</version>
       </dependency>
             <dependency>
             <groupId>commons-lang</groupId>
             <artifactId>commons-lang</artifactId>
             <version>${commons.lang.version}</version>
       </dependency>
             <dependency>
             <groupId>commons-pool</groupId>
             <artifactId>commons-pool</artifactId>
             <version>${commons.pool.version}</version>
       </dependency>
      
<dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
              <version>${mysql.version}</version>
              <scope>runtime</scope>
       </dependency>


       <!-- JSF 2 -->
       <dependency>
              <groupId>com.sun.faces</groupId>
              <artifactId>jsf-api</artifactId>
              <version>${jsf.version}</version>
       </dependency>
       <dependency>
              <groupId>com.sun.faces</groupId>
              <artifactId>jsf-impl</artifactId>
              <version>${jsf.version}</version>
       </dependency>
       <dependency>
              <groupId>org.primefaces</groupId>
              <artifactId>primefaces</artifactId>
              <version>${primefaces.version}</version>
       </dependency>
       <dependency>
              <groupId>org.primefaces.themes</groupId>
              <artifactId>bluesky</artifactId>
              <version>${primefaces-theme.version}</version>
       </dependency>
       <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>servlet-api</artifactId>
             <version>${servlet.version}</version>
             <scope>provided</scope>
       </dependency>

       <dependency>
             <groupId>javax.servlet.jsp</groupId>
             <artifactId>jsp-api</artifactId>
             <version>${jsp.version}</version>
             <scope>provided</scope>
       </dependency>
       <dependency>
             <groupId>taglibs</groupId>
             <artifactId>standard</artifactId>
             <version>${taglibs-standard.version}</version>
       </dependency>
       <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>jstl</artifactId>
             <version>${jstl.version}</version>
       </dependency>


</dependencies>


@ocascal

Comentarios

Publicar un comentario

Entradas populares de este blog

Arquetipo Maven para Spring + JSF2 (PrimeFaces)

Simplificando el trabajo con Maven La creación de un proyecto desde cero con Spring + JSF2 (Primefaces) + JDBC, aunque usemos Maven, no es una tarea fácil y rápida. Si bien, una vez que se conoce lo que hay que hacer nos damos cuenta de que es algo mecánico que, tal vez, se podría automatizar. Como sabemos, Maven usa sus arquetipos para la creación y el modelado de proyectos. ¿Podríamos hacer un arquetipo que cubriera nuestras necesidades para los proyectos con Spring+Primefaces? Buscando un poco he encontrado que ya hay gente que se ha planteado esto y es lo que vamos a ver en este manual, preparando nuestro sistema desde cero. Instalación de Maven Nos vamos a la Web de descarga de Apache Maven y nos descargamos la versión 3.2.2 que en este momento es la más actual. Para ello accedemos a http://maven.apache.org/download.cgi La descomprimimos en el la ubicación donde queremos dejar Maven, para nuestro caso que es Windows lo hacemos en C:\ApacheSoftware\