Empiece con Hibernate

Es bueno comprender la necesidad del mapeo relacional / de objetos (ORM) en las aplicaciones Java, pero probablemente esté ansioso por ver a Hibernate en acción. Comenzaremos mostrándote un ejemplo simple que demuestra algo de su poder.

Como probablemente sepa, es tradicional que un libro de programación comience con un ejemplo de "Hola mundo". En este capítulo, seguimos esa tradición al presentar Hibernate con un programa relativamente simple "Hola mundo". Sin embargo, simplemente imprimir un mensaje en una ventana de la consola no será suficiente para demostrar realmente Hibernate. En su lugar, nuestro programa almacenará los objetos recién creados en la base de datos, los actualizará y realizará consultas para recuperarlos de la base de datos.

Además del ejemplo canónico de "Hello World", presentamos las API principales de Hibernate y damos detalles para una configuración básica.

"Hola mundo" con Hibernate

Las aplicaciones de Hibernate definen clases persistentes que se "asignan" a las tablas de la base de datos. Nuestro ejemplo de "Hola mundo" consta de una clase y un archivo de mapeo. Veamos cómo se ve una clase persistente simple, cómo se especifica el mapeo y algunas de las cosas que podemos hacer con instancias de la clase persistente usando Hibernate.

El objetivo de nuestra aplicación de muestra es almacenar mensajes en una base de datos y recuperarlos para su visualización. La aplicación tiene una clase persistente simple Message, que representa estos mensajes imprimibles. Nuestra Messageclase se muestra en el Listado 1.

Listado 1. Message.java: una clase persistente simple

paquete hola; Mensaje de clase pública {identificación larga privada; texto de cadena privada; mensaje privado nextMessage; Mensaje privado () {} Mensaje público (Texto de cadena) {this.text = texto; } public Long getId () {id de retorno; } setId vacío privado (ID largo) {this.id = id; } public String getText () {texto de retorno; } public void setText (String text) {this.text = texto; } mensaje público getNextMessage () {return nextMessage; } public void setNextMessage (Mensaje nextMessage) {this.nextMessage = nextMessage; }}

Nuestra Messageclase tiene tres atributos: el atributo identificador, el texto del mensaje y una referencia a otro Message. El atributo de identificador permite que la aplicación acceda a la identidad de la base de datos (el valor de clave principal) de un objeto persistente. Si dos instancias de Messagetienen el mismo valor de identificador, representan la misma fila en la base de datos. Hemos elegido Longel tipo de atributo de nuestro identificador, pero no es un requisito. Hibernate permite prácticamente cualquier cosa para el tipo de identificador, como verá más adelante.

Es posible que haya notado que todos los atributos de la Messageclase tienen métodos de acceso a propiedades de estilo JavaBean. La clase también tiene un constructor sin parámetros. Las clases persistentes que usamos en nuestros ejemplos casi siempre se verán así.

Las instancias de la Messageclase pueden ser administrados (hecho persistente) por Hibernate, pero no tiene que ser. Dado que el Messageobjeto no implementa ninguna clase o interfaz específica de Hibernate, podemos usarlo como cualquier otra clase de Java:

Mensaje mensaje = nuevo mensaje ("Hola mundo"); System.out.println (message.getText ());

Este fragmento de código hace exactamente lo que esperamos de las aplicaciones "Hello World": imprime "Hello World"en la consola. Puede parecer que estamos tratando de ser lindos aquí; de hecho, estamos demostrando una característica importante que distingue a Hibernate de algunas otras soluciones de persistencia, como los beans de entidad EJB (Enterprise JavaBean). Nuestra clase persistente se puede utilizar en cualquier contexto de ejecución; no se necesita un contenedor especial. Por supuesto, viniste aquí para ver Hibernate en sí, así que guardemos una nueva Messageen la base de datos:

Sesión sesión = getSessionFactory (). OpenSession (); Transacción tx = session.beginTransaction (); Mensaje mensaje = nuevo mensaje ("Hola mundo"); session.save (mensaje); tx.commit (); session.close ();

