Programación en castellano
Inicio > Tutoriales > Lenguajes orientados a objeto > J2EE > Persistencia de Objetos Java: El Camino hacia Hibernate
-Tutoriales

Persistencia de Objetos Java: El Camino hacia Hibernate


Tomcat y WebWork

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.

 
Patrocinados
 

Copyright © 1999-2007 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad
Mantenida por: Claudio y Dani.

Hospedaje web y servidores dedicados linux por Ferca Network

red internet: jugar gratis | amor | navidad 2009 | registro de dominios | servidores dedicados
más internet: comprar | gratis | posicionamiento en buscadores | decoración libre | gifs animados