miércoles, 13 de mayo de 2009

HIbernate - Parte 2: Persistiendo Objetos Simples usando Anotaciones (metadatos)

En el tutorial anterior vimos como crear una pequeña aplicación con Hibernate usando mapeos en archivos XML. Aunque esto es bastante útil (en algunas situaciones) puede ser bastante engorroso, especialmente en aplicaciones muy grandes.

Afortunadamente existe otra forma de realizar estos mapeos, y es usando anotaciones o metadatos, los cuales se agregan directamente al código fuente y, por lo tanto, es más fácil estar conscientes de estos datos de mapeos al estar editando el código fuente. Además usan muchos valores por default, ya no será necesario que escribamos toda la información para los mapeos.

En esta ocasión mostraré como realizar el mismo ejemplo que en el tutorial anterior, pero haciendo uso de anotaciones en lugar de archivos de mapeo XML.

Para este ejemplo usaremos MySQL 5.1 como base de datos. Si no la tienen pueden descargarla desde aquí. También deben bajar el conector para Java versión 5.1.7 desde aquí. Además usaremos la biblioteca de Hibernate que creamos también en el tutorial anterior.

Hibernate proporciona un API especial para trabajar usando anotaciones llamada "Hibernate Annotations" que puede ser descargada desde aquí. Actualmente la última versión es la 3.4.0 GA y es la que usaremos para este tutorial.

Una vez descargado el archivo y descomprimido, veremos que dentro del directorio de "hibernate-annotations" hay un archivo llamado "hibernate-annotations.jar". Este jar contiene las clases principales de Hibernate annotations. Dentro del directorio "lib" encontrarán algunos jars de soporte.

Crearemos una biblioteca que contendrá estos archivos para que sea fácil agregarlos cada vez que queramos trabajar con anotaciones de Hibernate.

Abrimos el NetBeans y vamos al menú "Tools -> Libraries":




En la ventana que se abre seleccionamos la opción "New Library..." con lo que se abre otra ventana. En esta ventana le damos a la biblioteca el nombre "HibernateAnotaciones" (sin espacios), de tipo "Class Libraries":



Presionamos el botón "OK" y ya tendremos creada nuestra biblioteca, ahora hay que agregar los jars que la conformarán. Presionamos el botón "Add JAR/Folder" y seleccionamos, del directorio de "hibernate-annotations" los siguientes archivos:

  • hibernate-annotations.jar

y del directorio lib:

  • ejb3-persistence.jar
  • hibernate-commons-annotations.jar

Nuestra biblioteca debe verse así:



Ahora cada vez que queramos usar hibernate con anotaciones deberemos agregar la biblioteca "Hibernate" (que creamos en el tutorial anterior) y la nueva biblioteca "HibernateAnotaciones".

Ahora creamos un nuevo proyecto desde el menú "File -> New Project... -> Java -> Java Application". Llamamos a la aplicación "HibernateAnotaciones" y nos aseguramos que las opciones "Create Main Class" y "Set as Main Project" estén seleccionadas. Presionamos el botón "Finish" y veremos la clase "Main" en nuestro editor.

Agregamos las bibliotecas de Hibernate a nuestro proyecto. Hacemos clic derecho sobre el nodo "Libraries" del proyecto y agregamos las dos bibliotecas de Hibernate que creamos ("Hibernate" y "HibernateAnotaciones"), seleccionando la opción "Add Library..." del menú contextual que se abre. Aprovechamos para agregar el driver de la base de datos, el archivo "mysql-connector-java-5.1.7-bin.jar" usando la opción "Add JAR/Folder..." de ese mismo menú contextual. Al final el nodo "Libraries" debe verse así:



También es buen momento para crear la base de datos "pruebahibernate" si no lo han hecho. Igual que en el tutorial anterior no se preocupen por crear tablas, ya que será el propio Hibernate quien lo haga.

Creamos una clase simple que servirá como Entidad. De hecho reutilizaremos la clase "Contacto" del tutorial anterior, cuyo código quedó de esta forma:

public class Contacto implements Serializable
{
    private long id;
    private String nombre;
    private String email;
    private String telefono;

    public Contacto()
    {
    }

    public Contacto(String nombre, String email, String telefono)
    {
        this.nombre = nombre;
        this.email = email;
        this.telefono = telefono;
    }

    public String getEmail()
    {
        return email;
    }

    public void setEmail(String email)
    {
        this.email = email;
    }

    public long getId()
    {
        return id;
    }

    private void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }

    public String getTelefono()
    {
        return telefono;
    }

    public void setTelefono(String telefono)
    {
        this.telefono = telefono;
    }
}


En el caso anterior habíamos dejado la clase así, y el archivo de mapeo se encargaba del resto. En este caso agregaremos en la clase las anotaciones para realizar el mapeo.

Antes de comenzar debo decir que en realidad Hibernate no trabaja solo cuando usamos anotaciones. Si recuerdan, cuando creamos la biblioteca de "HibernateAnotaciones" entre los archivos que agregamos para conformarla se encontraba "ejb3-persistence.jar". Este archivo contiene las clases de un Framework llamado "JPA" o "Java Persistence API", la cual es el API "oficial" de persistencia para aplicaciones Java. Trabaja utilizando lo que se conoce como un "proveedor de persistencia" que es quien se encarga de hacer el verdadero trabajo con la base de datos. En este caso Hibernate será el proveedor de persistencia. Las anotaciones que usaremos si son de JPA. Hibernate también proporciona algunas anotaciones, pero son por si queremos agregar funcionalidad extra, lo cual escapa al objetivo de este tutorial.

No usaremos al 100% JPA ya que no realizaremos la configuración como debe ser (en un archivo llamado "persistence.xml") sino que seguiremos usando Hibernate para configurar todo.

