Persistencia de Objetos Java: El Camino hacia Hibernate

En esta p�gina finalmente dejaremos atr�s la aburrida aplicaci�n basada en la l�nea de comandos e integraremos Tomcat, WebWork e Hibernate para crear una peque�a aplicaci�n web.

El primer paso es instalar Tomcat. Este ejemplo utiliza Tomcat 4.1.29.

.�Reestructurar el Directorio de Trabajo

Primero, reestructuraremos nuestro directorio de trabajo, para mantener las partes de nuestra aplicaci�n claramente separadas:


.
    +src
        +de
            +gloegl
                +road2hibernate
                    +data
                        User.java
                        Event.java
                        User.hbm.xml
                        Event.hbm.xml
                    +actions
        hibernate.cfg.xml
        log4j.properties
    +config
    +static-web
    +views
    +lib
        cglib2.jar
        commons-logging.jar  
        hibernate2.jar      
        jta.jar    
        odmg.jar
        commons-collections.jar  
        dom4j.jar            
        jdbc2_0-stdext.jar  
        log4j.jar 

Hemos movido nuestras clases y mapeos al subdirectorio data de road2hibernate. Por supuesto que tambi�n hemos tenido que modificar las declaracioens package de los ficheros Java y los nombres de clases en los ficheros de mapeo.

Adem�s, hemos creado nuevos directorios:

  • src/de/gloegl/road2hibernate/actions
    Contendr� el c�digo fuente de las acciones WebWork.
  • config
    Contendr� los ficheros de configuraci�n que despu�s se situar�n en el WEB-INF/config de nuestra aplicaci�n web.
  • static-web
    Contendr� todo el contenido est�tico de nuestra aplicacion como ficheros HTML y de imagen.
  • views
    Contendr� todos nuestros ficheros de vistas que contienen el html que despu�s se mostrar� al usuario.

Como utilizaremos WebWork para nuestra aplicaci�n y Velocity como motor de plantillas de vistas, necesitar�s descargar WebWork y Velocity. S�lo necesitaremos webwork.jar y velocity-dep-1.3.1.jar, para situarlos en el directorio lib bajo el directorio de trabajo.

.�Configurar WebWork

Ahora configuraremos nuestra aplicaci�n para que utilice WebWork. Primero necesitamos un fichero web.xml que situaremos en el directorio config:

.�config/web.xml:


<!DOCTYPE web-app PUBLIC 
    "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" 
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-appara>
   <display-name>Eventmanager</display-name>

    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>webwork.dispatcher.ServletDispatcher</servlet-class>
        <load-on-startupara>1</load-on-startupara>
    </servlet>

    <servlet>
        <servlet-name>velocity</servlet-name>
        <servlet-class>webwork.view.velocity.WebWorkVelocityServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>velocity</servlet-name>
        <url-pattern>*.vm</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-appara>

Este fichero contiene todos los servlets y mapeos de servlets que WebWork necesita para ejecutarse.

Adem�s, necesitaremos un fichero webwork.properties, que situaremos directamente en el directorio src:

.�src/webwork.properties:

webwork.action.packages=de.gloegl.road2hibernate.actions, webwork.action.standard

Este fichero contiene s�lo la declaraci�n de los paquetes donde WebWork buscar� sus acciones.

.�Actualizar el Proceso de Construcci�n

Ahora haremos que nuestro proceso de construcci�n situe juntos todos los ficheros y genere la estructura adecuada para una aplicaci�n web:

.�en build.xml:


<project name="hibernate-tutorial" default="compile">

    <property name="sourcedir" value="${basedir}/src"/>
    <property name="targetdir" value="${basedir}/bin"/>
    <property name="librarydir" value="${basedir}/lib"/>
    <property name="configdir" value="${basedir}/config"/>
    <property name="wardir" value="${basedir}/war"/>
    <property name="viewsdir" value="${basedir}/views"/>
    <property name="static-htmldir" value="${basedir}/static-web"/>

    <target name="setup-war-structure" depends="compile">
        <mkdir dir="${wardir}"/>
        <mkdir dir="${wardir}/WEB-INF"/>
        <mkdir dir="${wardir}/WEB-INF/classes"/>
        <mkdir dir="${wardir}/WEB-INF/lib"/>
        <mkdir dir="${wardir}/WEB-INF/views"/>
        
        <copy todir="${wardir}/WEB-INF/classes">
            <fileset dir="${targetdir}"/>
        </copy>
        <copy todir="${wardir}/WEB-INF/lib">
            <fileset dir="${librarydir}"/>
        </copy>
        <copy todir="${wardir}/WEB-INF/views">
            <fileset dir="${viewsdir}"/>
        </copy>
        <copy todir="${wardir}/WEB-INF">
            <fileset dir="${configdir}"/>
        </copy>
        <copy todir="${wardir}/">
            <fileset dir="${static-htmldir}"/>
        </copy>
    </target>

    ...

Ejecutando este objetivo se crear� un directorio llamado war, que contendr� la aplicaci�n web igual que luego se estructurar� en el fichero war. Despu�s de ejecutar este objetivo, el directorio war se parecer� a esto:


    +war
        +WEB-INF
            web.xml
            +classes
                +de
                    +gloegl
                        +road2hibernate
                            +data
                                User.class
                                Event.class
                                User.hbm.xml
                                Event.hbm.xml   
                            +actions                             
                hibernate.cfg.xml
                log4j.properties
                webwork.properties
            +lib
                <all library files here>

Ahora a�adiremos las tareas espec�ficas de Tomcat a nuestro fichero de construcci�n, para as� poder instalar nuestra aplicaci�n en Tomcat utilizando ant. Necesitar�s ajustar el ejemplo a tu entorno:

.�en build.xml:


<project name="hibernate-tutorial" default="compile">

    ....

    <!-- ADJUST THIS ! -->
    <property name="manager.url"   value="http://localhost:8080/manager"/>
    <property name="tomcatdir" value="C:/Program Files/Tomcat"/>
    <property name="app.path"      value="/eventmanager"/>
    <property name="manager.username" value="username"/>
    <property name="manager.password" value="password"/>

    <path id="tasks.classpath">
        <fileset dir="${tomcatdir}/server/lib">
          <include name="catalina-ant.jar"/>
        </fileset>
    </path>

    <taskdef 
        name="install" 
        classname="org.apache.catalina.ant.InstallTask" 
        classpathref="tasks.classpath"/>
    <taskdef 
        name="reload"  
        classname="org.apache.catalina.ant.ReloadTask" 
        classpathref="tasks.classpath"/>
    <taskdef 
        name="remove"  
        classname="org.apache.catalina.ant.RemoveTask" 
        classpathref="tasks.classpath"/>

    <target name="install" depends="setup-war-structure">
        <install url="${manager.url}"
            username="${manager.username}"
            password="${manager.password}"
            path="${app.path}"
            war="file://${wardir}"/>
    </target>

    <target name="reload" depends="setup-war-structure">    
        <reload url="${manager.url}"
            username="${manager.username}"
            password="${manager.password}"
            path="${app.path}"/>    
    </target>
    
    <target name="remove">
        <remove url="${manager.url}"
            username="${manager.username}"
            password="${manager.password}"
            path="${app.path}"/>
    </target>

    ...
</project>

Ahora arrancamos Tomcat y ejecutamos ant install desde la l�nea de comandos -- esto instalar� la aplicaci�n en Tomcat. Deber�as poder acceder a la aplicaci�n eventmanager apuntando tu navegador a la direcci�n http://localhost:8080/eventmanager/ - sin embargo s�lo obtendr�s un listado de directorio generado por Tomcat. Todav�a no hemos creado ning�n contenido para que lo muestre la aplicaci�n.

