Páginas

martes, 11 de diciembre de 2012

HQSLDB + Hibernate + Spring + JSF2

En esta entrada vamos a comentar cómo trabajar con Hibernate, Spring y HSQLDB. El objetivo es montar un ejemplo minimizando la parte declarativa (ficheros applicationContext.xml y faces-config.xml) y maximizando el uso de anotaciones.

Las anotaciones que aparecerán en cada una de las capas son:
  • Presentación: @ManagedBean y @SessionScoped
  • Negocio: @Service, @Autowired y @Transactional
  • DAO's: @Repository, @Autowired
  • Pojos: @Entity, @Table, @Id, @Column
Partiremos de un proyecto Maven donde tendremos los directorios estándar src/main/java y src/main/resources.

Vamos a empezar desde abajo. Tendremos una tabla llamada "EJEMPLO" con dos columnas, la columna "ID" que será la clave primaria y la columna "DESCRIPCION" que contendrá algún texto. Por tanto tendremos una clase como la siguiente:

package es.ine.sgtic.TestHSQL.pojo;

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

@Entity
@Table (name="Ejemplo")
public class EjemploPojo {

    private String id;
    private String descripcion;
   
    @Id
    @Column(name="id")
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    @Column(name="descripcion")
    public String getDescripcion() {
        return descripcion;
    }
    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }
}


El fichero src/main/resources/META-INF/persistence.xml contendrá:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
    xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

    <persistence-unit name="PERSISTENCE_UNIT" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
    </persistence-unit>
</persistence>


En la capa de acceso a datos tendríamos un interfaz IEjemploDao.java:

package es.ine.sgtic.TestHSQL.dao;

import java.util.List;
import es.ine.sgtic.TestHSQL.pojo.EjemploPojo;

public interface IEjemploDao {
    public void save(EjemploPojo pojo);
    public List<EjemploPojo> getEjemplos();
}


En la capa de acceso a datos tendríamos también una implementación JPA del interfaz anterior EjemploDaoJpa.java:

package es.ine.sgtic.TestHSQL.dao.jpa;

import java.util.List;
import javax.persistence.EntityManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.stereotype.Repository;
import es.ine.sgtic.TestHSQL.dao.IEjemploDao;
import es.ine.sgtic.TestHSQL.pojo.EjemploPojo;

@Repository
public class EjemploDaoJpa implements IEjemploDao {

    private Log logger = LogFactory.getLog(EjemploDaoJpa.class);

    @Autowired
    private EntityManager entityManager;
   
    public void save(final EjemploPojo pojo) {
        logger.info("Save(pojo)");    
        try {
            entityManager.persist(pojo);
        } catch (RuntimeException re) {
            logger.error("persist failed", re);
            throw re;
        }
    }
   
    public List<EjemploPojo> getEjemplos() {
        logger.info("getEjemplos()");       
        try {
            final StringBuilder query = 

            new StringBuilder("from ").append(EjemploPojo.class.getName()).append(" c ");
            final JpaTemplate jpaTemplate = new JpaTemplate(entityManager);
            return (List<EjemploPojo>) jpaTemplate.find(query.toString());
        } catch (RuntimeException re) {
            logger.error("getEjemplos failed", re);
            throw re;
        }
    }
}


A destacar las siguientes anotaciones:
  • @Repository. Anotación de Spring que indica que la presente clase es un DAO.
  • @Autowired. Permite que Spring inyecte el valor apropiado al atributo asociado tras invocarse el constructor de la clase.
En la capa de negocio tendríamos un interfaz IGestorEjemplo.java:

package es.ine.sgtic.TestHSQL.negocio;

import java.util.List;
import es.ine.sgtic.TestHSQL.pojo.EjemploPojo;

public interface IGestorEjemplo {
    public void salvarEjemplo(EjemploPojo pojo);
    public List<EjemploPojo> recuperaEjemplos();
}



En la capa de negocio tendríamos también una implementación del interfaz anterior GestorEjemploImpl.java:

package es.ine.sgtic.TestHSQL.negocio.impl;