Ahora si, pasemos a anotar nuestra clase. La anotación más importante es "javax.persistence.Entity", la cual indica que la clase es una "Entidad", por lo que la anotación se coloca a nivel de clase.

@Entity
public class Contacto implements Serializable
{
}


Cuando Hibernate genere la tabla para esta entidad lo hará en base al nombre de la misma. En este caso creará una tabla llamada "contacto". Pero, en lo personal, prefiero que los nombres de las tablas estén el plural. Así que usaré la anotación "javax.persistence.Table" para indicar cuál será el nombre de la tabla generada (o si ya tenemos una base de datos creada, cuál es el nombre de la tabla en la que se almacenarán estas entidades):

@Entity 
@Table(name="contactos")
public class Contacto implements Serializable 
{ 
}


Después debemos indicar cuál atributo de la clase será el identificador. Esto lo hacemos con la anotación "javax.persistence.Id". Este atributo podemos colocarlo en dos lugares: en el getter del atributo identificador, o directamente en el atributo.

Cuando colocamos la anotación en el atributo decimos que se realiza un acceso por "atributo" (field access), o sea que Hibernate lee y establece los valores directamente de los atributos. Cuando la colocamos en el getter decimos que se realiza un acceso por "propiedad" (property access), o sea que Hibernate accesa a los valores mediante los setters y getters. No pueden mezclarse los tipos de acceso, así que elijan con cuidado. Pueden encontrar un poco más de información acerca de esto aquí (es un tutorial de JPA)

En este caso colocaré la anotación en el atributo:

@Id
private long id;


Podemos personalizar la forma en la que se generará el valor del identificador usando la anotación "javax.persistence.GeneratedValue" e indicando la "estrategia" que se usará para generarlo. Existen solo 4 estrategias en JPA (si usamos las anotaciones de Hibernate podemos usar todos los generadores de Hibernate):

  • AUTO
  • IDENTITY
  • SEQUENCE
  • TABLE

Las más usadas son "AUTO" e "IDENTITY". La primera usa la estrategia por default de la base de datos, mientras que la segunda genera los identificadores en orden (1, 2, 3, etc.). Pueden encontrar más información aquí.

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;


Esto es todo lo que es necesario hacerle a la clase, el resto de las propiedades que se persistirán serán obtenidas mediante reflexión, la explicación es la misma que en el tutorial anterior. Todos los atributos que no estén marcados como "transient" o con la anotación "javax.persistence.Transient" serán persistidas.

Si queremos personalizar la columna de la tabla que se generará de un atributo, podemos hacerlo con la anotación "javax.persistence.Column". Por ejemplo, yo no quiero que la columna generada del atributo "email" sea "email", sino que sea "e_mail", por lo que agrego la anotación de la siguiente forma:

@Column(name="e_mail")
private String email;


Al final la clase "Contacto" queda de la siguiente forma (omitiendo los imports):

@Entity
@Table(name="contactos")
public class Contacto implements Serializable
{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    private String nombre;
    
    @Column(name="e_mail")
    private String email;
    private String telefono;

    public Contacto()
    {
    }

    public Contacto(String nombre, String email, String telefono)
    {
        this.nombre = nombre;
        this.email = email;
        this.telefono = telefono;
    }

    public String getEmail()
    {
        return email;
    }

    public void setEmail(String email)
    {
        this.email = email;
    }

    public long getId()
    {
        return id;
    }

    private void setId(long id)
    {
        this.id = id;
    }

    public String getNombre()
    {
        return nombre;
    }

    public void setNombre(String nombre)
    {
        this.nombre = nombre;
    }

    public String getTelefono()
    {
        return telefono;
    }

    public void setTelefono(String telefono)
    {
        this.telefono = telefono;
    }
}


Y esto nos ahorrará el tener que crear el archivo de mapeo para esta clase ^-^. Ahora configuraremos Hibernate. Como expliqué anteriormente: La configuración de Hibernate puede hacerse en tres lugares:

  • Un archivo de propiedades llamado "hibernate.properties".
  • Un archivo XML llamado "hibernate.cfg.xml".
  • En código dentro de la misma aplicación.

Yo usaré el archivo "hibernate.cfg.xml". Así que crearemos este archivo en nuestro proyecto. Hacemos clic derecho en el nodo "Source Packages" del proyecto. En el menú contextual que se abre seleccionamos "New -> XML Document...":



Nombramos al archivo "hibernate.cfg", el IDE se encargará de colocarle la extensión ".xml". Ubicamos el archivo en el directorio "src":



Con esto lograremos que el archivo quede en la raíz del classpath de la aplicación, también conocido como el paquete default. Presionamos el botón "Next >" y en la pantalla siguiente indicamos que queremos crear un documento XML bien formado (como en el caso anterior). Presionamos el botón "Finish" para generar el archivo. Si nos fijamos en el archivo generado, este debe encontrarse en un paquete llamado "<default package>":



Borramos todo el contenido del archivo generado y colocamos las siguientes líneas:

<?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>

</hibernate-configuration>


El DTD nos ayudará para que el IDE autocomplete las etiquetas.

Como podemos ver, el elemento raíz del archivo de configuración es "<hibernate-configuration>" y, por lo tanto, todos los elementos los colocaremos entre estas dos etiquetas.

Lo primero que debemos hacer es configurar un "session-factory", que básicamente es lo que le dice a Hibernate cómo conectarse y manejar la conexión a la base de datos. Podemos tener más de un "session-factory" en el archivo de configuración (por si quisiéramos conectarnos a más de una base de datos), pero por lo regular solo ponemos uno:

<hibernate-configuration>
    <session-factory>

    </session-factory>
</hibernate-configuration>


Lo siguiente es configurar la conexión a nuestra base de datos. En este archivo se configuran los parámetros básicos y típicos para una conexión (la URL, nombre de usuario, contraseña, driver, etc.). Cada uno de estos parámetros se configura dentro de una etiqueta "<property>" (al igual que casi todos los elementos del archivo de configuración). Como dije antes, usaré una base de datos MySQL para este ejemplo, así que mi configuración queda de la siguiente forma:

<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost/pruebahibernate</property>
<property name="connection.username">usuario</property>
<property name="connection.password">password</property>


Después, configuramos el pool de conexiones de Hibernate. En este caso como es un ejemplo muy simple, solo nos interesa tener una conexión en el pool, por lo que colocamos la propiedad "connection.pool_size" con un valor de "1":

<property name="connection.pool_size">1</property>


*Nota: Este pool de conexiones es solo para efectos de pruebas en desarrollo. Cuando nuestra aplicación para a producción se recomienda usar un pool de conexiones distinto, como C3P0.

El siguiente paso es muy importante. Debemos indicar el "dialecto" que usará Hibernate para comunicarse con la base de datos. Este dialecto es la variante de SQL que usa la base de datos para ejecutar queries. Indicamos el dialecto con el "fully qualified class name", o el nombre completo de la clase incluyendo el paquete. En el caso de MySQL 5 usamos "org.hibernate.dialect.MySQL5Dialect". En esta página pueden encontrar una lista más o menos completa de los dialectos soportados por Hibernate, pero siempre es mejor revisar la documentación de la versión que estén usando para estar seguros:

<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>


Otras dos propiedades importantes que podemos configurar son: "show_sql", que indica si queremos que las consultas SQL generadas sean mostradas en el stdout (normalmente la consola), y "hbm2ddl.auto", que indica si queremos que se genere automáticamente el esquema de la base de datos (las tablas). "show_sql" puede tomar valores de "true" o "false", yo lo colocaré en "true" (lo que puede ser bueno mientras estamos en etapas de desarrollo o pruebas, pero querrán cambiar su valor cuando su aplicación pase a producción). Por otro lado "hbm2ddl.auto" puede tomar los valores, según la documentación oficial, de "validate", "update", "create", y "create-drop" (falta "none") (aunque no todos los valores funcionan para todas las bases de datos). Yo los colocaré de la siguiente forma:

<property name="show_sql">true</property> 
<property name="hbm2ddl.auto">create-drop</property>


Con el valor "create-drop" hacemos que cada vez que se ejecute la aplicación Hibernate elimine las tablas de la base de datos y las vuelva a crear, cuando su sistema pase a producción sería bueno que quitaran esta propiedad.

Para terminar la configuración, debemos indicar dónde están las clases entidades. Cuando usamos Hibernate con archivos de mapeo usamos el atributo "resource" del elemento "<mapping>", en el que indicábamos en dónde se encontraba dicho archivo de mapeo. Cuando usamos anotaciones (y por lo tanto no hay archivos de mapeo) debemos indicar las clases anotadas usando su atributo "class", de la siguiente forma:

<mapping class="hibernateanotaciones.Contacto" />


Tal vez encuentren que "<mapping>" tiene otro atributo que es "package". No se confundan, este atributo NO sirve para indicar un paquete en el que se encuentren las clases entidades anotadas y lograr que Hibernate encuentre automáticamente estas clases para ahorrarnos el trabajo de indicar cada clase. De hecho este atributo se usa cuando tenemos paquetes anotados. No estoy seguro de para qué pueden usarse, pero solo no se confundan ^-^!.

El archivo de configuración queda de la siguiente forma:

<?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>         
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>         
        <property name="connection.url">jdbc:mysql://localhost/pruebahibernate</property>     
        <property name="connection.username">usuario</property>       
        <property name="connection.password">password</property>     
        <property name="connection.pool_size">1</property>         
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>     
        <property name="show_sql">true</property>     
        <property name="hbm2ddl.auto">create-drop</property>      
        <mapping class="hibernateanotaciones.Contacto" />     
    </session-factory>
</hibernate-configuration>




Esto es todo lo que debemos poner en el archivo de configuración. Ahora veremos cómo crear el código de acceso a la base de datos. Al igual que en el tutorial anterior crearemos una clase de utilidad llamada "HibernateUtil", que se hará cargo de inicializar y hacer el acceso al "SessionFactory". La clase será casi idéntica, con una excepción.

Creamos una nueva clase llamada "HibernateUtil" y dentro de esta clase declaramos un atributo static de tipo "SessionFactory", así nos aseguraremos de que solo existe una instancia en la aplicación. Además lo declararemos como final para que la referencia no pueda ser cambiada después de que la hayamos asignado.

private static final SessionFactory sessionFactory;


Después usamos un bloque de inicialización estático para inicializar esta variable en el momento en el que la clase sea cargada en la JVM.

La última vez usamos una instancia de la clase "org.hibernate.cfg.Configuration" para cargar la configuración. Cuando usamos anotaciones usamos una instancia de "org.hibernate.cfg.AnnotationConfiguration". Si usamos el método "configure()" que no recibe parámetros entonces Hibernate busca el archivo "hibernate.cfg.xml" que creamos anteriormente. Una vez que tenemos este objeto, entonces podemos inicializar la instancia de "SessionFactory" con su método "buildSessionFactory". Además como este proceso puede lanzar "HibernateException" (que extiende de "RuntimeException") la cachamos y lanzamos como un "ExceptionInInitializarError" (que es lo único que puede lanzarse desde un bloque de inicialización). El bloque de inicialización queda de la siguiente forma:

static
{
    try
    {
    sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    } catch (HibernateException he)
    {
        System.err.println("Ocurrió un error en la inicialización de la SessionFactory: " + he);
        throw new ExceptionInInitializerError(he);
    }
}


Finalmente creamos un método static para recuperar la instancia de la "SessionFactory":

public static SessionFactory getSessionFactory()
{ 
    return sessionFactory;
} 


La clase "HibernateUtil" queda de la siguiente forma:

import org.hibernate.HibernateException;
import org.hibernate.SessionFactory; 
import org.hibernate.cfg.AnnotationConfiguration;

public class HibernateUtil
{  
    private static final SessionFactory sessionFactory;   

    static 
    { 
        try 
        { 
            sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory(); 
        } catch (HibernateException he) 
        { 
           System.err.println("Ocurrió un error en la inicialización de la SessionFactory: " + he); 
            throw new ExceptionInInitializerError(he); 
        } 
    }  

    public static SessionFactory getSessionFactory() 
    { 
        return sessionFactory; 
    } 
}




Ahora escribiremos una clase DAO (nuevamente no seguiremos el patrón al pie de la letra, es solo para mostrar cómo trabajar con Hibernate) que nos permitirá realizar operaciones de base de datos. De hecho es la misma clase "ContactosDAO" del tutorial anterior. Copiaré los pasos y la explicación para que no tengan que regresar a revisarla

Creamos una clase llamada "ContactosDAO" y agregamos dos atributos, uno llamado "sesion" de tipo "org.hibernate.Session", y otro llamado "tx" de tipo "org.hibernate.Transaction".

private Session sesion;
private Transaction tx;


Estos atributos nos servirán para mantener la referencia a la sesión a base de datos, y a la transacción actual, respectivamente. Ahora agregaremos dos métodos de utilidad. El primero nos ayudará a iniciar una sesión y una transacción en la base de datos. Llamaremos a este método "iniciaOperacion", y la implementación es la siguiente:

private void iniciaOperacion() throws HibernateException
{
    sesion = HibernateUtil.getSessionFactory().openSession();
    tx = sesion.beginTransaction();
}


En el método anterior obtenemos una referencia a "SessionFactory" usando nuestra clase de utilidad "HibernateUtil". Una vez que tenemos la "SessionFactory" creamos una conexión a la base de datos e iniciamos una nueva sesión con el método "openSession()". Una vez teniendo la sesión iniciamos una nueva transacción y obtenemos una referencia a ella con "beginTransaction()".

Ahora el segundo método de utilidad (llamado "manejaExcepcion") nos ayudará a manejar las cosas en caso de que ocurra una excepción. Si esto pasa queremos que la transacción que estamos ejecutando se deshaga y se relance la excepción (o podríamos lanzar una propia). Por lo que el método queda así:

private void manejaExcepcion(HibernateException he) throws HibernateException
{
    tx.rollback();
    throw new HibernateException("Ocurrió un error en la capa de acceso a datos", he);
}


Ahora crearemos los métodos que nos permitirán realizar las tareas de persistencia de una entidad "Contacto", conocidas en lenguaje de base de datos como CRUD: guardarla, actualizarla, eliminarla, buscar un entidad "Contacto" y obtener todas los contactos que existen en la base de datos, así que comencemos.

Afortunadamente Hibernate hace que esto sea fácil ya que proporciona métodos para cada una de estas tareas. Primero veamos como guardar un objeto "Contacto". Para esto Hibernate proporciona el método "save" en nuestro objeto de tipo "org.hibernate.Session", que se encarga de generar el "INSERT" apropiado para la entidad que estamos tratando de guardar. El método "guardaContacto" queda de la siguiente forma:

public long guardaContacto(Contacto contacto)
{ 
    long id = 0;  

    try 
    { 
        iniciaOperacion(); 
        id = (Long)sesion.save(contacto); 
        tx.commit(); 
    }catch(HibernateException he) 
    { 
        manejaExcepcion(he);
        throw he; 
    }finally 
    { 
        sesion.close(); 
    }  
    return id; 
}


Regresamos el "id" generado al guardar el "Contacto" solo por si queremos usarlo más adelante en el proceso (como lo haremos nosotros), o si queremos mostrarle al usuario, por alguna razón, el identificador del contacto.

Ahora veremos cómo actualizar un "Contacto". Para eso usamos el método "update", de nuestro objeto "session", en nuestro método "actualizaContacto":

public void actualizaContacto(Contacto contacto) throws HibernateException 
{ 
    try 
    { 
        iniciaOperacion(); 
        sesion.update(contacto); 
        tx.commit(); 
    }catch (HibernateException he) 
    { 
        manejaExcepcion(he); 
        throw he; 
    }finally 
    { 
        sesion.close(); 
    } 
}


Como podemos ver, el método para actualizar es muy similar al método para guardar la entidad. Lo mismo ocurre con el método para eliminarla, "eliminaContacto":

public void eliminaContacto(Contacto contacto) throws HibernateException 
{ 
    try 
    { 
        iniciaOperacion(); 
        sesion.delete(contacto); 
        tx.commit(); 
    } catch (HibernateException he) 
    { 
        manejaExcepcion(he); 
        throw he; 
    }finally 
    { 
        sesion.close(); 
    } 
}


Ahora veremos unos métodos un poco más interesantes.

Cuando queremos buscar una entidad podemos usar varios criterios. La forma más fácil es buscar una entidad particular usando su "id". El objeto "org.hibernate.Session" proporciona dos métodos para esto: "load" y "get". Los dos hacen prácticamente lo mismo. En base al identificador y tipo de la entidad recuperan la entidad indicada, con la diferencia de que "load" lanza una excepción en caso de que la entidad indicada no sea encontrada en la base de datos, mientras que "get" simplemente regresa "null". Pueden usar el que prefieran, en lo personal me gusta más "get", así que lo usaré para el método "obtenContacto":

public Contacto obtenContacto(long idContacto) throws HibernateException
{ 
    Contacto contacto = null;  

    try 
    { 
        iniciaOperacion(); 
        contacto = (Contacto) sesion.get(Contacto.class, idContacto); 
    } finally 
    { 
        sesion.close(); 
    }  
    return contacto; 
}


Finalmente veremos el método "obtenListaContactos" que recupera todos los Contactos que estén guardados en la base de datos. Como en este caso regresaremos una lista de elementos deberemos crear una consulta. Nuevamente usaremos HQL para generar dicha consulta:

public List<Contacto> obtenListaContactos() throws HibernateException 
{ 
    List<Contacto> listaContactos = null;  
    
    try 
    { 
        iniciaOperacion(); 
        listaContactos = sesion.createQuery("from Contacto").list(); 
    }finally 
    { 
        sesion.close(); 
    }  

    return listaContactos; 
}


Eso es todo, nuestra consulta para recuperar todos los Contactos que tenemos en la base de datos solo debemos usar la clausula "from Contacto". Si queren una referencia más amplia de HQL pueden encontrarla en esta página.

Bien, eso es todo. La clase "ContactosDAO" queda de la siguiente forma (omitiendo los imports):

public class ContactosDAO
{  
    private Session sesion; 
    private Transaction tx;  

    public long guardaContacto(Contacto contacto) throws HibernateException 
    { 
        long id = 0;  

        try 
        { 
            iniciaOperacion(); 
            id = (Long) sesion.save(contacto); 
            tx.commit(); 
        } catch (HibernateException he) 
        { 
            manejaExcepcion(he); 
            throw he; 
        } finally 
        { 
            sesion.close(); 
        }  

        return id; 
    }  

    public void actualizaContacto(Contacto contacto) throws HibernateException 
    { 
        try 
        { 
            iniciaOperacion(); 
            sesion.update(contacto); 
            tx.commit(); 
        } catch (HibernateException he) 
        { 
            manejaExcepcion(he); 
            throw he; 
        } finally 
        { 
            sesion.close(); 
        } 
    }  

    public void eliminaContacto(Contacto contacto) throws HibernateException 
    { 
        try 
        { 
            iniciaOperacion(); 
            sesion.delete(contacto); 
            tx.commit(); 
        } catch (HibernateException he) 
        { 
            manejaExcepcion(he); 
            throw he; 
        } finally 
        { 
            sesion.close(); 
        } 
    }  

    public Contacto obtenContacto(long idContacto) throws HibernateException 
    { 
        Contacto contacto = null;  
        try 
        { 
            iniciaOperacion(); 
            contacto = (Contacto) sesion.get(Contacto.class, idContacto); 
        } finally 
        { 
            sesion.close(); 
        }  

        return contacto; 
    }  

    public List<Contacto> obtenListaContactos() throws HibernateException 
    { 
        List<Contacto> listaContactos = null;  

        try 
        { 
            iniciaOperacion(); 
            listaContactos = sesion.createQuery("from Contacto").list(); 
        } finally 
        { 
            sesion.close(); 
        }  

        return listaContactos; 
    }  

    private void iniciaOperacion() throws HibernateException 
    { 
        sesion = HibernateUtil.getSessionFactory().openSession(); 
        tx = sesion.beginTransaction(); 
    }  

    private void manejaExcepcion(HibernateException he) throws HibernateException 
    { 
        tx.rollback(); 
        throw new HibernateException("Ocurrió un error en la capa de acceso a datos", he); 
    } 
}


Ahora, y para terminar el ejemplo, haremos uso de "ContactosDAO" en la clase "Main" para crear y recuperar algunas instancias de "Contactos". Como la clase es la misma que en el tutorial anterior no daré una explicación. La clase "Main" queda de la siguiente forma:

public class Main
{ 
    public static void main(String[] args) 
    { 
        ContactosDAO contactosDAO = new ContactosDAO(); 
        Contacto contactoRecuperado = null;  
        long idAEliminar = 0;  

        //Creamos tes instancias de Contacto 
        Contacto contacto1 = new Contacto("Contacto 1", "contacto1@contacto.com", "12345678"); 
        Contacto contacto2 = new Contacto("Contacto 2", "contacto2@contacto.com", "87654321"); 
        Contacto contacto3 = new Contacto("Contacto 3", "contacto3@contacto.com", "45612378");  

        //Guardamos las tres instancias, guardamos el id del contacto1 para usarlo posteriormente 
        idAEliminar = contactosDAO.guardaContacto(contacto1); 
        contactosDAO.guardaContacto(contacto2); 
        contactosDAO.guardaContacto(contacto3);  

        //Modificamos el contacto 2 y lo actualizamos 
        contacto2.setNombre("Nuevo Contacto 2"); 
        contactosDAO.actualizaContacto(contacto2);  

        //Recuperamos el contacto1 de la base de datos 
        contactoRecuperado = contactosDAO.obtenContacto(idAEliminar); 
        System.out.println("Recuperamos a " + contactoRecuperado.getNombre());  

        //Eliminamos al contactoRecuperado (que es el contacto3) 
        contactosDAO.eliminaContacto(contactoRecuperado);  

        //Obtenemos la lista de contactos que quedan en la base de datos y la mostramos 
        List<Contacto> listaContactos = contactosDAO.obtenListaContactos();  
        System.out.println("Hay " + listaContactos.size() + "contactos en la base de datos");  

        for(Contacto c : listaContactos) 
        { 
            System.out.println("-> " + c.getNombre()); 
        } 
    } 
}




En el momento que ejecutemos la aplicación Hibernate creará la tabla correspondiente en la base de datos (llamada "contactos", como lo indicamos) y con los siguientes campos:



Como podemos ver la tabla se ha generado en base a las indicaciones que hemos hecho con las anotaciones.

Para terminar veamos los datos de la tabla. Según la salida en la consola del NetBeans, debe haber dos contactos en la base de datos "Nuevo Contacto 2" y "Contacto 3":



Si vemos los datos en la base comprabos que, efectivamente, esto es correcto:



Con lo que comprobamos que el ejemplo funciona!!! ^- ^.

Bien, pues en esta ocasión vimos cómo usar Hibernate sin hacer uso de archivos de mapeo en XML, indicando esos datos en el código fuente de la clase.

Esto tiene sus ventajas y desventajas, así que debe usarse con cuidado. Por ejemplo, como la información está en el código fuente, cualquier cambio necesitará que recompilemos la aplicación. Además que nuestra entidad ahora ha quedado atada al API de JPA.

Esto puede no ser problema en muchos casos, pero habrá unos en los que si lo sea, como cuando no tenemos el código fuente de las entidades, o no queremos atarlas a JPA, porque las usaremos posteriormente en otra aplicación que no usará JPA. Pero, nuevamente, esto depende de la aplicación que estén desarrollando.

Además podemos mezclar ambos métodos en donde, si existe un mapeo en los dos lugares para una misma clase, el mapero del archivo XML toma precedencia.

Espero que este tutorial les haya sido de utilidad. En los próximos tutoriales mostraré como trabajar con Hibernate para hacer cosas más interesantes y mostraré como realizar la configuración de las dos formas, con anotaciones y archivos de mapeo.

No olviden dejar sus dudas, comentarios y sugerencias.

Saludos.

Descarga los archivos de este tutorial desde aquí:

Entradas Relacionadas:

45 comentarios:

  1. Este comentario ha sido eliminado por un administrador del blog.

    ResponderEliminar
  2. Hola Alex:
    excelente el tutorial, lo he probado y me funciona, soy nuevo en el tema, pero tengo una duda, ¿cómo hago con tablas relacionadas?; por ejemplo necesito mostrar datos de dos o mas tablas, usualmente en sql utilizo el inner join, como hago aqui??

    saludos...

    ResponderEliminar
  3. Hola;

    En este momento estoy preparando un turorial justamente de eso, pero aún me falta para terminarlo.

    Te resumo rápidamente:

    Como en Hibernate no escribes nada de SQL y todo lo hacer con objetos, los dos objetos deben estar relacionados, o sea, uno debe tener una instancia del otro. Por ejemplo, si tienes que una Persona tiene una Direccion, la relación debe ser más o menos así:

    @Entity
    class Persona
    {
    Direccion dir;
    }

    y establaces el tipo de relación (uno a uno, uno a muchos, muchos a muchos) con una anotación (@OneToOne, @OneToMany, o @ManyToMany) Esta relación la estableces en el campo relacionado. Siguiendo con el ejemplo anterior, como es una relación uno a uno quedaria más o menos así:


    @Entity
    class Persona
    {
    @OneToOne
    Direccion dir;
    }


    si vas a tener relaciones uno a muchos usas colecciones:

    @Entity
    class Persona
    {
    @OneToMany
    List<Direccion> direcciones = new ArrayList<Direccion>();
    }

    y dependiendo de tus necesidades usas un fetch type.

    Puedes investigar un poco de este tema en Google y cualquier duda me dices.

    Saludos

    ResponderEliminar
  4. hola donde se puede descargar el proyecto?

    ResponderEliminar
  5. Hola Jose David;

    Perdón, no he tenido tiempo para subir los proyecto a algún sitio para que los puedan descargar.

    Pero si tienes algún problema con tu código dime qué es lo que pasa y trataré de ayudarte a resolverlo.

    Saludos

    ResponderEliminar
  6. Hola antes que nada muchas gracias por el tutorial, mira yo lo he hecho en eclipse, pongo exactamente las mismas librerias que tu dices, pero no tengo exito, al parecer es un problema de librerias, el error que me arroja es con las SLF4J, que puedo hacer, desde ya te doy las gracias, atentamente Fernando, el error que me arroja es este:

    SLF4J: Class path contains multiple SLF4J bindings.
    SLF4J: Found binding in [jar:file:/C:/Documents%20and%20Settings/pajaro/Escritorio/cft/Hibernate02/Librerias/slf4j-log4j12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
    SLF4J: Found binding in [jar:file:/C:/Documents%20and%20Settings/pajaro/Escritorio/cft/Hibernate02/Librerias/slf4j-simple-1.5.8.jar!/org/slf4j/impl/StaticLoggerBinder.class]
    SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
    Exception in thread "main" java.lang.NullPointerException
    at org.hibernateanotaciones.ContactosDAO.guardaContacto(ContactosDAO.java:36)
    at org.hibernateanotaciones.Main.main(Main.java:25)


    Gracias

    ResponderEliminar
  7. Hola Fernando;

    Lo que ocurre es que estas colocando dos jars que usan la misma clase y en las mismas versiones, y la maquina virtual no sabe cual de las dos usar.

    Las dos versiones de sl4j que estas esando es la simple, y la que hace uso de log4j. Yo te recomiendo que solo uses la simple. O sea que puedes eliminar el archivo "slf4j-log4j12.jar" del classpath de tu aplicación

    Saludos

    ResponderEliminar
  8. Buena onda de cursos...Excelentes...

    ResponderEliminar
  9. Hola soy Nancy y soy nueva con Hibernate.
    El tutorial es excelente.
    Felicitaciones.
    El problema es que me da muchos errores, y creo que por la base de datos. Estoy utilizando apache2triad y creo que ese es el problema, ademas me da errores del driver, pero me he descargado la versión que viene en tú tutorial, pero igual me da problemas.
    Haber si me puedes hechar una mano.
    Gracias

    ResponderEliminar
  10. Hola Nancy;

    Muchas gracias por tus comentarios.

    ¿Me podrías decir qué errores son los que te dá?

    Se me ocurre que los errores podrían ser de la versión de MySQL que viene en apache2triad. ¿Podrías revisar cuál es?

    Saludos

    ResponderEliminar
  11. Hola Alex mira la version de MySQL5.0.18.
    Haber si me puedes hechar una mano.
    No sabes como te lo agradecería.
    Saludos.

    ResponderEliminar
  12. 1391 [main] ERROR org.hibernate.tool.hbm2ddl.SchemaExport - schema export unsuccessful
    java.sql.SQLException: No suitable driver
    at java.sql.DriverManager.getConnection(DriverManager.java:545)
    at java.sql.DriverManager.getConnection(DriverManager.java:140)
    at org.hibernate.connection.DriverManagerConnectionProvider.getConnection(DriverManagerConnectionProvider.java:133)
    at org.hibernate.tool.hbm2ddl.SuppliedConnectionProviderConnectionHelper.prepare(SuppliedConnectionProviderConnectionHelper.java:51)
    at org.hibernate.tool.hbm2ddl.SchemaExport.execute(SchemaExport.java:252)
    at org.hibernate.tool.hbm2ddl.SchemaExport.create(SchemaExport.java:211)
    at org.hibernate.impl.SessionFactoryImpl.(SessionFactoryImpl.java:343)
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1327)
    at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:867)
    at hibernateanotaciones.HibernateUtil.(HibernateUtil.java:27)
    at hibernateanotaciones.ContactosDAO.iniciaOperacion(ContactosDAO.java:115)
    at hibernateanotaciones.ContactosDAO.guardaContacto(ContactosDAO.java:33)
    at hibernateanotaciones.Main.main(Main.java:34)
    1469 [main] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 0, SQLState: 08001
    1469 [main] ERROR org.hibernate.util.JDBCExceptionReporter - No suitable driver
    Exception in thread "main" java.lang.NullPointerException
    at hibernateanotaciones.ContactosDAO.manejaExcepcion(ContactosDAO.java:121)
    at hibernateanotaciones.ContactosDAO.guardaContacto(ContactosDAO.java:38)
    at hibernateanotaciones.Main.main(Main.java:34)
    Java Result: 1
    )

    ResponderEliminar
  13. Alex el tutorial ha sido fenomenal, en verdad lo explicas todo muy bien.
    Después de tanto darle vueltas y darme tanto fallos lo que he hecho es instalarme MySQL aparte y ya me funciona.
    No sabes me has salvado la vida con tu tutorial.
    Tengo que entregar un proyecto de fin de ciclo que abarca base de datos orientada a objetos y Ldap.
    Nuevamente gracias.

    ResponderEliminar
  14. hola!!!

    oye al tratar de hacer este ejmplo con anotaciones me sale el siguiente error!!

    Initial SessionFactory creation failed. org.hibernate.MappingException: An AnnotationConfiguration instance is required to use

    que crees que pueda ser??

    ResponderEliminar
  15. Hola Nancy;

    Que bueno que lograste resolver el problema :D. Tal vez la versión de MySQL que viene en apache2triad tenga algún problema.

    Y que bueno que el tutorial te haya servido :D.

    Saludos

    ResponderEliminar
  16. Hola Juan Carlos;

    Se me ocurre que puedes estar usando una instancia de "Configuration" en HibernateUtil en vez de "AnnotationConfiguration" o que hayas olvidado incluir algún jar en las librerias del proyecto.

    ResponderEliminar
  17. Gracias por el segundo capitulo, muy buena explicación, sobre todo los pro y contra de usar archivos de mapeo xml Vs las anotaciones.

    ResponderEliminar
  18. Amigo, muy bueno el tutorial, me funcionó a la perfección. Pero quisiera saber si podrias explicar en un tutorial pequeño, como configurar el C3PO (tal vez para este ejemplo).
    Resulta que lo descargue y lo agregue a mi proyecto (creando la libreria en netbeans), pero me arrojaba NullPointerException en la clase ContactosDAO que tienes en este tutorial.
    Podrias hacer uno, usando el C3PO?
    Muchas gracias, Saludos desde Colombia.

    ResponderEliminar
  19. Excelente aporte¡¡¡ No sólo esta entrada sino el blog al completo :) Se agradece mucho tu esfuerzo¡¡¡

    Es un blog necesario en FAVORITOS de cualquier explorador

    Gracias por tu trabajo, me ha sacado de muchos apuros¡¡

    ResponderEliminar
  20. hola la verdad es un exelente tutorial para el manejo de hibernate pero me quedan algunas dudas, espero me las puedas de verdad responder, aunque sea de pasadita.

    1.-Cuando agrego el numero de conexiones en el archivo de hibernate se genera automaticamente el pool?????

    yo configure el pool aparte con jdbc y me gustaria saber como usar a hibernate con pool ed conexiones pero si con la configuracion que le diste es mas que suficiente para que interprete esa parte???????

    ResponderEliminar
  21. Excelente tutorial te agradesco mucho.
    Me podrias orientar de como deberia modificar el codigo del metodo guardaContacto() de tal forma que pueda cachar y retornar el error en el caso de que el id ya exista.
    Gracias nuevamente.

    ResponderEliminar
  22. @sickdhartha

    Hola sickdhartha;

    Efectivamente, cuando pones el número de conexiones en el archivo de configuración se maneja de forma automática un pool de conexiones. Sin embargo, como comento en el artículo, este pool es solo para pruebas, no se recomienda ya en un ambiente de producción. Para este caso lo mejor es usar algún otro pool como c3p0, para el cual hibernate ya tiene una muy buena integración.

    Saludos

    ResponderEliminar
  23. @Carlos Agustín L. Avila

    Hola Carlos;

    En realidad el id se genera de forma automática, por lo que no hay forma de que se repita. En caso de que quisieras generarlo manualmente y este se repitiera, automáticamente recibirias una HibernateExeption indicando que el identificador ya existe.

    Saludos

    ResponderEliminar
  24. Che disculpa, pero me saltó este error y probé de todo y no pude solucionarlo, agradeceria tu ayuda

    Exception in thread "main" org.hibernate.HibernateException: Ocurrió un error en la capa de acceso a datos at hibernateconanotaciones.ContactosDAO.manejaExcepcion(ContactosDAO.java:114)
    at hibernateconanotaciones.ContactosDAO.guardaContacto(ContactosDAO.java:30)
    at hibernateconanotaciones.Main.main(Main.java:29)
    Caused by: org.hibernate.MappingException: Unknown entity: hibernateconanotaciones.Contacto
    at org.hibernate.impl.SessionFactoryImpl.getEntityPersister(SessionFactoryImpl.java:550)
    at hibernateconanotaciones.ContactosDAO.guardaContacto(ContactosDAO.java:26)

    Ojalá me puedas ayudar.

    ResponderEliminar
  25. Lo solucioné reemplazando
    import org.hibernate.annotations.Entity;

    con

    import javax.persistence.Entity;

    gracias igual

    ResponderEliminar
  26. Só duas palavras deste tutorial.

    Simples e eficaz.

    Agora a serio, esta muito bom.

    Cumprimentos.

    ResponderEliminar
  27. Hola, busco ayuda acerca de este interesante tutorial. Seguí el ejemplo, pero no me funciona.
    Me genera el siguiente error:
    run:
    Exception in thread "main" java.lang.NullPointerException
    at dao.ContactosDAO.guardaContacto(ContactosDAO.java:38)
    at mapeos.Main.main(Main.java:34)
    Java Result: 1
    BUILD SUCCESSFUL (total time: 1 second)

    Gracias

    ResponderEliminar
    Respuestas
    1. Tengo el mismo problema!! lo pudiste solucionar?

      Eliminar
    2. Agregar la libreria hibernate-jpa-2.0-api-1.0.1.Final.jar

      Eliminar
  28. excelente tu metodología para enseñar, me están viniendo de 10 tus tutos. Estaría bueno uno sobre Struts, Spring e Hibernate, los 3 frameworks integrados:D , jaja es mucho pedir! lo que pasa es que en muchos laburos ultimamente están utilizandolos

    ResponderEliminar
  29. Hola Alex!,

    Excelente tutorial, al igual que el anterior en estos días revisaré los demás.

    Saludos Cordiales.

    ResponderEliminar
  30. Hola Alex antes que nada gracias, por compartir tu tiempo y experiencia, seguí el tutorial, y mi pregunta es, yo quiero utilizar hibernate para aplicaciones swing, la configuración seria la misma o existe algún archivo .xml donde se le tiene que indicar?
    Otra pregunta, caso yo no quiera que el hibernate genere las tablas, en el .xml le indico como "false" con eso bastaría para que el al realizar la conexión y que el hibernate reconozca las tablas que creo de forma manual? de ante mano saludos y gracias!

    ResponderEliminar
  31. intente ejecutar tu ejemplo en mi netbeans 7.1 con sus respectivas librerias y me tira este error.. lo debuggie y cuando hace el openSesion() metirar el error



    Exception in thread "main" java.lang.NullPointerException
    at hibernate.anotaciones.dao.ContactosDAO.guardaContacto(ContactosDAO.java:47)
    at hibernate.anotaciones.Main.main(Main.java:32)
    Java Result: 1

    ResponderEliminar
  32. Añade la libreria Hibernate JPA y ya lo tienes resuelto.

    ResponderEliminar
  33. Hola, excelente tutorial, me ha aclarado muchos conceptos, pero tengo una pregunta. Me he dado cuenta de que si ejecutas varias veces las inserciones, te inserta en la base de datos los mismos registros varias veces, lo cual en una aplicación real no es aceptable. ¿Como podría controlar esto?. Me explico, antes de insertar un nuevo registro, comprobar que no existe ya e insertarlo, y en el caso de que ya exista en la base de datos, tirar un error.
    Un saludo y gracias de antemano!

    ResponderEliminar
    Respuestas
    1. Como programador de aplicaciones es algo que tenes que manejar aca te dejo un método que desarrolle para tu caso: public boolean isRepetido(Contacto contacto) {

      try {

      iniciaOperacion();

      Contacto nuevoContacto = contacto;

      List listaContactos = obtenListaContactos();

      for (Contacto c : listaContactos) {

      if (nuevoContacto.getNombre().equals(c.getNombre())
      && nuevoContacto.getEmail().equals(c.getEmail())) {
      System.out.println("Lo encontramos!!");

      return true;

      }

      }

      } catch (HibernateException e) {
      manejaExcepcion(e);

      }

      return false;

      }

      Eliminar
  34. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  35. https://www.youtube.com/watch?v=4d79Rj-n710

    ResponderEliminar
  36. Vengo del futuro: enero de 2016, quiero saber si aún respondes algunas dudas, ya que recién empiezo con hibernate, gracias.

    ResponderEliminar
    Respuestas
    1. Hola viajero. Claro que sí, aún respondemos cualquier duda que se tenga de cualquier tema de Java :)

      Saludos

      Eliminar
    2. Hola, yo tambien vengo del futuro: marzo 2016, realize tutorial y me manda esto:
      Exception in thread "main" java.lang.NullPointerException
      at hibernateanotaciones.ContactosDAO.guardaContacto(ContactosDAO.java:34)
      at hibernateanotaciones.Inicio.main(Inicio.java:18)
      Yo tambien quiero saber como te contacto para dudas, tal ves te invite una Pepsi Perfect. He buscado en internet sobre el error y segun AnnotationConfiguration es deprecated, me imagino que por la epoca, pero aún asi pienso seguir tu tutorial, es el mejor que he visto, a pesar de la epoca.

      Eliminar