.�Una Primera Acci�n WebWork

Nuestra primera acci�n WebWork ser� muy simple - no hara nada excepto reenviar a una vista:

.�/src/de/gloegl/road2hibernate/actions/EventList.java:


package de.gloegl.road2hibernate.actions;

import webwork.action.ActionSupport;

public class EventList extends ActionSupport {
    
}

La clase extiende ActionSupport pero no sobreescribe ning�n m�todo. Por eso tiene el comportamiento por defecto de ActionSupport: la acci�n ser� reenviada a la vista definida como success. Esto es lo que tenemos que hacer en un fichero llamado views.properties, situado en el directorio src:

.�/src/views.properties:

eventlist.action=EventList
eventlist.success=/WEB-INF/views/eventlist.vm

Esto define una acci�n llamada eventlist que ser� manejada por nuestra clase EventList. Adem�s, se ha definido la vista success. Ahora tenemos que escribir el fichero eventlist.vm, que situaremos el directorio views - ant lo copiar� a la localizaci�n correcta.

.�views/eventlist.vm:


<html>
    <head>
        <title>Event List</title>
    </head>
    <body>
        <h1>Successful</h1>
    </body>
</html>

Como puedes ver, es s�lo un fichero HTML ... que modificaremos para incluir contenido din�mico m�s adeltante. Ahora ejecuta ant reload. Deber�as poder probar la acci�n apuntando tu nevegador a http://localhost:8080/eventmanager/eventlist.action - deber�as ver la cabecera Successful en tu navegador.

.�El Patr�n "Open Session in View"

Cuando utilizamos caracter�sticas como la carga lenta, Hibernate necesita una conexi�n abierta. Para hacer esto f�cilmente en una aplicaci�n web usaremos un patr�n sencillo. Crearemos un servlet filter que manejar� nuestra sesi�n y la cerrar� al final de la petici�n web.

.�src/de/gloegl/road2hibernate/util/SessionManager.java:


package de.gloegl.road2hibernate.util;

import java.io.*;
import javax.servlet.*;

import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Session;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.cfg.Configuration;

public class SessionManager implements Filter
{
    protected static ThreadLocal hibernateHolder = new ThreadLocal(); 
    protected static ThreadLocal txHolder = new ThreadLocal(); 
    protected static SessionFactory factory;
    
     public void init(FilterConfig filterConfig) throws ServletException
     {
        // Initialize hibernate
        try
        {
            factory = new Configuration().configure().buildSessionFactory();
        }
        catch (HibernateException ex) { throw new ServletException(ex); }
     }
     
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
                   throws IOException, ServletException
    {
        if (hibernateHolder.get() != null)
            throw new IllegalStateException(
                "A session is already associated with this thread!  "
                + "Someone must have called getSession() outside of the context "
                + "of a servlet request.");
            
        try
        {
            chain.doFilter(request, response);
        }
        finally
        {
            Session sess = (Session)hibernateHolder.get();
            Transaction tx = (Transaction)txHolder.get();
            if (sess != null)
            {
                hibernateHolder.set(null);
                
                try
                {
                    if (tx != null) {
                        tx.commit();
                    }
                    sess.close();
                }
                catch (HibernateException ex) { throw new ServletException(ex); }
            }
        }
    }
    
    public static Session getSession() throws HibernateException
    {
        Session sess = (Session)hibernateHolder.get();
        Transaction tx = (Transaction)txHolder.get();
                
        if (sess == null)
        {
            sess = factory.openSession();
            txHolder.set(sess.beginTransaction());
            hibernateHolder.set(sess);
        } else {
            if (tx == null) {
                throw new IllegalStateException("Transaction was allready rolled back");
            }
        }    
        return sess;
    }
    