Este código llama a Hibernate Sessione Transactioninterfaces. (Llegaremos a esa getSessionFactory()llamada pronto). Da como resultado la ejecución de algo similar al siguiente SQL:

insertar en MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID) valores (1, 'Hola mundo', nulo) 

Espere, la MESSAGE_IDcolumna se está inicializando con un valor extraño. No establecimos la idpropiedad de messageningún lugar, por lo que esperaríamos que lo fuera null, ¿verdad? En realidad, la idpropiedad es especial: es una propiedad de identificador , tiene un valor único generado. (Discutiremos cómo se genera el valor más adelante). MessageHibernate asigna el valor a la instancia cuando save()se llama.

Para este ejemplo, asumimos que la MESSAGEStabla ya existe. Por supuesto, queremos que nuestro programa "Hello World" imprima el mensaje en la consola. Ahora que tenemos un mensaje en la base de datos, estamos listos para demostrarlo. El siguiente ejemplo recupera todos los mensajes de la base de datos, en orden alfabético, y los imprime:

Sesión newSession = getSessionFactory (). OpenSession (); Transacción newTransaction = newSession.beginTransaction (); Lista de mensajes = newSession.find ("de Mensaje como orden m por m.text asc"); System.out.println (messages.size () + "mensaje (s) encontrado (s):"); for (Iterador iter = messages.iterator (); iter.hasNext ();) {Mensaje mensaje = (Mensaje) iter.next (); System.out.println (message.getText ()); } newTransaction.commit (); newSession.close ();

La cadena literal "from Message as m order by m.text asc"es una consulta de Hibernate, expresada en el propio Hibernate Query Language (HQL) orientado a objetos de Hibernate. Esta consulta se traduce internamente al siguiente SQL cuando find()se llama:

seleccione m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID de MESSAGES m orden por m.MESSAGE_TEXT asc 

El fragmento de código imprime:

1 mensaje (s) encontrado (s): Hola mundo 

Si nunca antes ha usado una herramienta ORM como Hibernate, probablemente esperaba ver las declaraciones SQL en algún lugar del código o metadatos. Ellos no están ahí. Todo el SQL se genera en tiempo de ejecución (en realidad, al inicio, para todas las declaraciones SQL reutilizables).

Para permitir que ocurra esta magia, Hibernate necesita más información sobre cómo la Messageclase debe ser persistente. Esta información generalmente se proporciona en un documento de mapeo XML . El documento de mapeo define, entre otras cosas, cómo se Messageasignan las propiedades de la clase a las columnas de la MESSAGEStabla. Veamos el documento de mapeo en el Listado 2.

Listado 2. Un mapeo XML de Hibernate simple


  

El documento de mapeo le dice a Hibernate que la Messageclase debe persistir en la MESSAGEStabla, que la propiedad del identificador se asigna a una columna nombrada MESSAGE_ID, que la propiedad de texto se asigna a una columna nombrada MESSAGE_TEXTy que la propiedad nombrada nextMessagees una asociación con muchos a uno. multiplicidad que se asigna a una columna nombrada NEXT_MESSAGE_ID. (No se preocupe por los otros detalles por ahora).

Como puede ver, el documento XML no es difícil de entender. Puede escribirlo y mantenerlo fácilmente a mano. Cualquiera sea el método que elija, Hibernate tiene suficiente información para generar completamente todas las declaraciones SQL que serían necesarias para insertar, actualizar, eliminar y recuperar instancias de la Messageclase. Ya no necesita escribir estas declaraciones SQL a mano.

Nota
Muchos desarrolladores de Java se han quejado del "infierno de los metadatos" que acompaña al desarrollo de J2EE. Algunos han sugerido un movimiento desde los metadatos XML hacia el código Java simple. Aunque aplaudimos esta sugerencia por algunos problemas, ORM representa un caso en el que los metadatos basados ​​en texto son realmente necesarios. Hibernate tiene valores predeterminados sensibles que minimizan la escritura y una definición de tipo de documento maduro que se puede usar para autocompletar o validación en editores. Incluso puede generar metadatos automáticamente con varias herramientas.