import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import es.ine.sgtic.TestHSQL.dao.IEjemploDao;
import es.ine.sgtic.TestHSQL.negocio.IGestorEjemplo;
import es.ine.sgtic.TestHSQL.pojo.EjemploPojo;

@Service("gestorEjemplo")
public class GestorEjemploImpl implements IGestorEjemplo {

    private Log logger = LogFactory.getLog(GestorEjemploImpl.class);

    @Autowired
    private IEjemploDao ejemploDao;
   
    @Transactional
    public void salvarEjemplo(EjemploPojo pojo){
        logger.info("salvarEjemplo()");
        ejemploDao.save(pojo);
    }

    @Transactional
    public List<EjemploPojo> recuperaEjemplos(){
        logger.info("recuperaEjemplos()");
        return ejemploDao.getEjemplos();
    }
}


A destacar las siguientes anotaciones:
  • @Service. Anotación de Spring que indica que la presente clase es un servicio de la clase de negocio.
  • @Autowired. Permite que Spring inyecte el valor apropiado al atributo asociado tras invocarse el constructor de la clase.
  • @Transactional. Marcamos el método asociado como transaccional.
En la capa de presentación pondremos por ejemplo un bean JSF2 de ámbito "Session", que dará respaldo funcional a una o varias pantallas. En nuestro ejemplo el bean sería la clase SesionBean.java:

package es.ine.sgtic.TestHSQL.bean;

import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import es.ine.sgtic.TestHSQL.negocio.IGestorEjemplo;
import es.ine.sgtic.TestHSQL.pojo.EjemploPojo;

@ManagedBean
@SessionScoped
public class SesionBean {

    @ManagedProperty(value="#{gestorEjemplo}")
    private IGestorEjemplo gestorEjemplo;
   
    public void setGestorEjemplo(IGestorEjemplo gestorEjemplo) {
        this.gestorEjemplo = gestorEjemplo;
    }




    //Atributos que se usarán en diversas pantallas
    private String codigo;
    private String descripcion;
    private List<EjemploPojo> lst;
    

    //Métodos setter/getter de los atributos
    public String getCodigo() {
        return codigo;
    }
    public void setCodigo(String codigo) {
        this.codigo = codigo;
    }
    public String getDescripcion() {
        return descripcion;
    }
    public void setDescripcion(String descripcion) {
        this.descripcion = descripcion;
    }
    public List<EjemploPojo> getLst() {
        return lst;
    }
    public void setLst(List<EjemploPojo> lst) {
        this.lst = lst;
    }


    public String insertaEjemplo(){
        final EjemploPojo pojo = new EjemploPojo();
        pojo.setId(this.codigo);
        pojo.setDescripcion(this.descripcion);
        gestorEjemplo.salvarEjemplo(pojo);
        lst = gestorEjemplo.recuperaEjemplos();

        return null;
    }
}


A destacar las siguientes anotaciones:
  • @ManagedBean. Anotación JSF2 que marca la clase como un bean JSF.
  • @SessionScoped. Anotación JSF2 que marca indica a JSF que el Bean tiene ámbito de sessión, esto es, ofertará sus funciones/atributos accesibles a cualquier página de la capa de presentación en lo que se mantenga activa la sesión del usuario.
  • @ManagedProperty. Anotación JSF2 que indica qué gestor de negocio debe inyectarse al atributo asociado.
Nos queda únicamente comentar el contenido de los ficheros de configuración: applicationContext.xml, faces-config.xml y el pom.xml de maven.

El contenido del applicationContext.xml es el siguiente:

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:jee="http://www.springframework.org/schema/jee"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <bean id="dataSourcePool" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${proyecto.web.jdbc.driverName}" />
        <property name="url" value="${proyecto.web.jdbc.url}" />
        <property name="username" value="${proyecto.web.jdbc.user}" />
        <property name="password" value="${proyecto.web.jdbc.password}" />
    </bean>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSourcePool" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="${hibernate.show_sql}" />
                <property name="generateDdl" value="true" />
                <property name="databasePlatform" value="${hibernate.database_platform}" />
            </bean>
        </property>
    </bean>

    <bean id="entityManager" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    <tx:annotation-driven />
    <context:annotation-config base-package="es.ine.sgtic" />
    <context:component-scan base-package="es.ine.sgtic" />
</beans>


Comentarios:
  • El fichero está repleto de ${xxxxxxxx} que son los valores que se definen desde el fichero pom.xml de maven a la hora de montar el proyecto para distintos entornos: localhost, desarrollo, producción, ...
  • <context:annotation-config base-package="es.ine.sgtic" /> Activa la detección de anotaciones: @Autowire en el paquete especificado y sus dependientes.
  • <context:component-scan base-package="es.ine.sgtic" /> Activa la detección de anotaciones @Service y @Repostitory en el paquete especificado y sus dependientes.
  • showSql="${hibernate.show_sql}". Indicamos a hibernate que genere una traza en la consola por cada sentencia lanzada sobre la base de datos. Esta línea debería estar deshabilitada en producción.
  • generateDdl="true". Cuando se cree la SessionFactory de Hibernate, se creará el esquema de base de datos, cuando se cierre el  SessionFactory las tablas serán eliminadas. Este ajuste es provisional y para desarrollo, pero si ya existe una base de datos con información deberíamos dejar la línea comentada o a false.
  • databasePlatform="${hibernate.database_platform}". Este parámetro indica a hibernate la base de datos con la que va a trabajar para que realice los ajustes correspondientes en las sentencias. No todas las bases de datos entienden un SQL 100% idéntico, no todas las bases de datos colocan un Integer en el mismo tipo de campo, etc.
El contenido del faces-config.xml es el siguiente:

<?xml version="1.0" encoding="UTF-8"?>

<faces-config 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"
    version="2.0">
 

    <application>
        <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
        <resource-bundle>
            <base-name>mensaje.Mensaje</base-name>
            <var>MensajeApp</var>
        </resource-bundle>
        <locale-config>
            <default-locale>es</default-locale>
            <supported-locale>ca</supported-locale>
            <supported-locale>ca_VA</supported-locale>
            <supported-locale>eu</supported-locale>
            <supported-locale>gl</supported-locale>
            <supported-locale>en</supported-locale>
        </locale-config>
    </application>
</faces-config>


Comentarios: En el fichero desaparecen todas las declaraciones de los bean JSF y las referencias a los gestores de negocio que pudiesen usar.

El contenido del pom.xml es el siguiente:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>es.ine.sgtic</groupId>
    <artifactId>TestHSQL</artifactId>
    <packaging>war</packaging>
    <version>1.0</version>
    <build>
        <resources>
            <resource>
                <filtering>false</filtering>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>*.properties</exclude>
                    <exclude>applicationContext*.xml</exclude>
                </excludes>
            </resource>
            <resource>
                <filtering>true</filtering>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.properties</include>
                    <include>applicationContext*.xml</include>
                </includes>
            </resource>
        </resources>
        <finalName>${proyecto.web.final_name}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${proyecto.build.jdk_version}</source>
                    <target>${proyecto.build.jdk_version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>tomcat-maven-plugin</artifactId>
                <configuration>
                    <warFile>${project.build.directory}/${project.build.finalName}.war</warFile>
                    <update>true</update>
                    <path>/${project.build.finalName}</path>
                    <charset>${project.build.sourceEncoding}</charset>
                    <url>http://${proyecto.tomcat_deploy.server_port}/manager</url>
                    <username>${proyecto.tomcat_deploy.user}</username>
                    <password>${proyecto.tomcat_deploy.password}</password>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>es.ine.sgtic.lib.spring.2.5.0.j2ee</groupId>
            <artifactId>el-api</artifactId>
            <version>1.5.0_06</version>
            <type>jar</type>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>es.ine.sgtic.lib.tomcat6</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <type>jar</type>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>es.ine.sgtic.lib.spring.2.5.0.junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.4</version>
            <type>jar</type>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>es.ine.sgtic.comun.lib.webapp</groupId>
            <artifactId>jsf2mojarra-hibernate-bbddunica-lib</artifactId>
            <version>2.0</version>
            <type>pom</type>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <artifactId>ine-util-comunes</artifactId>
                    <groupId>es.ine.sgtic.comun.util.utilidades</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>es.ine.sgtic.lib.spring.2.5.0.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>es.ine.sgtic.comun.util.utilidades</groupId>
            <artifactId>ine-util-base</artifactId>
            <version>2.0</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <properties>
        <!-- Propiedades del proyecto -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <proyecto.build.jdk_version>1.6</proyecto.build.jdk_version>

        <!-- Propiedades del plugins -->
        <proyecto.tomcat_deploy.server_port>localhost:8080</proyecto.tomcat_deploy.server_port>
        <proyecto.tomcat_deploy.user>admin</proyecto.tomcat_deploy.user>
        <proyecto.tomcat_deploy.password>admin</proyecto.tomcat_deploy.password>

        <!-- Ajustes hibernate  -->
        <hibernate.show_sql>true</hibernate.show_sql>
        <hibernate.database_platform>org.hibernate.dialect.HSQLDialect</hibernate.database_platform

        <proyecto.web.jdbc_comun.driverName>org.hsqldb.jdbcDriver</proyecto.web.jdbc_comun.driverName>

        <!-- Log de Aplicacion -->
        <log4j.debug.ruta_fichero>/var/log/tomcat6/logs/TestHSQL/local</log4j.debug.ruta_fichero>
        <log4j.debug.MaxFileSize>9999KB</log4j.debug.MaxFileSize>
        <log4j.debug.MaxBackupIndex>3</log4j.debug.MaxBackupIndex>
        <log4j.debug.appender_activo>CONSOLA,FICHERO</log4j.debug.appender_activo>
        <log4j.debug.nivel_traza.general>ERROR</log4j.debug.nivel_traza.general>
        <log4j.debug.nivel_traza.spring>
ERROR</log4j.debug.nivel_traza.spring>
        <log4j.debug.nivel_traza.hibernate>
ERROR</log4j.debug.nivel_traza.hibernate>
        <log4j.debug.nivel_traza.myfaces>
ERROR</log4j.debug.nivel_traza.myfaces>
        <log4j.debug.nivel_traza.apache_commons>
ERROR</log4j.debug.nivel_traza.apache_commons>

        <!-- Propiedades del application-context -->
        <proyecto.web.final_name>TestHSQL</proyecto.web.final_name>

        <!-- BBDD -->
        <!-- StandAlone (El servidor de BBDD levantado en otro sitio) -->
        <!-- <proyecto.web.jdbc_padron.url>jdbc:hsqldb:hsql://localhost/dbname</proyecto.web.jdbc_padron.url> -->
        <!-- Fichero (El fichero en la raíz del classpath) -->
        <proyecto.web.jdbc_padron.url>jdbc:hsqldb:file:dbname</proyecto.web.jdbc_padron.url>
        <!-- Fichero (El fichero en el disco duro) -->
        <!--<proyecto.web.jdbc_padron.url>jdbc:hsqldb:file:/home/egdepedro/Escritorio/dbname</proyecto.web.jdbc_padron.url>-->
        <!-- Memoria (No hay fichero) -->
        <!-- <proyecto.web.jdbc_padron.url>jdbc:hsqldb:mem:dbname</proyecto.web.jdbc_padron.url> -->
        <proyecto.web.jdbc_padron.user>sa</proyecto.web.jdbc_padron.user>
        <proyecto.web.jdbc_padron.password></proyecto.web.jdbc_padron.password>
    </properties>
</project>



Comentarios:

En este fichero se fijan los parámetros que definen la base de datos, el comportamiento de hibernate, etc. En este caso no se han definido profiles, pero podríamos tener varios "desa", "prod", ...
En cuanto a la BBDD HSQLDB, se plantean tres posibilidades, en la primera tenemos una base de datos en la raíz del classpath: "jdbc:hsqldb:file:dbname", en la segunda tenemos la base de datos en cierta ubicación del disco duro: "jdbc:hsqldb:file:/home/xxxxxx/yyyyyy/dbname" y en la tercera tenemos la BBDD permanentemente levantada en cierto servidor: "jdbc:hsqldb:hsql://xxx.xxx.xxx.xxx/dbname"



No hay comentarios:

Publicar un comentario