    public static void rollback() throws HibernateException {
        Transaction tx = (Transaction)txHolder.get();
        if (tx == null) {
            throw new IllegalStateException("Transaction was allready rolled back");
        }
        
        tx.rollback();
        txHolder.set(null);
    }
            
    
    public void destroy() {
        try {
            factory.close();
        }
        catch (HibernateException ex) { throw new RuntimeException(ex); }
    }       
}

Veamos que se ha hecho aqu�: la clase SessionManager almacenar� una sesi�n Hibernate en una variable ThreadLocal. Un ThreadLocal contendr� un ejemplar de una variable por cada thread. Por eso si hay dos Threads paralelos ejecut�ndose, el ThreadLocal contendr� dos sesiones. Se llamar� al m�todo doFilter en cada petici�n. Permitir� que la petici�n contin�e llamando a chain.doFilter(). Despu�s de que se haya terminado el procesamiento de la petici�n chain.doFilter() retorna, recupera la Session del thread actual de ThreadLocal y lo cierra. Chequea si la transaci�n todav�a existe y la entrega si es as�.

En la funci�n init(), se configurar� la SessionFactory cuando el SessionManager se necesite por primera vez.

Nuestras acciones pueden invocar el m�todo est�tico getSession() del SessionManager para obtener una Session - si se invoca varias veces durante un thread, se utilizar� siempre la misma sesi�n.

Nuestras acciones pueden invocar al m�todo rollback() para deshacer una transaci�n.

tenemos que modificar el classpath en nuestro objetivo compile para hacer que esto fucione:

.�en build.xml:


    <target name="compile" depends="copy-resources">
      <javac srcdir="${sourcedir}"
             destdir="${targetdir}"
             debug="on"
      >
      
          <classpath>
              <fileset dir="${librarydir}">
                  <include name="*.jar"/>
              </fileset>
              <fileset dir="${tomcatdir}/common/lib">
                  <include name="*.jar"/>
              </fileset>
          </classpath>        
      </javac>
    </target>

Finalmente, tenemos que configurar el filtro en el fichero web.xml:

.�config/web.xml:


<!DOCTYPE web-app PUBLIC 
    "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" 
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-appara>
   <display-name>Eventmanager</display-name>

    <filter>
        <filter-name>sessionmanager</filter-name>
        <filter-class>de.gloegl.road2hibernate.util.SessionManager</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>sessionmanager</filter-name>
        <servlet-name>action</servlet-name>
    </filter-mapping>

    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>webwork.dispatcher.ServletDispatcher</servlet-class>
        <load-on-startupara>1</load-on-startupara>
    </servlet>

    <servlet>
        <servlet-name>velocity</servlet-name>
        <servlet-class>webwork.view.velocity.WebWorkVelocityServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>velocity</servlet-name>
        <url-pattern>*.vm</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-appara>

.�Acceder a Hibernate desde la Acci�n

Ya hemos configurado nuestro marco de trabajo, finalmente podemos empezar a utilizar Hibernate para cargar los datos en nuestra acci�n:

.�src/de/gloegl/road2hibernate/actions/EventList.java:


package de.gloegl.road2hibernate.actions;

import java.util.List;
import webwork.action.ActionSupport;
import net.sf.hibernate.Session;
import net.sf.hibernate.HibernateException;
import de.gloegl.road2hibernate.util.SessionManager;

public class EventList extends ActionSupport {
    private List events;
    
    public List getEvents() {
        return events;
    }
    
    public String doExecute() {
        try {
            Session s = SessionManager.getSession();
            
            events = s.find("from Event");
            
            return SUCCESS;
        } catch (HibernateException e) {
            e.printStackTrace();
            return ERROR;
        }
    }
}

Simplemente recuperamos la sesi�n desde nuestro SessionManager, y cargamos la lista de Events utilizando session.find(). Luego lo asignamos a un atributo de nuestra acci�n. Ahora podemos utilizar este atributo en nuestra vista:

.�views/eventlist.vm:


<html>
    <head>
        <title>Event List</title>
    </head>
    <body>
        <h1>Events</h1>           
        <ul>
        #foreach( $event in $events )
            <li>$event.title</li>
        #end
        </ul>
    </body>
</html>

Utilizando el lenguaje de plantillas de velocity, simplemente iteramos sobre los eventos, e imprimimos sus t�tulos. Ahora que nuestra infraestructura est� situada, puedes ver qu� f�cil es crear acciones y vistas. Si ahora llamas a http://localhost:8080/eventmanager/eventlist.action, no ver�s nada - porque no tenemos Events en la base de datos. Por eso crearemos otra acci�n para crear eventos:

Pero primero crearemos un fichero index.html, donde enlazar nuestras acciones:

.�static-web/index.html:


<html>
    <head>
        <title>Hibernate Event Manager</title>
    </head>
    <body>
    
        <h2>Hibernate Event Manager</h2>
        <ul>
            <li>
                <a href="eventlist.action">Event Listing</a>
            </li>
            <li>
                <a href="newevent.enter.action">New Event</a>
            </li>
        </ul>
    
    </body>
</html>

Luego, crearemos la acci�n NewEvent:

.�src/de/gloegl/road2hibernate/actions/NewEvent.java:


package de.gloegl.road2hibernate.actions;

import java.util.List;
import webwork.action.ActionSupport;
import webwork.action.CommandDriven;
import net.sf.hibernate.Session;
import net.sf.hibernate.HibernateException;
import de.gloegl.road2hibernate.util.SessionManager;
import de.gloegl.road2hibernate.data.Event;

public class NewEvent extends ActionSupport implements CommandDriven {
    private String title;
    
    public String getTitle() {
        return title;
    }
    
    public void setTitle(String title) {
        this.title = title;
    }
        
    public String doExecute() {
        try {
            Session s = SessionManager.getSession();
            
            Event event = new Event();
            event.setTitle(title);
            
            s.save(event);
            s.flush();
            
            return SUCCESS;
        } catch (HibernateException e) {
            e.printStackTrace();
            return ERROR;
        }
    }
    
    public String doEnter() {
        return INPUT;
    }
}

Como puedes ver, esta acci�n tiene dos m�todos doXXX(). La raz�n es porque esta vez usaremos el soporte de acciones dirigias por comandos que tiene Webwork -- esto es por lo que esta acci�n implementa el interface CommandDriven. Echemos un vistazo al fichero views.properties que nos aclarar� esto:

.�src/views.properties:


eventlist.action=EventList
eventlist.success=/WEB-INF/views/eventlist.vm

newevent.action=NewEvent
newevent.input=/WEB-INF/views/newEventForm.vm
newevent.success=index.html
newevent.enter.action=NewEvent!enter

Tenemos dos acciones: newevent usar� la clase NewEvent con el comando por defecto que llamar� a doExecute(). newevent.enter usara la acci�n NewEvent con el comando enter, que llamar� al m�todo doEnter(). B�sicamente newevent.enter nos traer� la vista newEventForm, y newevent nos llevar� a index.html despu�s de grabar el nuevo objeto Event.

Finalmente necesitamos codificar el fichero vista que hemos especificado.

.�views/newEventForm.vm:


<html>
    <head>
        <title>Event List</title>
    </head>
    <body>
        <h1>New Event</h1>           
        <form method="POST" action="newevent.action">
            <table>
                <tr>
                    <td>Title:</td>
                    <td><input type="text" name="title" value="$!title"/></td>
                </tr>
                <tr>
                    <td colspan="2"><input type="submit"/></td>
                </tr>
            </table>
        </form>
    </body>
</html>

Si ahora compilas y recargas la aplicaci�n y vas a la direcci�n http://localhost:8080/eventmanager, te deber�a aparecer la p�gina index.html desde donde podr�s crear nuevos eventos y listarlos.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP