domingo, 12 de febrero de 2017

Tutorial: Hola Mundo: Capa de Persistencia

Hasta ahora se construyó una aplicación  básica para mostrar los conceptos básicos de Spring Framework.


En ésta entrada se mostrará la integración de la capa de persistencia, haciendo énfasis únicamente en la persistencia sin detalle de los conceptos ya mencionados.

Contenido


Crear Proyecto
En Eclipse Crear proyecto Dynamic Web Project.
Convertir el proyecto a Proyecto Maven.

Modificar el POM.XML: Las siguientes son las dependencias necesarias para completar el tutorial.
<dependencies>
    <!-- Spring -->
    <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-core</artifactId>
     <version>4.3.2.RELEASE</version>
 </dependency>
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>4.3.2.RELEASE</version>
 </dependency>
 <!-- Servlet -->
 <dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>servlet-api</artifactId>
  <version>2.5</version>
  <scope>provided</scope>
 </dependency>
 <!-- Hibernate -->  
 <dependency>
     <groupId>org.hibernate</groupId>
     <artifactId>hibernate-entitymanager</artifactId>
     <version>5.2.2.Final</version>
 </dependency>  
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-orm</artifactId>
     <version>4.3.2.RELEASE</version>
 </dependency>
 <!-- Oracle, instalado localmente en Archiva --> 
 <dependency>
   <groupId>com.oracle</groupId>
   <artifactId>ojdbc7</artifactId>
   <version>12.1.0.1</version>
 </dependency>  
</dependencies>

Dentro de las dependecias se incluye el módulo ORM (Object Relational Mapping) de Spring, y en EntityManager de Hibernate.

IMPORTANTE: La dependencia del JDBC de Oracle (ojdbc7) NO se encuentra en el repositorio central de Maven.
Es necesario descargarla del sitio de Oracle y adicionarla manualmente a Maven o como librería de la aplicación. Si se está haciendo uso de un gestor como Apache Archiva, simplemente se puede subir la libería a Archiva, asignarle un GAV y se podrá acceder normalmente desde el POM. Por ejemplo para el ejercicio se subió a Archiva con el siguiente GAV:
<!-- Oracle, instalado localmente en Archiva --> 
 <dependency>
   <groupId>com.oracle</groupId>
   <artifactId>ojdbc7</artifactId>
   <version>12.1.0.1</version>
 </dependency>

Crear una clase java para el controlador:Crear una clase básica con el siguiente contenido:
package pkg.sw.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/")
public class PersistenceController {

 @RequestMapping("/hello") 
 public ModelAndView viewDate () {    
  return new ModelAndView("hello");
 }

}

Crear JSP: Crear dos JSP uno inicial llamado index.jsp y otro dentro del directorio jsp: jsp/hello.jsp.

index.jsp


<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Soluciones Web</title>
</head>
<body>
 <h1>Hola mundo - Capa Persistencia</h1>
 <a href="hello">Dar Click acá</a>
</body>
</html>

jsp/hello.jsp

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Soluciones Web</title>
</head>
<body>
 <h2>Respuesta desde el controlador Spr</h2> 
</body>
</html>

Crear los archivos de configuración de Spring que se mostraron en la entrada anterior y de la misma forma relacionarlos en el Web.xml.


  • web.xml
  • applicationContext.xml
  • spring-servlet.xml
Probar la aplicación a éste punto: Al cargarla muestra el index.jsp a dar clic en el enlace muestra hello.jsp.
Servicio:
Ahora se creará el servicio con su interfaz. Se creará una operación que envíe un mensaje al JSP mediante la inyección en el controlador:
Interfaz servicio:
package pkg.sw.service;

public interface ProductService {

 public String getProducts();

}


Implementación Servicio:
package pkg.sw.service.impl;

import org.springframework.stereotype.Service;
import pkg.sw.service.ProductService;


@Service
public class ProductServiceImpl implements ProductService{

 public ProductServiceImpl() {}
 
 public String getProducts()
 {
  return "Product1 <br/> Product2 <br/> Product3";
 }

}


Modificar Controlador:
package pkg.sw.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import pkg.sw.service.ProductService;

@Controller
@RequestMapping("/")
public class PersistenceController {

 @Autowired
 private ProductService productService;
 
 @RequestMapping("/hello") 
 public ModelAndView viewDate () {  
  
 return new ModelAndView("hello", 
                   "listProducts", productService.getProducts());
 }

}


Mostrar la variable enviada en el modelo desde en controlador en el JSP:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Soluciones Web</title>
</head>
<body>
 <h2>Respuesta desde el controlador Spr</h2> 
 <div>${listProducts}</div>
</body>
</html>

IMPORTANTE: Para efectos de éste ejercicio, con el fin de mostrar fácilmente el resultado, se envía dentro de la cadena las etiquetas BR de salto de línea, en vez de hacerlos con un objeto y con JSTL o AngularJS, pero en aplicaciones reales no es recomendable ésta práctica.

Probar la aplicación a éste punto:

Procedimiento Persistencia:
A éste punto se ha repasado lo construido en el tutorial anterior, a partir de ahora se inicia la construcción y configuración de la capa de persistencia.
Con el ejercicio se pretende mostrar la configuración necesaria para Hibernate, y no se tiene en cuenta el paso de parámetros, simplemente se manejarán cadenas de texto (String). Tampoco se tiene en cuenta el flujo de excepciones/validaciones para efectos del ejercicio.

Configuración:
Configuración del archivo applicationContext.xml: Este archivo es de configuración de la aplicación y se adicionará la configuración de persistencia de Hibernate, se recomienda separar en un archivo exclusivo, pero para éste ejercicio se manejará sobre el mismo archivo. Se adiciona también a la cabecera los esquemas necesarios.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:jee="http://www.springframework.org/schema/jee"
 xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/jee 
        http://www.springframework.org/schema/jee/spring-jee.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">        
   
    <!-- enabling annotation driven configuration /-->
    <context:annotation-config/>
 
  <!-- DataSource -->
  <jee:jndi-lookup id="dataSource" jndi-name="jdbc/SWTEST"/>  
    
 <!-- EntityManager -->
 <bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
   <property name="dataSource" ref="dataSource" />
   <property name="packagesToScan" value="pkg.sw.repository" />
   <property name="jpaVendorAdapter">
         <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
      </property>
      <property name="jpaProperties">
         <props>            
            <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>                       
         </props>
      </property>      
   </bean>
   
   <!-- Transaction Manager -->
   <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <property name="entityManagerFactory" ref="myEmf" />
   </bean>
   
   <tx:annotation-driven />  
   
<!-- Exception translation bean post processor -->
   <bean id="persistenceExceptionTranslationPostProcessor"
      class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" /> 
    
    <!-- Scans the classpath of this application for @Components to deploy as beans -->
    <context:component-scan base-package="pkg.sw.repository" />
    <context:component-scan base-package="pkg.sw.service" />

</beans>

En el archivo se ha creado:

  • El Data Source por JNDI. Significa que la administración de la conexión a la base de datos se le delegó al servidor de aplicaciones Weblogic Server. Por lo tanto se debe crear previamente el DataSource 'jdbc/SWTEST' en Weblogic.
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/SWTEST"/>
    



    NOTA: Si se desea también se puede hacer la conexión manualmente desde la aplicación así:

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
           <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
           <property name="url" value="jdbc:oracle:thin:@10.85.84.142:1521:DSFSPE" />
           <property name="username" value="user" />
           <property name="password" value="pass" />
     </bean>
    


  • La definición del EntityManager
    Hibernate SessionFactory vs. EntityManagerFactory
    Es preferible EntityManagerFactory y EntityManager porque están definidos por el estándar JPA. SessionFactory y Session son específicos de Hibernate. El EntityManager invoca Session de Hibernate internamente.
    <!-- EntityManager -->
        <bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
          <property name="dataSource" ref="dataSource" />
          <property name="packagesToScan" value="pkg.sw.repository" />
          <property name="jpaVendorAdapter">
             <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
          </property>
          <property name="jpaProperties">
             <props>            
                <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>                       
             </props>
          </property>      
       </bean>
    


    Donde se referencia al datasource creado, se especifican los paquetes a escanear, y se configura Hibernate, en este caso el dialecto para la base de datos Oracle.

  • Configuración del Transacción Manager. Donde se referencia el Bean del Entity Manager creado.
    <!-- Transaction Manager -->
       <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
          <property name="entityManagerFactory" ref="myEmf" />
       </bean>
  • Habilitar Transacción Manager y anotaciones @Transactional.
    <tx:annotation-driven />
  • Finalmente el bean que actúa como traductor de manejo de excepciones para los beans que tengan la anotación @Repository.
    (http://docs.spring.io/spring/docs/current/spring-framework-reference/html/orm.html )
    <!-- Exception translation bean post processor -->
    <bean id="persistenceExceptionTranslationPostProcessor"
    class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
    

Weblogic.xml: Para evitar problemas de compatibilidad de las librerías existentes en Weblogic vs las librerías incluidas en el POM.XML se indicará en el archivo weblogic.xml de la aplicación preferencia a las librerías del WEB-INF:
<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app" 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-app_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.8/weblogic-web-app.xsd">
<wls:weblogic-version>12.2.1</wls:weblogic-version>
<wls:context-root>SWHelloWorlPersistence</wls:context-root>
    
<wls:container-descriptor>
    <wls:prefer-web-inf-classes>true</wls:prefer-web-inf-classes>
</wls:container-descriptor>
    
</wls:weblogic-web-app>

Se pone a TRUE el tag prefer-web-inf-classes, lo que hará que las clases ubicadas en el directorio WEB-INF de la aplicación Web, se carguen con preferencia sobre las clases cargadas en el contenedor.
Crear entidad: Se crea una entidad en la base de datos a donde se hizo la conexión. Para el ejercicio se creará una tabla PRODUCTO con los atributos ID, MARCA, NOMBRE, PRECIO.

Se crea la clase java para la entidad, con las anotaciones correspondientes como se hace normalmente JPA.
package pkg.sw.repository;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="SW_TEST_PRODUCTS") 
public class Product implements Serializable {

 private static final long serialVersionUID = 1L; 
 @Id
    @Column(name = "ID")    
    private Integer id; 
 @Column(name = "NOMBRE")
    private String nombre;
    @Column(name = "PRECIO")
    private Integer precio;
    @Column(name = "MARCA")
    private String marca;
    
 public Integer getID() {
  return id;
 }
 public void setID(Integer id) {
  id = id;
 }
 public String getNOMBRE() {
  return nombre;
 }
 public void setNOMBRE(String nombre) {
  nombre = nombre;
 }
 public Integer getPRECIO() {
  return precio;
 }
 public void setPRECIO(Integer precio) {
  precio = precio;
 }
 public String getMARCA() {
  return marca;
 }
 public void setMARCA(String marca) {
  marca = marca;
 }
 
 public String toString() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("nombre: " + nombre + ",");
        buffer.append("precio: " + precio + ",");
        buffer.append("marca: " + marca);
        return buffer.toString();
    }

}

Adicionalmente se hizo la operación toString, simplemente para devolver la información del objeto en una cadena.

Crear el DAO

Interface: ProductDao.java 


package pkg.sw.repository;

public interface ProductDao {

 public String getProductList();
 public void persistProduct(Product prod);

}

Implementación: ProductDaoImpl.java: Donde se pone la anotación @Repository que es el estereotipo de Spring para la capa de persistencia, se inicializa el EntityManager para luego ser usado en las operaciones, por ejemplo para éste caso de consulta y de persistencia.
package pkg.sw.repository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import pkg.sw.repository.ProductDao;
import pkg.sw.repository.Product;

import java.util.List;

@Repository(value = "productDaoSW")
public class ProductDaoImpl implements ProductDao {
 
 private EntityManager em = null;

    /*
     * Sets the entity manager.
     */
    @PersistenceContext
    public void setEntityManager(EntityManager em) {
        this.em = em;
    }
    
    @Transactional(readOnly = true)
 public String getProductList()
 {     
     List _products = em.createQuery("select p from Product p order by p.ID").getResultList();
     
     StringBuffer buffer = new StringBuffer();        
        
     for (Product p : _products)
     {
      buffer.append(p.toString() + "
");
     }
          
     return  buffer.toString();     
 }    
    
    @Transactional(readOnly = false)
    public void persistProduct(Product prod) {
        em.persist(prod);
    }

}

Para el ejercicio se crearon dos operaciones una de consulta a la tabla y otra de inserción.


Ajustar el Servicio
Las clase del servicio se ajustan para utilizar las dos operaciones expuestas en el DAO, se inyectará el DAO con @Autowired de la misma forma como se ha hecho antes.
Interface: ProductService.java


package pkg.sw.service;

import pkg.sw.repository.Product;

public interface ProductService {

 public String getProducts();
 public void saveProduct(Product prod);

}

Implementación: ProductServiceImpl.java: Donde se inyecta el DAO y se crean las operaciones para mostrar la lista de productos desde la tabla de la base de datos y para guardar un nuevo registro.
package pkg.sw.service.impl;

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

import pkg.sw.repository.Product;
import pkg.sw.repository.ProductDao;
import pkg.sw.service.ProductService;


@Service
public class ProductServiceImpl implements ProductService{
 
  @Autowired
  private ProductDao productDao;
  
  public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
     }
  
 public String getProducts()
 {
  String _products = productDao.getProductList();
  return _products;
 }
 
 public void saveProduct(Product prod)
 {
  productDao.persistProduct(prod);
 }
}

Ajustar el controlador:
Como ya se tenía inyectado el servicio en el controlador, ahora se va a actualizar para que invoque las nuevas operaciones creadas, se redireccionará al JSP hello para mostrar la lista de productos desde la base de datos, y se enviará al JSP addProduct para registrar un nuevo registro en la base de datos.

PersistenceController.java
package pkg.sw.controller;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import pkg.sw.repository.Product;
import pkg.sw.service.ProductService;

@Controller
@RequestMapping("/")
public class PersistenceController {

 @Autowired
 private ProductService productService;
 
 @RequestMapping("/hello") 
 public ModelAndView viewDate () {  
  
  return new ModelAndView("hello", "listProducts", productService.getProducts());
 }
 
 @RequestMapping(value="/addProduct", method = RequestMethod.GET)
    protected Product formBackingObject(HttpServletRequest request) throws ServletException {
   Product product = new Product();         
        return product;
    }

 @RequestMapping(value="/addProduct", method = RequestMethod.POST)
 public ModelAndView onSubmit(@Valid Product product, BindingResult result)
 {  
  product.setID(5);
  productService.saveProduct(product);
  
  ModelAndView mv = new ModelAndView("hello");
  mv.addObject("listProducts", productService.getProducts());
  mv.addObject("msg", "Producto insertado.");  
  return mv;
  
 }
}


Hay que anotar que se hicicron dos operaciones en el controlador para "/addProduct", una por POST y otra por GET
La que va por GET es la que se invoca al inicio, simplemente en el link del JSP index hacia el JSP addProduct, ésta devuelve un nuevo objeto del tipo de la entidad Product. En el siguiente paso del JSP se explicará más al respecto.


@RequestMapping(value="/addProduct", method = RequestMethod.GET)
    protected Product formBackingObject(HttpServletRequest request) throws ServletException {
   Product product = new Product();         
        return product;
    }

La que va por POST onSumit, se ejecutará al oprimir el botón submit del formulario en el JSP, es en ésta operación donde se persiste el producto que se recibe en el parámetro y finalmente se direcciona a el JSP hello para ver la lista de los productos de la base de datos.
@RequestMapping(value="/addProduct", method = RequestMethod.POST)
 public ModelAndView onSubmit(@Valid Product product, BindingResult result)
 {  
  productService.saveProduct(product);
  
  ModelAndView mv = new ModelAndView("hello");
  mv.addObject("listProducts", productService.getProducts());
  mv.addObject("msg", "Producto insertado.");  
  return mv;
  
 }



Ajuste de JSPs:

hello.jsp: Simplemente muestra los dos parámetros que recibe desde el modelo:
  • listProducts
  • msg

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Soluciones Web</title>
</head>
<body>
 <h2>Respuesta desde el controlador Spr</h2> 
 <div>${listProducts}</div>
 <br/>
 <br/>
 <div>${msg}</div>
 <br/>
 <a href="/SWHelloWorlPersistence/">Home</a>
</body>
</html>


Creación de addProduct.jsp: para éste JSP se va a utilizar <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> que son las etiquetas de Formulario de Spring, las cuales tienen atributos que permitirán mapear la entidad Product para pasarla al controlador. No se profundzará el uso de Formulario de Spring en ésta entrada.

addProduct.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Soluciones Web</title>
</head>
<body>
 <h3>Add Product</h3> 
 <form:form method="post" commandName="product">
  ID<form:input path="ID"/>
  Marca<form:input path="MARCA"/>
  Nombre<form:input path="NOMBRE"/>
  Precio<form:input path="PRECIO"/>
  <input type="submit" value="Enviar">
 </form:form>
 <a href="/SWHelloWorlPersistence/">Home</a>
</body>
</html>

Formulario de Spring: El atributo commandName="product" de la etiqueta form, es el parámetro que se recibe desde la invocación get, e inicializa el objeto.
<form:form method="post" commandName="product">


Product product = new Product();         
        return product;



Los atributos path de los input así mismo deben ser los nombres de los atributos del objeto, para éste caso de Product:
ID<form:input path="id"/>
Marca<form:input path="marca"/>
Nombre<form:input path="nombre"/>
Precio<form:input path="precio"/>
<input type="submit" value="Enviar">


     @Id
    @Column(name = "ID")    
    private Integer id; 
 @Column(name = "NOMBRE")
    private String nombre;
    @Column(name = "PRECIO")
    private Integer precio;
    @Column(name = "MARCA")
    private String marca;

De ésta manera se envía el objeto Product seteado al controlador, y como el formulario está con método POST se dispara la operación de persistencia en el controlador.

Probar la aplicación:

Lista de productos:

Adicionar Producto:

Comprobar la inserción en la base de datos:

Conclusión
Se ha mostrado la configuración de la capa de persistencia y se obtienen los conceptos de las configuraciones XML de Spring para una aplicación completa:




No hay comentarios.:

Publicar un comentario