Mapeo Sencillo
La siguiente serie de documentos tiene por objetivo presentar ejemplos de tipos de mapeos que se pueden realizar con Hibernate 3 y Java Persistence API ( JPA ). La intención es cubrir paulatinamente con ejemplos desde los mapeos más sencillos, hasta los más complicados.
En este primer documento se muestra el manejo manual de sesiones y transacciones; escenario que se simplifica en otros tutoriales mediante el manejo de dependency injection y de transacciones declarativas.
Mapeo Sencillo
Este primer documento presenta un mapeo sencillo de una clase ( FacturaDTO ) hacia una tabla en base de datos ( factura1 ). Las clases de código que se manejarán en este tutorial se pueden obtener a partir de este archivo comprimido; y se presentan visualmente en la siguiente imagen:
![]() |
Las clases que se ejecutarán en esta aplicación sencilla se almacenan en este caso en el paquete de java single_table_simple_key. Obviamente, una aplicación de producción no llevaría nombres de paquetes como éste, pero para efectos de poder organizar todos los ejemplos de esta serie en un solo proyecto java, esta organización resulta práctica.
Dentro de este paquete se definen los siguientes archivos:
- create.sql define el código DDL para la definición de la tabla factura1 en una base de datos. Dependiendo del manejador de base de datos que se utilice, este código puede requerir ajustes. Se proporciona como código de referencia para que pueda ser ejecutado sobre un manejador en particular; este paso debe realizar de forma manual por un desarrollador antes de ejecutar el código de la aplicación.
- hibernate.cfg.xml es el archivo de configuración propio de Hibernate.
- FacturaDTO.java define el código para FacturaDTO, la clase que será mapeada hacia la tabla factura1.
- Test.java define una clase que probará el mapeo realizando inserciones, actualizaciones, borrados y búsquedas sobre tipos de datos FacturaDTO.
Fuera del paquete single_table_simple_key se definen los archivos para la configuración de facilidades de bitácora commons-logging.properties y log4j.properties. El objetivo de estos archivos es poder direccionar la salida de bitácora producida por la aplicación y sus componentes, y también poder configurar el nivel de filtrado de los mensajes que se desplegarán. Se puede experimentar con la configuración de estos archivos si se desea, pero con el objetivo de mantener la discusión lo más simple posible, no se entrará a detalle acerca del funcionamiento de los mismos a lo largo de este documento.
Además de estos archivos de código, se requieren una serie de librerías para poder hacer funcionar la aplicación. Las librerías que se utilizaron para hacer funcionar esta aplicación se pueden obtener de aquí, y a las mismas habría que agregar las librerías estándares de J2EE 1.4 ( o superior ) y los drivers de la base de datos que se esté utilizando.
La siguiente imagen reorganiza algunos de los archivos mostrados anteriormente, en términos de la funcionalidad que tendrán como parte de la aplicación:
![]() |
Las siguientes secciones desglosan el contenido cada uno de estos archivos.
Base de Datos: create.sql
![]() |
La estructura de la tabla factura1 es muy sencilla:
CREATE TABLE factura1
(
fa_id SERIAL NOT NULL,
fa_version INT NOT NULL,
fa_name VARCHAR(40) NOT NULL,
PRIMARY KEY( fa_id )
);
Como puede verse, se trata de una tabla con tres campos:
- El campo fa_id, que representa la llave primaria de la tabla, y que en este caso se trata de un campo autoincrementado. La sintaxis para definir autoincremento en algunas bases de datos diferirá a la que se presenta aquí, por lo que este código puede requerir ajsutes en esos casos. Si un manejador de base de datos no soporta columnas de autoincremento, en ese caso alguna otra estrategia para la generación de la llave primaria debe ser utilizada ( secuencias, valor máximo, etc. ).
- El campo fa_version, que se utiliza para definir una columna de versión, para implementar el bloqueo optimista de recursos. Esta columna, en caso de definirse, se insertará y actualizará automáticamente por parte de Hibernate.
- El campo fa_name, que representa una cadena típica que es mapeada hacia la base de datos.
Se pueden adicionar columnas adicionales con otros tipos de datos si así se desea. En este caso se mantiene la tabla lo más sencilla posible con el fin de facilitar las explicaciones.
El contenido de este archivo deberá ser ejecutado manualmente sobre la base de datos que se vaya a utilizar utilizando las herramientas propias de dicho manejador.
Configuración de Hibernate: hibernate.cfg.xml
![]() |
El archivo de configuración de Hibernate tiene la siguiente estructura:
<?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="hibernate.connection.driver_class">
driver</property>
<property name="hibernate.connection.url">
url</property>
<property name="hibernate.connection.username">
login</property>
<property name="hibernate.connection.password">
password</property>
<property name="hibernate.connection.pool_size">
1</property>
<property name="hibernate.dialect">
org.hibernate.dialect.XXXDialect</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory
</property>
<mapping class="single_table_simple_key.FacturaDTO"/>
</session-factory>
</hibernate-configuration>
En el código anterior, se deben sustituir los valores driver, url, login y password por los correspondientes a la conexión que se esté realizando en ese momento. El valor org.hibernate.dialect.XXXDialect también debe personalizarse a que corresponda con la clase que define el dialecto de la base de datos que se está utilizando. Revisar esta referencia para verificar cuáles son los dialectos disponibles.
La configuración de Hibernate listada con anterioridad puede visualizarse como separada en dos partes: propiedades de configuración y mapeos:
![]() |
Las propiedades de configuración se refieren a:
- Los datos de conexión hacia el manejador de base de datos a través de las propiedades hibernate.connection.driver_class, hibernate.connection.url, hibernate.connection.username e hibernate.connection.password, que corersponden a los cuatro parámetros que normalmente se requieren en JDBC para realizar conexión hacia una base de datos relacional. En este caso se está configurando una aplicación Java SE tradicional, y por lo tanto se hace uso de conexiones simples.
- Como en este caso se hace uso de conexiones simples ( en contraste al manejo de datasources en servidores de aplicaciones ), se define en este caso también el tamańo del pool de conexiones. Para una prueba sencilla, una conexión es más que suficiente:
- El manejo de dialectos permite personalizar el SQL generado por Hibernate hacia una base de datos en particular.
- El manejo de transacciones será por JDBC en este caso, ya que se está trabajando con una aplicación Java SE. En aplicaciones Java EE ( o J2EE ) se recomienda más trabajar con transacciones JTA que aprovechan las características de un servidor de aplicaciones:
<property name="hibernate.connection.driver_class">
driver</property>
<property name="hibernate.connection.url">
url</property>
<property name="hibernate.connection.username">
login</property>
<property name="hibernate.connection.password">
password</property>
Por ejemplo, una base de datos Derby podría manejar un driver class org.apache.derby.jdbc.EmbeddedDriver, un URL de conexión como jdbc:derby:test, un usuario cualquiera como sa, y un campo de password vacío.
<property name="hibernate.connection.pool_size">
1</property>
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLDialect</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory</property>
Por otro lado, la sección de mapeos hace referencia hacia la configuración de mapeos de la aplicación. En Hibernate 3 se pueden tener dos tipos de mapeos: mapeos basados en un archivo XML, y mapeos sobre clases con anotaciones JPA estándares. En este caso únicamente se está mapeando una clase, y este mapeo se realiza por medio de anotaciones ( lo que nos ahorra la creación de un archivo XML adicional ):
<mapping class=
"single_table_simple_key.FacturaDTO"/>
Mapeo: FacturaDTO.java
![]() |
El código de la clase FacturaDTO es el siguiente:
package single_table_simple_key;
import org.hibernate.annotations.Proxy;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.*;
import java.io.Serializable;
/**
* Mapeo simple de un objeto factura
*/
@Entity @Table( name="factura1" )
@Proxy( lazy=false )
public class FacturaDTO implements Serializable
{
private int _id;
private int _version;
private String _nombre;
/////////////////////////////////////////////////////////
// Getter Persistent Methods
@Id @Column( name="fa_id" )
@GeneratedValue( strategy=IDENTITY )
public int getId()
{ return _id; }
@Version @Column( name="fa_version" )
public int getVersion()
{ return _version; }
@Column( name="fa_name" )
public String getNombre()
{ return _nombre; }
/////////////////////////////////////////////////////////
// Setter Methods
public void setId( int id )
{ _id = id; }
public void setVersion( int version )
{ _version = version; }
public void setNombre( String nombre )
{ _ nombre = nombre; }
}
Como puede verse, esta es una clase sencilla que almacena tres propiedades, cada una de las cuales se refiere a una columna en la tabla factura1. Visualmente se puede ver esta relación como sigue:
![]() |
El mapeo de este código java contra la base de datos se desglosa en la siguiente tabla:
| FacturaDTO.java | create.sql |
|---|---|
@Entity @Table( name="factura1" )
|
CREATE TABLE factura1 |
@Id @Column( name="fa_id" )
|
fa_id SERIAL NOT NULL,
|
@Version @Column( name="fa_version" )
|
fa_version INT NOT NULL |
@Column( name="fa_name" )
|
fa_name VARCHAR(40) NOT NULL |
Mapeo de la Tabla
@Entity @Table( name="factura1" )
@Proxy( lazy=false )
public class FacturaDTO implements Serializable
{
...
}
La configuración de la tabla hace uso en este caso de las siguientes anotaciones:
- javax.persistence.Entity ( @Entity ): Esta es la etiqueta JPA para designar a un componente de entidad ( el sucesor de los EJB Entity Beans 2.x ).
- javax.persistence.Table ( @Table ): Esta etiqueta JPA estándar permite definir el nombre de la tabla en base de datos hacia la que se mapeará la clase, en este caso factura1. En caso de no definir esta anotación, se tomará el nombre de la clase como el nombre de la tabla ( en este caso se requeriría una tabla llamada FacturaDTO ), lo cual en algunas aplicaciones es práctico, pero en ciertas organizaciones donde se manejan convenciones estrictas de nombres para los objetos de la base de datos puede no resultar adecuado.
- org.hibernate.annotations.Proxy ( @Proxy ): Esta es una anotación de Hibernate que indica que la clase actual no será un proxy que maneja lazy loading; en un escenario de lazy loading, los datos asociados al objeto son cargados a partir de base de datos cuando alguna de las propiedades del objeto ( fuera de la llave primaria ) son invocados. Para que un escenario de este estilo sea exitoso, el objeto siempre debería trabajar relacionado con una conexión. Pero en los ejemplos que se presentan en la clase de pruebas de este tutorial se abren y cierran conexiones y transacciones, por lo que un manejo basado en lazy loading sobre la clase actual lanzaría una excepción de tipo org.hibernate.LazyInitializationException.
[ERROR]27/08/2007 15:34:17,655 (Test): Error:
org.hibernate.LazyInitializationException:
could not initialize proxy -
the owning Session was closed
at org.hibernate.proxy.AbstractLazyInitializer.initialize
(AbstractLazyInitializer.java:60)
Es por ello que el manejo de esta anotación con el atributo lazy=false se requiere en este caso. Otra manera en Hibernate 3 para asegurarse que no se maneje lazy loading a nivel clase es declarando la clase como final, en cuyo caso el manejo de esta anotación se vuelve innecesario. Sin embargo, puede ser importante mencionar que la especificación JPA no permite que las clases manejadas de acuerdo a esta especificación estén marcadas como finales ( aunque la implementación particular de Hibernate sí lo permite ).
Por otro lado, el mapeo de cada propiedad de la clase hacia las columnas en la base de datos se podría realizar a nivel campo o a nivel propiedad ( en el método getter o setter de la clase ). En este tutorial se hace uso del mapeo a nivel propiedad.
Mapeo de la Llave Primaria
@Id @Column( name="fa_id" )
@GeneratedValue( strategy=IDENTITY )
public int getId()
{ return _id; }
La configuración del campo que implementa la llave primaria hace uso en este caso de las siguientes anotaciones:
- javax.persistence.Id ( @Id ) que es la anotación estándar JPA para indicar que la columna actual es una llave primaria simple. En documentos posteriores se mostrará el manejo de llaves primarias compuestas.
- javax.persistence.Column ( @Column ) que es la anotación estándar JPA para definir el nombre de la columna en la base de datos asociada a esta columna. Si no se define esta anotación, el nombre de la columna será igual al nombre de la propiedad. El nombre de la propiedad puede derivarse a partir de un método getter o setter de la siguiente manera:
- Quitar el prefijo get / set; en este caso al quitar el prefijo al método getId() queda “Id”.
- Pasar el primer caracter de la cadena a minúscula; en este caso el nombre de la propiedad sería “id”.
- javax.persistence.GeneratedValue ( @GeneratedValue ) que es la anotación estándar para definir estrategias para la generación de llaves primarias. En este caso se hace uso de un campo de identidad. Hibernate define facilidades adicionales al estándar para definir estrategias de generación de identidad; estas facilidades se pueden definir por medio de anotaciones propias de Hibernate ( como org.hibernate.annotations.GenericGenerator ).
Mapeo del Campo de Versión ( Opcional )
@Version @Column( name="fa_version" )
public int getVersion()
{ return _version; }
El campo de versión es una columna numérica o de tipo timestamp, y se denota por medio de la anotación javax.persistence.Version. Este tipo de campos definen una columna para el manejo de bloqueos de registros optimistas. Un bloque optimista es una técnica común en aplicaciones web para minimizar el número de actualizaciones concurrentes hacia un mismo registro. En un escenario de este tipo cada registro maneja un contador ( campo de versión ) que es actualizado con cada escritura hacia los datos de dicho registro.
![]() |
Una operación de escritura sobre el registro siempre verifica primero que esta marca coincida con la lectura anterior que realizó un usuario sobre dicho registro. Si las marcas coinciden, el registro se actualiza y el número de versión se incrementa.
![]() |
En caso de que el número de versión de la actualización no coincida con el número de versión en el registro de la base de datos, se lanza una excepción de tipo org.hibernate.StaleObjectStateException.
![]() |
[ERROR]27/08/2007 15:24:42,844 (AbstractFlushingEventListener):
Could not synchronize database state with session
org.hibernate.StaleObjectStateException:
Row was updated or deleted
by another transaction (or unsaved-value mapping was incorrect):
[single_table_simple_key.FacturaDTO#2]
at org.hibernate.persister.entity.AbstractEntityPersister.check
(AbstractEntityPersister.java:1680)
Hibernate realiza esta verificación y actualización del campo de versión de forma transparente al usuario.
Mapeo de Campos Sencillos
@Column( name="fa_name" )
public String getNombre()
{ return _nombre; }
La anotación javax.persistence.Column ( @Column ) permite personalizar el nombre de una columna hacia la base de datos. Si no se definiera esta anotación para una propiedad, ésta se buscaría mapear contra la base de datos utilizando el nombre por defecto para la propiedad; en este caso “nombre”.
En el caso que una propiedad del componente no se deseara mapear hacia la base de datos, se tendría que marcar dicha propiedad con la anotación javax.persistence.Transient, por ejemplo:
@Transient // no mapeada
public String getNombre()
{ return _nombre; }
Prueba Ejecutable: Test.java
![]() |
El siguiento fragmento de código muestra la parte inicial de la clase de prueba:
package single_table_simple_key;
import org.hibernate.*;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.criterion.Expression;
import org.apache.log4j.Logger;
import java.util.List;
/**
* Manipulación de una tabla simple con llave sencilla.
*/
public final class Test
{
private static Logger _logger = Logger.getLogger( Test.class );
public static void main(String[] args)
{
try
{ new Test().execute(); }
catch( Exception e )
{ _logger.error( "Error:", e ); }
}
...
}
Aquí simplemente se crea una instancia para escritura a bitácora llamada _logger, y se ejecuta el método execute() de la clase actual. Todos los errores que se produzcan se mandarán a bitácora.
Creación de la Fábrica de Sesiones de Hibernate
La primera parte del método execute() de la clase Test se lista a continuación:
/**
* Método de ejecución de altas, bajas, cambios y consultas.
*/
private void execute() throws Exception
{
SessionFactory factory = createSessionFactory();
...
}
La clase org.hibernate.SessionFactory es el objeto que permitirá crear sesiones de Hibernate hacia la base de datos.
![]() |
El método createSessionFactory() se define como sigue:
private SessionFactory createSessionFactory()
throws HibernateException
{
AnnotationConfiguration configuration =
new AnnotationConfiguration();
configuration.configure(
"/single_table_simple_key/hibernate.cfg.xml" );
return configuration.buildSessionFactory();
}
Esto simplemente construye la configuración de Hibernate a partir del archivo hibernate.cfg.xml que se discutió con anterioridad.
![]() |
Inserción de Datos
Posteriormente, se realiza la inserción de datos como parte del método execute() de la clase Test, y se obtiene el valor autogenerado para el nuevo registro:
/**
* Método de ejecución de altas, bajas, cambios y consultas.
*/
private void execute() throws Exception
{
SessionFactory factory = createSessionFactory();
int id = insertObject( factory );
...
}
El método insertObject() crear una nueva instancia de tipo FacturaDTO, abre una sesión de Hibernate a partir de la fábrica de sesiones, abre una transacción sobre dicha sesión, y por último realiza la inserción del dato a través del método save() de la misma sesión.
![]() |
El listado completo de esta funcionalidad es el siguiente:
private int insertObject( SessionFactory factory )
{
FacturaDTO factura = new FacturaDTO();
factura.setNombre( "factura1" );
Session session = factory.openSession();
try
{ insertObject( session, factura ); }
finally
{ session.close(); }
return factura.getId();
}
private void insertObject( Session session, FacturaDTO factura )
{
boolean commit = false;
Transaction transaction = session.beginTransaction();
try
{
session.save( factura );
commit = true;
}
finally
{
if( commit )
transaction.commit();
else
transaction.rollback();
}
}
Como puede verse, se hace uso de un bloque try/finally para asegurar el cierre de la sesión de Hibernate que se ha abierto ( org.hibernate.Session ), y también se hace uso de un bloque try/finally para asegurar el cierre de la transacción ( org.hibernate.Transaction ).
Nótese que el identificador generado por la base de datos está disponible automáticamente por medio del método getId() de la clase una vez que la inserción se ha realizado.
Lectura de Datos
El siguiente paso es mostrar cómo se puede hacer la lectura de un registro, mediante la obtención del registro que se ha insertado como parte del método execute() de la clase Test:
/**
* Método de ejecución de altas, bajas, cambios y consultas.
*/
private void execute() throws Exception
{
SessionFactory factory = createSessionFactory();
int id = insertObject( factory );
FacturaDTO factura = getObject( factory, id );
_logger.debug( "Nombre inicial: " + factura.getNombre() );
...
}
El método getObject() utiliza una sesión de Hibernate para obtener una instancia de tipo FacturaDTO a partir de su identificador por medio del método load(). En este caso, como se trata de una operación de solo lectura, no se utilizan transacciones:
![]() |
El listado completo de esta funcionalidad es el siguiente:
private FacturaDTO getObject( SessionFactory factory, int id )
{
Session session = factory.openSession();
try
{ return ( FacturaDTO )session.load(
FacturaDTO.class, id ); }
finally
{ session.close(); }
}
El método load() de la interfase org.hibernate.Session recibe el nombre de la clase, y el identificador único del dato a cargar. Si se quisiera cargar un dato de un identificador no válido, se lanzaría una excepción de tipo org.hibernate.ObjectNotFoundException:
[ERROR]27/08/2007 16:49:14,238 (Test): Error:
org.hibernate.ObjectNotFoundException:
No row with the given identifier
exists: [single_table_simple_key.FacturaDTO#2000]
at org.hibernate.impl.SessionFactoryImpl$1.handleEntityNotFound
(SessionFactoryImpl.java:372)
La interfase org.hibernate.Session define un método get() que puede manejarse en lugar de load() y que regresa null si no encuentra el objeto asociado a la llave solicitada, en lugar de lanzar excepciones.
Actualización de Datos
Después se realiza una actualización de datos:
/**
* Método de ejecución de altas, bajas, cambios y consultas.
*/
private void execute() throws Exception
{
SessionFactory factory = createSessionFactory();
int id = insertObject( factory );
FacturaDTO factura = getObject( factory, id );
_logger.debug( "Nombre inicial: " + factura.getNombre() );
updateObject( factory, factura );
...
}
El método updateObject() modifica un campo del DTO ( que actualmente se encuentra en modo desconectado ), y por medio del método update() de una sesión de Hibernate se realiza la actualización en la base de datos en el contexto de una transacción:
![]() |
El listado completo de esta funcionalidad es el siguiente:
private void updateObject(
SessionFactory factory, FacturaDTO factura )
{
factura.setNombre( "factura2" );
Session session = factory.openSession();
try
{ updateObject( session, factura ); }
finally
{ session.close(); }
}
private void updateObject(
Session session, FacturaDTO factura )
{
boolean commit = false;
Transaction transaction = session.beginTransaction();
try
{
session.update( factura );
commit = true;
}
finally
{
if( commit )
transaction.commit();
else
transaction.rollback();
}
}
La estructura de este código es muy similar a la de inserción. La verificación del campo de versión del DTO proporcionado ( en caso de haberse definido alguno ) contra la base de datos se realiza de forma automática como parte de la ejecución del método update().
En este caso el uso del método update() es importante para la actualización, ya que se está trabajando con un componente que se encontraba desconectado de la sesión; esto significa que la sesión a través de la cual el objeto fue leído de base de datos ya había sido cerrada con anterioridad, como será el caso en aplicaciones web donde cada uno de estos pasos se realiza en una pantalla distinta de la interfase de usuario.
Si se está trabajando con un objeto que se encuentra relacionado con una sesión, la simple modificación del estado de este objeto ( como cuando se ejecutan sus métodos setter ) ocasionará actualizaciones hacia la base de datos al cerrarse la transacción actual o ejecutar el método flush() definido en la interfase org.hibernate.Session. Un ejemplo de este tipo de actualizaciones sería el siguiente:
private void updateObject(
Session session, int id, String nombre )
{
boolean commit = false;
Transaction transaction = session.beginTransaction();
try
{
FacturaDTO factura =
( FacturaDTO )session.load( FacturaDTO.class, id );
factura.setNombre( nombre );
// se altera el estado del objeto
// no se requiere ejecutar el método update()
commit = true;
}
finally
{
if( commit )
transaction.commit();
else
transaction.rollback();
}
}
Lectura de Datos por Criteria
Una vez actualizada la información en base de datos, el programa de prueba realiza de nuevo la lectura del registro pero ahora utilizando el método queryObject():
/**
* Método de ejecución de altas, bajas, cambios y consultas.
*/
private void execute() throws Exception
{
SessionFactory factory = createSessionFactory();
int id = insertObject( factory );
FacturaDTO factura = getObject( factory, id );
_logger.debug( "Nombre inicial: " + factura.getNombre() );
updateObject( factory, factura );
factura = queryObject( factory, id );
_logger.debug( "Nombre final: " + factura.getNombre() );
...
}
El método queryObject() realiza la consulta de un dato utilizando la interfase org.hibernate.Criteria. Esta interfase permite construir consultas hacia la base de datos utilizando una perspectiva orientada a objetos:
![]() |
El listado completo de esta funcionalidad es el siguiente:
private FacturaDTO queryObject(
SessionFactory factory, int id )
{
Session session = factory.openSession();
try
{
Criteria criteria = session.createCriteria(
FacturaDTO.class );
criteria.add( Expression.eq( "id", id ) );
List> results = criteria.list();
return ( FacturaDTO )results.get( 0 );
}
finally
{ session.close(); }
}
En este caso se construye un objeto Criteria para el tipo de dato FacturaDTO por medio del método createCriteria() de la interfase Session. Posteriormente se agrega una expresión a la búsqueda que limita los resultados a únicamente aquellos valores cuya propiedad “id” corresponda al identificador pasado como parámetro.
El método list() permite regresar un listado de todos aquellos objetos que cumplan con los criterios de búsqueda, y de este listado obtenemos el primer elemento y lo regresamos.
En lugar del método list(), también se puede utilizar el método uniqueResult() cuando se realizan consultar que sabemos que regresan un solo resultado. En este caso el método anteriorquedaría como sigue:
private FacturaDTO queryObject(
SessionFactory factory, int id )
{
Session session = factory.openSession();
try
{
Criteria criteria = session.createCriteria(
FacturaDTO.class );
criteria.add( Expression.eq( "id", id ) );
return ( FacturaDTO )criteria.uniqueResult();
}
finally
{ session.close(); }
}
Lectura de Datos por Query ( HQL )
Otra manera de realizar lectura de datos es utilizando HQL ( Hibernate Query Language ). El programa de pruebas hace uso de esta facilidad en su siguiente paso:
/**
* Método de ejecución de altas, bajas, cambios y consultas.
*/
private void execute() throws Exception
{
SessionFactory factory = createSessionFactory();
int id = insertObject( factory );
FacturaDTO factura = getObject( factory, id );
_logger.debug( "Nombre inicial: " + factura.getNombre() );
updateObject( factory, factura );
factura = queryObject( factory, id );
_logger.debug( "Nombre final: " + factura.getNombre() );
int max = findMaxKey( factory );
_logger.debug( "max key: " + max );
...
}
El método findMaxKey() realiza la consulta de un dato utilizando la interfase org.hibernate.Query. Esta interfase permite construir consultas hacia la base de datos utilizando HQL. Las búsquedas pueden regresar colecciones completas de objetos, o valores individuales; en este caso se regresa un valor entero simple:
![]() |
El listado completo de esta funcionalidad es el siguiente:
private int findMaxKey( SessionFactory factory )
{
Session session = factory.openSession();
try
{
Query query = session.createQuery
( "SELECT MAX( factura.id ) FROM FacturaDTO factura" );
List> results = query.list();
return ( Integer )results.get( 0 );
}
finally
{ session.close(); }
}
En este caso se lee el valor máximo de la propiedad id, y se regresa. De la misma manera que con Criteria, este método puede reescribirse para hacer uso de uniqueResult():
private int findMaxKey( SessionFactory factory )
{
Session session = factory.openSession();
try
{
Query query = session.createQuery
( "SELECT MAX( factura.id ) FROM FacturaDTO factura" );
return ( Integer )query.uniqueResult();
}
finally
{ session.close(); }
}
HQL es un lenguaje de consultas bastante poderoso, y una discusión a fondo del mismo supera el alcance de este documento; sin embargo, será presentado a detalle en otros tutoriales posteriores.
Borrado de Datos
Finalmente, se realiza el borrado del registro que fue insertado; el listado completo del método execute() es el siguiente:
/**
* Método de ejecución de altas, bajas, cambios y consultas.
*/
private void execute() throws Exception
{
SessionFactory factory = createSessionFactory();
int id = insertObject( factory );
FacturaDTO factura = getObject( factory, id );
_logger.debug( "Nombre inicial: " + factura.getNombre() );
updateObject( factory, factura );
factura = queryObject( factory, id );
_logger.debug( "Nombre final: " + factura.getNombre() );
int max = findMaxKey( factory );
_logger.debug( "max key: " + max );
deleteObject( factory, id );
}
El método deleteObject() realiza el borrado del registro utilizando las facilidades de borrado presentes en HQL, y disponibles a través de org.hibernate.Query. En ocasiones es conveniente realizar actualizaciones y borrados a través de este tipo de facilidades:
![]() |
El listado completo de esta funcionalidad es el siguiente:
private void deleteObject( SessionFactory factory, int id )
{
Session session = factory.openSession();
try
{ deleteObject( session, id ); }
finally
{ session.close(); }
}
private void deleteObject( Session session, Integer pk )
{
boolean commit = false;
Transaction transaction = session.beginTransaction();
try
{
Query query = session.createQuery
( "DELETE FROM FacturaDTO factura WHERE factura.id = :id" );
query.setInteger( "id", pk );
query.executeUpdate();
commit = true;
}
finally
{
if( commit )
transaction.commit();
else
transaction.rollback();
}
}
Nótese que el borrado se ejecuta en el contexto de una transacción, y se define el borrado a través del código HQL "DELETE FROM FacturaDTO factura WHERE factura.id = :id". En este código, “:id” representa un parámetro variable que se establece en la siguiente línea de código a través del método setInteger(). El borrado es enviado a la base de datos a través del método executeUpdate() de la interfase org.hibernate.Query.
Una manera alternativa de realizar borrados es por medio del método delete() de la interfase org.hibernate.Session.
![]() |
En este caso, primero se requiere realizar una consulta del dato, y posteriormente aplicar este método para borrarlo. El segundo método deleteObject() en el listado anterior puede ser reescrito de la siguiente forma de acuerdo a esta aproximación:
private void deleteObject( Session session, Integer pk )
{
boolean commit = false;
Transaction transaction = session.beginTransaction();
try
{
FacturaDTO factura =
( FacturaDTO )session.load( FacturaDTO.class, pk );
session.delete( factura );
commit = true;
}
finally
{
if( commit )
transaction.commit();
else
transaction.rollback();
}
}
Conclusión
De esta manera finaliza la presentación de un programa Java SE que realiza un mapeo básico con Hibernate, y que implementa las operaciones básicas de inserción, búsqueda, actualización y borrado sobre registros, haciendo manejo manual de conexiones y transacciones.
}Los siguientes tutoriales en esta serie presentarán manejo más sofisticado de servicios, con el fin de simplificar el código referente al manejo de conexiones y transacciones; y poder trabajar fragmentos de este código de forma declarativa. También se presentarán mapeos más avanzados, y relaciones entre entidades.
Regresar
Derechos Reservados de este Material:
Atribución-No Comercial-Licenciamiento Recíproco 2.5 México
http://creativecommons.org/licenses/by-nc-sa/2.5/mx/
Eres libre de: a) copiar, distribuir y comunicar públicamente la obra, b) hacer obras derivadas. Bajo las condiciones siguientes: a) Atribución. Debes reconocer la autoría de la obra en los términos especificados por el propio autor o licenciante. b) No comercial. No puedes utilizar esta obra para fines comerciales. c) Licenciamiento Recíproco. Si alteras, transformas o creas una obra a partir de esta obra, solo podrás distribuir la obra resultante bajo una licencia igual a ésta.
Al reutilizar o distribuir la obra, tiene que dejar bien claro los términos de la licencia de esta obra. Alguna de estas condiciones puede no aplicarse si se obtiene el permiso del titular de los derechos de autor. Nada en esta licencia menoscaba o restringe los derechos morales del autor.
Java and all Java-based trademarks and logos are trademarks of Sun Microsystems in the United States and/or other countries.
Other products and services are trademarks of their respective owners.
|

