Ahora, cambiemos nuestro primer mensaje y, mientras lo hacemos, creemos un nuevo mensaje asociado con el primero, como se muestra en el Listado 3.

Listado 3. Actualización de un mensaje

Sesión sesión = getSessionFactory (). OpenSession (); Transacción tx = session.beginTransaction (); // 1 es el ID generado del primer mensaje Message message = (Message) session.load (Message.class, new Long (1)); message.setText ("Saludos Earthling"); Mensaje nextMessage = mensaje nuevo ("Llévame con tu líder (por favor)"); message.setNextMessage (nextMessage); tx.commit (); session.close ();

Este código llama a tres declaraciones SQL dentro de la misma transacción:

seleccione m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID de MESSAGES m donde m.MESSAGE_ID = 1 inserte en MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID) valores (2, 'Lléveme con su líder (por favor)', nulo) actualice MESSAGES set MESSAGE_TEXT = 'Saludos Earthling', NEXT_MESSAGE_ID = 2 donde MESSAGE_ID = 1 

Notice how Hibernate detected the modification to the text and nextMessage properties of the first message and automatically updated the database. We've taken advantage of a Hibernate feature called automatic dirty checking: this feature saves us the effort of explicitly asking Hibernate to update the database when we modify the state of an object inside a transaction. Similarly, you can see that the new message was made persistent when a reference was created from the first message. This feature is called cascading save: it saves us the effort of explicitly making the new object persistent by calling save(), as long as it's reachable by an already persistent instance. Also notice that the ordering of the SQL statements isn't the same as the order in which we set property values. Hibernate uses a sophisticated algorithm to determine an efficient ordering that avoids database foreign key constraint violations but is still sufficiently predictable to the user. This feature is called transactional write-behind.

If we run "Hello World" again, it prints:

2 message(s) found: Greetings Earthling Take me to your leader (please) 

This is as far as we'll take the "Hello World" application. Now that we finally have some code under our belt, we'll take a step back and present an overview of Hibernate's main APIs.

Understanding the architecture

The programming interfaces are the first thing you have to learn about Hibernate in order to use it in the persistence layer of your application. A major objective of API design is to keep the interfaces between software components as narrow as possible. In practice, however, ORM APIs aren't especially small. Don't worry, though; you don't have to understand all the Hibernate interfaces at once. The figure below illustrates the roles of the most important Hibernate interfaces in the business and persistence layers.

We show the business layer above the persistence layer, since the business layer acts as a client of the persistence layer in a traditionally layered application. Note that some simple applications might not cleanly separate business logic from persistence logic; that's okay—it merely simplifies the diagram.

The Hibernate interfaces shown in the figure above may be approximately classified as follows:

  • Interfaces called by applications to perform basic CRUD (create/read/update/delete) and querying operations. These interfaces are the main point of dependency of application business/control logic on Hibernate. They include Session, Transaction, and Query.
  • Interfaces called by application infrastructure code to configure Hibernate, most importantly, the Configuration class.
  • Callback interfaces that allow the application to react to events occurring inside Hibernate, such as Interceptor, Lifecycle, and Validatable.
  • Interfaces that allow extension of Hibernate's powerful mapping functionality, such as UserType, CompositeUserType, and IdentifierGenerator. These interfaces are implemented by application infrastructure code (if necessary).

Hibernate makes use of existing Java APIs, including JDBC (Java Database Connectivity), Java Transaction API (JTA), and Java Naming and Directory Interface (JNDI). JDBC provides a rudimentary level of abstraction of functionality common to relational databases, allowing almost any database with a JDBC driver to be supported by Hibernate. JNDI and JTA allow Hibernate to be integrated with J2EE application servers.

En esta sección, no cubrimos la semántica detallada de los métodos API de Hibernate, solo el rol de cada una de las interfaces primarias. Puede encontrar la mayoría de estas interfaces en el paquete net.sf.hibernate. Echemos un vistazo breve a cada interfaz.

Las interfaces centrales

Las cinco interfaces principales se utilizan en casi todas las aplicaciones de Hibernate. Con estas interfaces, puede almacenar y recuperar objetos persistentes y controlar transacciones.

Interfaz de sesión