Hibernate.orgCommunity Documentation
Este capitulo, pensado para nuevos usuarios, ofrece una introduccion paso a paso
a Hibernate, comenzando con una sencilla aplicacion que usa una base de datos
en memoria. El tutorial esta basado en un tutorial anterior desarrollado por
Michal Gloegl. Todo el codigo fuente puedes encontrarlo en el directorio
project/tutorials/web de tu distribucion de Hibernate.
Este tutorial asume que el usuario tiene conocimientos tanto de Java como de SQL. Si dispones de un conocimiento limitado de Java y SQL es aconsejable que comiences con una buena introduccion a estas tecnologias como paso previo a intentar aprender Hibernate.
La distribucion de Hibernate contiene otra aplicacion de ejemplo bajo el
directorio de proyectos tutorial/eg.
Aunque puedes usar cualquier base de datos con la que te sientas comodo, nosotros vamos a usar HSQLDB (una base de datos en memoria escrita en Java) para evitar describir la instralacion y puesta en marcha de un servidor de bases de datos en concreto.
Lo primero que necesitamos hacer
es poner en marcha el entorno de desarrollo. Usaremos el
"esquema estandar" defendido por un monton de herramientas de construccion como Maven.
Maven, en particular, dispone de un buen recurso web describiendo este esquema.
Como este tutorial es una aplicacion web, crearemos y haremos uso de los directorios
src/main/java, src/main/resources y src/main/webapp.
Usaremos Maven en este tutorial, tomando ventaja de sus capacidades de gestion de dependencias asi como la habilidad de muchos IDE's de configurar automaticamente un proyecto basandose en el descriptor Maven.
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.hibernate.tutorials</groupId>
<artifactId>hibernate-tutorial</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>First Hibernate Tutorial</name>
<build>
<!-- we dont want the version to be part of the generated war file name -->
<finalName>${artifactId}</finalName>
</build>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<!-- Because this is a web app, we also have a dependency on the servlet api. -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
<!-- Hibernate uses slf4j for logging, for our purposes here use the simple backend -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<!-- Hibernate gives you a choice of bytecode providers between cglib and javassist -->
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
</dependency>
</dependencies>
</project>
Usar Maven no es obligatorio. Si deseas usar cualquier otra herramienta
para construir este tutorial (como Ant) el esquema seguira
siendo el mismo. El unico cambio es que necesitaras manipular manualmente
todas las dependencias requeridas. Si utilizas una herramienta como Ivy ofreciendo mantenimiento transitivo
de dependencias, seguiras necesitando las dependencias mencionadas debajo.
De cualquier otra manera necesitaras tener todas
las dependencias, tanto explicitas como transitivas, y añadirlas al classpath
del proyecto. Si estas trabajando con el paquete de distribucion de Hibernate,
estas dependencias son hibernate3.jar, todos
los archivos del directorio lib/required
asi como todos los archivos de los directorios lib/bytecode/cglib
o lib/bytecode/javassist;
de manera adicional, necesitaras tanto el archivo jar del servlet API como una de las implementaciones
del API para logging slf4j.
Guarda este archivo como pom.xml en el directorio principal del proyecto.
package org.hibernate.tutorial.domain; import java.util.Date; public class Event { private Long id; private String title; private Date date; public Event() {} public Long getId() { return id; } private void setId(Long id) { this.id = id; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
Guarda este archivo en el directorio src/main/java/org/hibernate/tutorial/domain.
La estructura basica de un archivo de mapeo se parece a esto:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="org.hibernate.tutorial.domain"> [...] </hibernate-mapping>
<hibernate-mapping package="org.hibernate.tutorial.domain"> <class name="Event" table="EVENTS"> </class> </hibernate-mapping>
<hibernate-mapping package="org.hibernate.tutorial.domain"> <class name="Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <generator class="native"/> </id> </class> </hibernate-mapping>
Finalmente, necesitamos indicar a Hibernate como mapear las propiedades restantes de la entidad. Por defecto, ninguna propiedad de la entidad es considerada persistente:
<hibernate-mapping package="org.hibernate.tutorial.domain">
<class name="Event" table="EVENTS">
<id name="id" column="EVENT_ID">
<generator class="native"/>
</id>
<property name="date" type="timestamp" column="EVENT_DATE"/>
<property name="title"/>
</class>
</hibernate-mapping>
De manera similar al elemento id, el atributo name del elemento property
informa a Hibernate que metodo setter usar. En este caso,
Hibernate buscara los metodos getDate(),
setDate(), getTitle()
y setTitle().
¿Por que el mapeo de la propiedad date
incluye el atributo column pero no lo incluye el mapeo de title? Sin el atributo column, Hibernate usa por defecto el nombre de
la propiedad como nombre para la columna. Esto funciona para title,
sin embargo date es una palabra reservada en muchas bases de datos
por lo que necesitas mapear la propiedad date con un nombre diferente.
El mapeo de title tambien carece del atributo type. Los tipos de datos declarados y usados en
los archivos de mapeo no son tipos de datos Java; tampoco son tipos de base de datos
SQL. Son tipos llamados tipos de datos Hibernate
, conversores que pueden trasladar desde tipos de datos Java a SQL y viceversa.
De nuevo, Hibernate intentara determinar la conversion correcta y el tipo de datos
por el mismo si el atributo type
no se encuentra presente. En algunos casos esta deteccion automatica, que es
determinada mediante reflexion, puede no resultar en el tipo que esperabas o necesitabas.
Este es el caso con la propiedad date.
Hibernate no puede conocer si la propiedad, que es del tipo java.util.Date, debe ser mapeada a una columna SQL del tipo date, timestamp, o
time. La informacion completa de fecha y hora
es preservada mapeando la propiedad con un convertidor timestamp.
Hibernate determina el tipo de datos a usar mediante reflexion en el momento en que los archivos de mapeo son procesados. Esto puede tomar tiempo y recursos, por lo que si el rendimiento en el arranque es importante deberias considerar definir explicitamente todos los tipos a usar.
Guarda este archivo como src/main/resources/org/hibernate/tutorial/domain/Event.hbm.xml.
Hibernate conectara
con la base de datos en nombre de tu aplicacion, por lo que necesita
conocer como obtener conexiones. Para este tutorial usaremos un pool
de conexiones (de manera opuesta a usar javax.sql.DataSource).
HIbernate viene con soporte para dos pool de conexiones JDBC de terceros:
c3p0 y proxool.Sin embargo, usaremos
el pool de conexiones integrado en Hibernate.
El pool de conexiones integrado en Hibernate no esta pensado de ninguna manera para su uso en produccion, ya que carece de diversas caracteristicas disponibles en cualquier pool de conexiones decente.
Para la configuracion de Hibernate, podemos usar un sencillo archivo hibernate.properties, un mas sofisticado archivo
hibernate.cfg.xml, o incluso una configuracion
programatica completa. Muchos usuarios prefieren el archivo de configuracion XML:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:hsql://localhost</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">update</property>
<mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Observa que este archivo de configuracion define un DTD diferente
Has configurado un SessionFactory.
SessionFactory es una factoria global reponsable de una base de datos
en particular. Si dispones de varias bases de datos, deberias usar varias
configuraciones <session-factory>
en varios archivos de configuracion para simplificar el arranque.
Los cuatro primeros elementos property
contienen la configuracion necesaria para la conexion JDBC. El elemento
contiene la configuracion necesaria para la conexion JDBC. El elemento
property que hace referencia a 'dialect' especifica
que variante de SQL tiene que generar Hibernate.
En muchos casos, Hibernate es capaz de determinar correctamente el dialecto que debe usar.
El mantenimiento automatico de sesiones para contextos de persistencia
de Hibernate es particularmente util en este contexto. La opcion
hbm2ddl.auto
activa la generacion automatica de esquemas en la base de datos. Esto puede
ser desactivado eliminando la citada opcion de de configuracion, o redireccionado
a un archivo con la ayuda de la tarea de Ant SchemaExport. Finalmente, añade el/los archivo(s) de mapeo
para clases persistentes a la configuracion.
Guarda este archivo como hibernate.cfg.xml
dentro del directorio src/main/resources.
Ahora construiremos el tutorial
con Maven. Por supuesto, necesitaras tener Maven instalado; esta disponible
desde la pagina de descargas de Maven.
Maven leera el archivo /pom.xml que hemos
creado antes y sabra como realizar algunas tareas basicas del proyecto. Primero,
ejecutemos la opcion compile para asegurarnos que
podemos compilar todo lo hecho hasta ahora:
[hibernateTutorial]$ mvn compile [INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Building First Hibernate Tutorial [INFO] task-segment: [compile] [INFO] ------------------------------------------------------------------------ [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Compiling 1 source file to /home/steve/projects/sandbox/hibernateTutorial/target/classes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 2 seconds [INFO] Finished at: Tue Jun 09 12:25:25 CDT 2009 [INFO] Final Memory: 5M/547M [INFO] ------------------------------------------------------------------------
package org.hibernate.tutorial.util; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil { private static final SessionFactory sessionFactory = buildSessionFactory(); private static SessionFactory buildSessionFactory() { try { // Create the SessionFactory from hibernate.cfg.xml return new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { // Make sure you log the exception, as it might be swallowed System.err.println("Initial SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return sessionFactory; } }
Guarda este codigo como src/main/java/org/hibernate/tutorial/util/HibernateUtil.java
package org.hibernate.tutorial; import org.hibernate.Session; import java.util.*; import org.hibernate.tutorial.domain.Event; import org.hibernate.tutorial.util.HibernateUtil; public class EventManager { public static void main(String[] args) { EventManager mgr = new EventManager(); if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date()); } HibernateUtil.getSessionFactory().close(); } private void createAndStoreEvent(String title, Date theDate) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate); session.save(theEvent); session.getTransaction().commit(); } }
El ejemplo anterior tambien ha evitado cualquier manejo de errores asi como cancelaciones de la transaccion en caso de que los primeros hubiera ocurrido.
Para ejecutar el codigo anterior, tenemos que hacer uso de la opcion 'exec' de Maven para
llamar a nuestra clase con los ajustes de classpath necesarios: mvn
exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager"
-Dexec.args="store"
Tal vez necesites realizar primero una llamada a mvn compile.
Deberias ver a Hibernate arrancando y, dependiendo de tu configuracion, un monton de salida de log. Cerca del final, se mostrara la siguiente linea:
[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)
Esta es la secuencia SQL INSERT ejecutada por
Hibernate.
Para listar eventos, añadimos el siguiente codigo al metodo 'main':
if (args[0].equals("store")) {
mgr.createAndStoreEvent("My Event", new Date());
}
else if (args[0].equals("list")) {
List events = mgr.listEvents();
for (int i = 0; i < events.size(); i++) {
Event theEvent = (Event) events.get(i);
System.out.println(
"Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()
);
}
}
Asi mismo, un nuevo metodo listEvents() es tambien añadido:
private List listEvents() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
List result = session.createQuery("from Event").list();
session.getTransaction().commit();
return result;
}
Aqui estamos usando una solicitud Hibernate Query Language (HQL) para leer
todos los objetos Event desde la base de datos.
Hibernate generara las sentencias SQL adecuadas, las enviara a la base de datos y
creara objetos Event con los datos obtenidos.
HQL te permite crear consultas mucho mas complejas.
Ahora podemos probar nuestra nueva funcionalidad, de nuevo usando la opcion 'exec'
de Maven: mvn exec:java
-Dexec.mainClass="org.hibernate.tutorial.EventManager"
-Dexec.args="list"
La primera version de la
clase Person seria algo asi:
package org.hibernate.tutorial.domain; public class Person { private Long id; private int age; private String firstname; private String lastname; public Person() {} // Accessor methods for all properties, private setter for 'id' }
<hibernate-mapping package="org.hibernate.tutorial.domain"> <class name="Person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="native"/> </id> <property name="age"/> <property name="firstname"/> <property name="lastname"/> </class> </hibernate-mapping>
Finalmente, añade el mapeo a la configuracion de Hibernate:
<mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/> <mapping resource="org/hibernate/tutorial/domain/Person.hbm.xml"/>
public class Person { private Set events = new HashSet(); public Set getEvents() { return events; } public void setEvents(Set events) { this.events = events; } }
<class name="Person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="native"/> </id> <property name="age"/> <property name="firstname"/> <property name="lastname"/> <set name="events" table="PERSON_EVENT"> <key column="PERSON_ID"/> <many-to-many column="EVENT_ID" class="Event"/> </set> </class>
El esquema de base de datos para este mapeo es el siguiente:
_____________ __________________ | | | | _____________ | EVENTS | | PERSON_EVENT | | | |_____________| |__________________| | PERSON | | | | | |_____________| | *EVENT_ID |<---->| *EVENT_ID | | | | EVENT_DATE | | *PERSON_ID |<---->| *PERSON_ID | | TITLE | |__________________| | AGE | |_____________| | FIRSTNAME | | LASTNAME | |_____________|
private void addPersonToEvent(Long personId, Long eventId) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Person aPerson = (Person) session.load(Person.class, personId); Event anEvent = (Event) session.load(Event.class, eventId); aPerson.getEvents().add(anEvent); session.getTransaction().commit(); }
private void addPersonToEvent(Long personId, Long eventId) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Person aPerson = (Person) session .createQuery("select p from Person p left join fetch p.events where p.id = :pid") .setParameter("pid", personId) .uniqueResult(); // Eager fetch the collection so we can use it detached Event anEvent = (Event) session.load(Event.class, eventId); session.getTransaction().commit(); // End of first unit of work aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached // Begin second unit of work Session session2 = HibernateUtil.getSessionFactory().getCurrentSession(); session2.beginTransaction(); session2.update(aPerson); // Reattachment of aPerson session2.getTransaction().commit(); }
else if (args[0].equals("addpersontoevent")) { Long eventId = mgr.createAndStoreEvent("My Event", new Date()); Long personId = mgr.createAndStorePerson("Foo", "Bar"); mgr.addPersonToEvent(personId, eventId); System.out.println("Added person " + personId + " to event " + eventId); }
private Set emailAddresses = new HashSet(); public Set getEmailAddresses() { return emailAddresses; } public void setEmailAddresses(Set emailAddresses) { this.emailAddresses = emailAddresses; }
El mapeo
para este Set es el siguiente:
<set name="emailAddresses" table="PERSON_EMAIL_ADDR"> <key column="PERSON_ID"/> <element type="string" column="EMAIL_ADDR"/> </set>
Aqui esta el esquema actualizado:
_____________ __________________ | | | | _____________ ___________________ | EVENTS | | PERSON_EVENT | | | | | |_____________| |__________________| | PERSON | | PERSON_EMAIL_ADDR | | | | | |_____________| |___________________| | *EVENT_ID |<--->| *EVENT_ID | | | | | | EVENT_DATE | | *PERSON_ID |<--->| *PERSON_ID |<--->| *PERSON_ID | | TITLE | |__________________| | AGE | | *EMAIL_ADDR | |_____________| | FIRSTNAME | |___________________| | LASTNAME | |_____________|
private void addEmailToPerson(Long personId, String emailAddress) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Person aPerson = (Person) session.load(Person.class, personId); // adding to the emailAddress collection might trigger a lazy load of the collection aPerson.getEmailAddresses().add(emailAddress); session.getTransaction().commit(); }
Primero, añade una
coleccion de participantes en la clase Event:
private Set participants = new HashSet(); public Set getParticipants() { return participants; } public void setParticipants(Set participants) { this.participants = participants; }
Ahora mapea este
lado de la asociacion en Event.hbm.xml.
<set name="participants" table="PERSON_EVENT" inverse="true"> <key column="EVENT_ID"/> <many-to-many column="PERSON_ID" class="events.Person"/> </set>
package org.hibernate.tutorial.web; // Imports public class EventManagerServlet extends HttpServlet { protected void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { SimpleDateFormat dateFormatter = new SimpleDateFormat( "dd.MM.yyyy" ); try { // Begin unit of work HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction(); // Process request and render page... // End unit of work HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit(); } catch (Exception ex) { HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback(); if ( ServletException.class.isInstance( ex ) ) { throw ( ServletException ) ex; } else { throw new ServletException( ex ); } } } }
Guarda este servlet como src/main/java/org/hibernate/tutorial/web/EventManagerServlet.java
Ahora puedes implementar el procesamiento de la solicitud y formar la pagina de respuesta.
// Write HTML header PrintWriter out = response.getWriter(); out.println("<html><head><title>Event Manager</title></head><body>"); // Handle actions if ( "store".equals(request.getParameter("action")) ) { String eventTitle = request.getParameter("eventTitle"); String eventDate = request.getParameter("eventDate"); if ( "".equals(eventTitle) || "".equals(eventDate) ) { out.println("<b><i>Please enter event title and date.</i></b>"); } else { createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate)); out.println("<b><i>Added event.</i></b>"); } } // Print page printEventForm(out); listEvents(out, dateFormatter); // Write HTML footer out.println("</body></html>"); out.flush(); out.close();
private void printEventForm(PrintWriter out) { out.println("<h2>Add new event:</h2>"); out.println("<form>"); out.println("Title: <input name='eventTitle' length='50'/><br/>"); out.println("Date (e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>"); out.println("<input type='submit' name='action' value='store'/>"); out.println("</form>"); }
private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) { List result = HibernateUtil.getSessionFactory() .getCurrentSession().createCriteria(Event.class).list(); if (result.size() > 0) { out.println("<h2>Events in database:</h2>"); out.println("<table border='1'>"); out.println("<tr>"); out.println("<th>Event title</th>"); out.println("<th>Event date</th>"); out.println("</tr>"); Iterator it = result.iterator(); while (it.hasNext()) { Event event = (Event) it.next(); out.println("<tr>"); out.println("<td>" + event.getTitle() + "</td>"); out.println("<td>" + dateFormatter.format(event.getDate()) + "</td>"); out.println("</tr>"); } out.println("</table>"); } }
protected void createAndStoreEvent(String title, Date theDate) { Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate); HibernateUtil.getSessionFactory() .getCurrentSession().save(theEvent); }de separar en capas tu codigo y acceder a
SessionFactory
de la manera que quieras. Normalmente usarias un diseño mas sofisticado
y moverias el codigo de acceso al interior de un objeto de acceso a datos
(patron de diseño DAO). En el Wiki de Hibernate puedes encontrar mas ejemplos.
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <servlet> <servlet-name>Event Manager</servlet-name> <servlet-class>org.hibernate.tutorial.web.EventManagerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Event Manager</servlet-name> <url-pattern>/eventmanager</url-pattern> </servlet-mapping> </web-app>
Si no tienes Tomcat instalado, descargalo desde http://tomcat.apache.org/ y sigue las instrucciones de instalacion que encontraras en la misma web. Nuestra aplicacion no requiere cambios en la configuracion estandar de Tomcat.
Con la aplicacion desplegada y Tomcat ejecutandose, accede a la aplicacion mediante la direccion http://localhost:8080/hibernate-tutorial/eventmanager.
Asegurate de mirar el log de Tomcat para ver que Hibernate se inicia cuando la primera
solicitud alcanza tu servlet (el inicializador estatico en HibernateUtil es llamado) y de obtener una salida
detallada si cualquier excepcion ocurriera.
Copyright © 2004 Red Hat Middleware, LLC.
Traducido por David Marco