Hibernate.orgCommunity Documentation
Hibernate es una solución completa de mapeo objeto/relacional que no sólo proteje al desarrollador de los detalles del sistema de administración de la base datos subyacente, sino que además ofrece administración de estado de objetos. Contrario a la administración de declaraciones
SQL en capas comunes de persistencia JDBC/SQL, esta es una vista natural orientada a objetos de la persistencia en aplicaciones Java.
En otras palabras, los desarrolladores de aplicaciones de Hibernate siempre deben pensar en el estado de sus objetos, y no necesariamente en la ejecución de declaraciones SQL. Hibernate se ocupa de esto y es sólamente relevante para el desarrollador de la aplicación al afinar el rendimiento del sistema.
Hibernate define y soporta los siguientes estados de objeto:
Transitorio - un objeto es transitorio si ha sido recién instanciado utilizando el operador new
, y no está asociado a una Session
de Hibernate. No tiene una representación persistente en la base de datos y no se le ha asignado un valor identificador. Las instancias transitorias serán destruídas por el recolector de basura si la aplicación no mantiene más una referencia. Utiliza la Session
de Hibernate para hacer un objeto persistente (y deja que Hibernate se ocupe de las declaraciones SQL que necesitan ejecutarse para esta transición).
Persistente - una instancia persistente tiene una representación en la base de datos y un valor identificador. Puede haber sido guardado o cargado, sin embargo, por definición, se encuentra en el ámbito de una Session
. Hibernate detectará cualquier cambio realizado a un objeto en estado persistente y sincronizará el estado con la base de datos cuando se complete la unidad de trabajo. Los desarrolladores no ejecutan declaraciones UPDATE
manuales, o declaraciones DELETE
cuando un objeto se debe poner como transitorio.
Separado - una instancia separada es un objeto que se ha hecho persistente, pero su Session
ha sido cerrada. La referencia al objeto todavía es válida, por supuesto, y la instancia separada podría incluso ser modificada en este estado. Una instancia separada puede ser re-unida a una nueva Session
más tarde, haciéndola persistente de nuevo (con todas las modificaciones). Este aspecto habilita un modelo de programación para unidades de trabajo de ejecución larga que requieren tiempo-para-pensar por parte del usuario. Las llamamos transaccciones de aplicación, por ejemplo, una unidad de trabajo desde el punto de vista del usuario.
Discutiremos ahora los estados y transiciones de estados (y los métodos de Hibernate que disparan una transición) en más detalle.
Las instancias recién instanciadas de una clase persistente, Hibernate las considera como transitorias. Podemos hacer una instancia transitoria persistente asociándola con una sesión:
DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) sess.save(fritz);
Si Cat
tiene un identificador generado, el identificador es generado y asignado al cat
cuando se llama a save()
. Si Cat
tiene un identificador assigned
, o una clave compuesta, el identificador debe ser asignado a la instancia de cat
antes de llamar a save()
. También puede utilizar persist()
en vez de save()
, con la semántica definida en el borrador de EJB3.
persist()
hace que una instancia transitoria sea persistente. Sin embargo, no garantiza que el valor identificador sea asignado a la instancia persistente inmediatamente, la tarea puede tener lugar durante el vaciado. persist()
también garantiza que no ejecutará una declaración INSERT
si se llama por fuera de los límites de una transacción. Esto es útil en conversaciones largas con un contexto extendido sesión/persistencia.
save()
sí garantiza el retorno de un identificador. Si se tiene que ejecutar un INSERT para obtener el identificador ( por ejemplo, generador "identidad", no "secuencia"), este INSERT tiene lugar inmediatamente sin importar si se encuentra dentro o fuera de una transacción. Esto es problemático en una conversación larga con un contexto extendido sesión/persistencia.
Opcionalmente, puede asignar el identificador utilizando una versión sobrecargada de save()
.
DomesticCat pk = new DomesticCat();
pk.setColor(Color.TABBY);
pk.setSex('F');
pk.setName("PK");
pk.setKittens( new HashSet() );
pk.addKitten(fritz);
sess.save( pk, new Long(1234) );
Si el objeto que hace persistente tiene objetos asociados (por ejemplo, la colección kittens
en el ejemplo anterior), estos objetos pueden ser hechos persistentes en cualquier orden que quiera a menos de que tenga una restricción NOT NULL
sobre una columna clave foránea. Nunca hay riesgo de violar restricciones de clave foránea. Sin embargo, puede que usted viole una restricción NOT NULL
si llama a save()
sobre los objetos en el orden equivocado.
Usualmente no se preocupe de este detalle, pues muy probablemente utilizará la funcionalidad de persistencia transitiva de Hibernate para guardar los objetos asociados automáticamente. Entonces, ni siquiera tienen lugar violaciones de restricciones NOT NULL
- Hibernate se ocupará de todo. Más adelante en este capítulo se discute la persistencia transitiva.
Los métodos load()
de Session
le proporcionan una forma de recuperar una instancia persistente si ya conoce su identificador. load()
toma un objeto clase y carga el estado dentro de una instancia recién instanciada de esa clase, en un estado persistente.
Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// you need to wrap primitive identifiers
long id = 1234;
DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );
Alternativamente, puede cargar estado dentro de una instancia dada:
Cat cat = new DomesticCat();
// load pk's state into cat
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();
Note que load()
lanzará una excepción irrecuperable si no hay una fila correspondiente en la base de datos. Si la clase se mapea con un proxy, load()
sólo retorna un proxy no inicializado y no llamará realmente a la base de datos hasta que invoque un método del proxy. Este comportamiento es muy útil si desea crear una asociación a un objeto sin cargarlo realmente de la base de datos. Además permite que múltiples instancias sean cargadas como un lote si se define batch-size
para el mapeo de la clase.
Si no tiene la certeza de que existe una fila correspondiente, debe utilizar el método get()
, que llama a la base de datos inmediatamente y devuelve nulo si no existe una fila correspondiente.
Cat cat = (Cat) sess.get(Cat.class, id);
if (cat==null) {
cat = new Cat();
sess.save(cat, id);
}
return cat;
Incluso puede cargar un objeto utilizando un SELECT ... FOR UPDATE
de SQL, usando un LockMode
. Consulte la documentación de la API para obtener más información.
Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);
Ninguna instancia asociada o colección contenida es seleccionada para actualizacion - FOR UPDATE
, a menos de que decida especificar lock
o all
como un estilo de cascada para la asociación.
Es posible volver a cargar un objeto y todas sus colecciones en cualquier momento, utilizando el método refresh()
. Esto es útil cuando se usan disparadores de base de datos para inicializar algunas de las propiedades del objeto.
sess.save(cat);
sess.flush(); //force the SQL INSERT
sess.refresh(cat); //re-read the state (after the trigger executes)
How much does Hibernate load from the database and how many SQL SELECT
s will it use? This depends on the fetching strategy. This is explained in Sección 21.1, “Estrategias de recuperación”.
Si no conoce los identificadores de los objetos que está buscando, necesita una consulta. Hibernate soporta un lenguaje de consulta orientado a objetos (HQL) fácil de usar pero potente a la vez. Para la creación de consultas programáticas, Hibernate soporta una funcionalidad sofisticada de consulta de Criteria y Example (QBC y QBE). También puede expresar su consulta en el SQL nativo de su base de datos, con soporte opcional de Hibernate para la conversión del conjunto de resultados a objetos.
Las consultas HQL y SQL nativas son representadas con una instancia de org.hibernate.Query
. Esta interfaz ofrece métodos para ligar parámetros, manejo del conjunto resultado, y para la ejecución de la consulta real. Siempre obtiene una Query
utilizando la Session
actual:
List cats = session.createQuery(
"from Cat as cat where cat.birthdate < ?")
.setDate(0, date)
.list();
List mothers = session.createQuery(
"select mother from Cat as cat join cat.mother as mother where cat.name = ?")
.setString(0, name)
.list();
List kittens = session.createQuery(
"from Cat as cat where cat.mother = ?")
.setEntity(0, pk)
.list();
Cat mother = (Cat) session.createQuery(
"select cat.mother from Cat as cat where cat = ?")
.setEntity(0, izi)
.uniqueResult();]]
Query mothersWithKittens = (Cat) session.createQuery(
"select mother from Cat as mother left join fetch mother.kittens");
Set uniqueMothers = new HashSet(mothersWithKittens.list());
Una consulta se ejecuta usualmente invocando a list()
. El resultado de la consulta será cargado completamente dentro de una colección en memoria. Las instancias de entidad recuperadas por una consulta se encuentran en estado persistente. El método uniqueResult()
ofrece un atajo si sabe que su consulta retornará sólamente un objeto. Las consultas que hacen uso de una recuperación temprana de colecciones usualmente retornan duplicados de los objetos raíz, pero con sus colecciones inicializadas. Puede filtrar estos duplicados a través de un Set
.
Ocasionalmente, puede lograr un mejor rendimiento al ejecutar la consulta utilizando el método iterate()
. Esto ocurrirá usualmente si espera que las instancias reales de entidad retornadas por la consulta estén ya en la sesión o en el caché de segundo nivel. Si todavía no están en caché, iterate()
será más lento que list()
y podría requerir muchas llamadas a la base de datos para una consulta simple, usualmente 1 para la selección inicial que sólamente retorna identificadores y n selecciones adicionales para inicializar las instancias reales.
// fetch ids
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
while ( iter.hasNext() ) {
Qux qux = (Qux) iter.next(); // fetch the object
// something we couldnt express in the query
if ( qux.calculateComplicatedAlgorithm() ) {
// delete the current instance
iter.remove();
// dont need to process the rest
break;
}
}
Las consultas de Hibernate a veces retornan tuplas de objetos. Cada tupla se retorna como un array:
Iterator kittensAndMothers = sess.createQuery(
"select kitten, mother from Cat kitten join kitten.mother mother")
.list()
.iterator();
while ( kittensAndMothers.hasNext() ) {
Object[] tuple = (Object[]) kittensAndMothers.next();
Cat kitten = (Cat) tuple[0];
Cat mother = (Cat) tuple[1];
....
}
Las consultas pueden especificar una propiedad de una clase en la cláusula select
. Pueden incluso llamar a funciones de agregación SQL. Las propiedades o agregaciones son considerados resultados "escalares" y no entidades en estado persistente.
Iterator results = sess.createQuery(
"select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
"group by cat.color")
.list()
.iterator();
while ( results.hasNext() ) {
Object[] row = (Object[]) results.next();
Color type = (Color) row[0];
Date oldest = (Date) row[1];
Integer count = (Integer) row[2];
.....
}
Los métodos en Query
se proporcionan para enlazar valores a los parámetros con nombre o parámetros ?
de estilo JDBC. Al contrario de JDBC, Hibernate numera los parámetros desde cero. Los parámetros con nombre son identificadores de la forma :name
en la cadena de la consulta. Las ventajas de los parámetros con nombre son las siguientes:
los parámetros con nombre son insensibles al orden en que aparecen en la cadena de consulta
pueden aparecer múltiples veces en la misma petición
son auto-documentados
//named parameter (preferred)
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
q.setString("name", "Fritz");
Iterator cats = q.iterate();
//positional parameter
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();
//named parameter list
List names = new ArrayList();
names.add("Izi");
names.add("Fritz");
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
q.setParameterList("namesList", names);
List cats = q.list();
Si necesita especificar enlaces sobre su conjunto de resultados, el número máximo de filas que quiere recuperar y/o la primera fila que quiere recuperar, puede utilizar los métodos de la interfaz Query
:
Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();
Hibernate sabe cómo traducir este límite de consulta al SQL nativo de su DBMS.
Si su controlador JDBC soporta ResultSet
s deslizables, la interfaz Query
se puede utilizar para obtener un objeto ScrollableResults
que permite una navegación flexible de los resultados de consulta.
Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
"order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {
// find the first name on each page of an alphabetical list of cats by name
firstNamesOfPages = new ArrayList();
do {
String name = cats.getString(0);
firstNamesOfPages.add(name);
}
while ( cats.scroll(PAGE_SIZE) );
// Now get the first page of cats
pageOfCats = new ArrayList();
cats.beforeFirst();
int i=0;
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
}
cats.close()
Note que se requiere una conexión de base de datos abierta y un cursor para esta funcionalidad. Utilice setMaxResult()
/setFirstResult()
si necesita la funcionalidad de paginación fuera de línea.
Queries can also be configured as so called named queries using annotations or Hibernate mapping documents. @NamedQuery
and @NamedQueries
can be defined at the class level as seen in Ejemplo 11.1, “Defining a named query using @NamedQuery” . However their definitions are global to the session factory/entity manager factory scope. A named query is defined by its name and the actual query string.
Ejemplo 11.1. Defining a named query using @NamedQuery
@Entity
@NamedQuery(name="night.moreRecentThan", query="select n from Night n where n.date >= :date")
public class Night {
...
}
public class MyDao {
doStuff() {
Query q = s.getNamedQuery("night.moreRecentThan");
q.setDate( "date", aMonthAgo );
List results = q.list();
...
}
...
}
Using a mapping document can be configured using the <query>
node. Remember to use a CDATA
section if your query contains characters that could be interpreted as markup.
Ejemplo 11.2. Defining a named query using <query>
<query name="ByNameAndMaximumWeight"><![CDATA[
from eg.DomesticCat as cat
where cat.name = ?
and cat.weight > ?
] ]></query>
Parameter binding and executing is done programatically as seen in Ejemplo 11.3, “Parameter binding of a named query”.
Ejemplo 11.3. Parameter binding of a named query
Query q = sess.getNamedQuery("ByNameAndMaximumWeight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();
El código real del programa es independiente del lenguaje de consulta utilizado. También puede definir consultas SQL nativas en metadatos, o migrar consultas existentes a Hibernate colocándolas en archivos de mapeo.
Observe además que una declaración de consulta dentro de un elemento <hibernate-mapping>
necesita de un nombre único global para la consulta, mientras que una declaración de consulta dentro de un elemento <class>
se hace única automáticamente al agregar el nombre completamente calificado de la clase. Por ejemplo, eg.Cat.ByNameAndMaximumWeight
.
Un filtro de colección es un tipo especial de consulta que puede ser aplicado a una colección persistente o array. La cadena de consulta puede referirse a this
, lo que quiere decir el elemento de la colección actual.
Collection blackKittens = session.createFilter(
pk.getKittens(),
"where this.color = ?")
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
.list()
);
La colección devuelta es considerada un bag, y es una copia de la colección dada. La colección original no es modificada. Esto es lo opuesto a lo que implica el nombre "filtro", pero es consistente con el comportamiento esperado.
Observe que los filtros no requieren una cláusula from
aunque pueden tener una si se necesita. Los filtros no están limitados a devolver los elementos de colección por sí mismos.
Collection blackKittenMates = session.createFilter(
pk.getKittens(),
"select this.mate where this.color = eg.Color.BLACK.intValue")
.list();
Incluso una consulta de filtro vacío es útil, por ejemplo, para cargar un subconjunto de elementos en una colección enorme:
Collection tenKittens = session.createFilter(
mother.getKittens(), "")
.setFirstResult(0).setMaxResults(10)
.list();
HQL es extremadamente potente pero algunos desarrolladores prefieren construir consultas dinámicamente utilizando una API orientada a objetos, en vez de construir cadenas de consulta. Hibernate brinda una API intuitiva de consulta Criteria
para estos casos:
Criteria crit = session.createCriteria(Cat.class);
crit.add( Restrictions.eq( "color", eg.Color.BLACK ) );
crit.setMaxResults(10);
List cats = crit.list();
The Criteria
and the associated Example
API are discussed in more detail in Capítulo 17, Consultas por criterios.
Puede expresar una consulta en SQL, utilizando createSQLQuery()
y dejar que Hibernate administre el mapeo de los conjuntos de resultados a objetos. Puede llamar en cualquier momento a session.connection()
y utilizar la Connection
JDBC directamente. Si elige usar la API de Hibernate, tiene que encerrar los alias de SQL entre llaves:
List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10")
.addEntity("cat", Cat.class)
.list();
List cats = session.createSQLQuery(
"SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
"FROM CAT {cat} WHERE ROWNUM<10")
.addEntity("cat", Cat.class)
.list()
SQL queries can contain named and positional parameters, just like Hibernate queries. More information about native SQL queries in Hibernate can be found in Capítulo 18, SQL Nativo.
Las instancias persistentes transaccionales (por ejemplo, los objetos cargados, creados o consultados por la Session
) pueden ser manipulados por la aplicación y cualquier cambio al estado persistente será persistido cuando se vacie la Session
. Esto se discute más adelante en este capítulo. No hay necesidad de llamar a un método en particular (como update()
, que tiene un propósito diferente) para hacer persistentes sus modificaciones. De modo que la forma más directa de actualizar el estado de un objeto es cargarlo con load()
y luego manipularlo directamente, mientras la Session
está abierta:
DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
cat.setName("PK");
sess.flush(); // changes to cat are automatically detected and persisted
A veces este modelo de programación es ineficiente pues requiere un SELECT
de SQL para cargar un objeto y un UPDATE
de SQL para hacer persistente su estado actualizado en la misma sesión. Por lo tanto, Hibernate ofrece un enfoque opcional, utilizando instancias separadas.
Muchas aplicaciones necesitan recuperar un objeto en una transacción, enviarla a la capa de UI para su manipulación, y entonces guardar los cambios en una nueva transacción. Las aplicaciones que usan este tipo de enfoque en un entorno de alta concurrencia usualmente utilizan datos versionados para asegurar el aislamiento de la unidad de trabajo "larga".
Hibernate soporta este modelo al proveer re-unión de instancias separadas utilizando los métodos Session.update()
o Session.merge()
:
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);
// in a higher layer of the application
cat.setMate(potentialMate);
// later, in a new session
secondSession.update(cat); // update cat
secondSession.update(mate); // update mate
Si el Cat
con identificador catId
ya hubiera sido cargado por secondSession
cuando la aplicación intentó volver a unirlo, se habría lanzado una excepción.
Utilice update()
si está seguro de que la sesión no tiene una instancia ya persistente con el mismo identificador. Utilice merge()
si quiere fusionar sus modificaciones en cualquier momento sin consideración del estado de la sesión. En otras palabras, update()
usualmente es el primer método que usted llamaría en una sesión actualizada, asegurando que la re-unión de sus instancias separadas es la primera operación que se ejecuta.
The application should individually update()
detached instances that are reachable from the given detached instance only if it wants their state to be updated. This can be automated using transitive persistence. See Sección 11.11, “Persistencia transitiva” for more information.
El método lock()
también le permite a una aplicación reasociar un objeto con una sesión nueva. Sin embargo, la instancia separada no puede haber sido modificada.
//just reassociate:
sess.lock(fritz, LockMode.NONE);
//do a version check, then reassociate:
sess.lock(izi, LockMode.READ);
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
sess.lock(pk, LockMode.UPGRADE);
Note que lock()
se puede utilizar con varios LockMode
s. Consulte la documentación de la API y el capítulo sobre el manejo de transacciones para obtener mayor información. La re-unión no es el único caso de uso para lock()
.
Other models for long units of work are discussed in Sección 13.3, “Control de concurrencia optimista”.
Los usuarios de Hibernate han pedido un método de propósito general que bien guarde una instancia transitoria generando un identificador nuevo, o bien actualice/reúna las instancias separadas asociadas con su identificador actual. El método saveOrUpdate()
implementa esta funcionalidad.
// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catID);
// in a higher tier of the application
Cat mate = new Cat();
cat.setMate(mate);
// later, in a new session
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)
La utilización y semántica de saveOrUpdate()
parece ser confuso para los usuarios nuevos. Primero, en tanto no esté tratando de utilizar instancias de una sesión en otra sesión nueva, no debe necesitar usar update()
, saveOrUpdate()
, o merge()
. Algunas aplicaciones enteras nunca usarán ninguno de estos métodos.
Usualmente update()
o saveOrUpdate()
se utilizan en el siguiente escenario:
la aplicación carga un objeto en la primera sesión
el objeto se pasa a la capa de UI
se realizan algunas modificaciones al objeto
el objeto se pasa abajo de regreso a la capa lógica de negocios
la aplicación hace estas modificaciones persistentes llamando a update()
en una segunda sesión
saveOrUpdate()
hace lo siguiente:
si el objeto ya es persistente en esta sesión, no haga nada
si otro objeto asociado con la sesión tiene el mismo identificador, lance una excepción
si el objeto no tiene ninguna propiedad identificadora, guárdelo llamando a save()
si el identificador del objeto tiene el valor asignado a un objeto recién instanciado, guárdelo llamando a save()
si el objeto está versionado por un <version>
o <timestamp>
, y el valor de la propiedad de versión es el mismo valor asignado a un objeto recién instanciado, guárdelo llamando a save()
de otra manera actualice el objeto llamando a update()
y merge()
es muy diferente:
si existe una instancia persistente con el mismo identificador asignado actualmente con la sesión, copie el estado del objeto dado en la instancia persistente
si no existe ninguna instancia persistente asociada a la sesión actualmente, intente cargarla desde la base de datos, o cree una nueva instancia persistente
la instancia persistente es devuelta
la instancia dada no se asocia a la sesión, permanece separada
Session.delete()
borrará el estado de un objeto de la base de datos. Sin embargo, su aplicación puede tener todavía una referencia a un objeto borrado. Lo mejor es pensar en delete()
al hacer transitoria una instancia persistente.
sess.delete(cat);
Puede borrar objetos en el orden que quiera, sin riesgo de violaciones de restricción de clave foránea. Aún es posible violar una restricción NOT NULL
sobre una columna de clave foránea borrando objetos en un orden erróneo, por ejemplo, si borra el padre, pero olvida borrar los hijos.
A veces es útil poder tomar un grafo de la instancias persistentes y hacerlas persistentes en un almacenamiento de datos diferente, sin regenerar los valores identificadores.
//retrieve a cat from one database
Session session1 = factory1.openSession();
Transaction tx1 = session1.beginTransaction();
Cat cat = session1.get(Cat.class, catId);
tx1.commit();
session1.close();
//reconcile with a second database
Session session2 = factory2.openSession();
Transaction tx2 = session2.beginTransaction();
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
tx2.commit();
session2.close();
El ReplicationMode
determina cómo replicate()
tratará los conflictos con filas existentes en la base de datos:
ReplicationMode.IGNORE
: ignora el objeto cuando existe una fila de la base de datos con el mismo identificador
ReplicationMode.OVERWRITE
: sobrescribe cualquier fila de la base de datos existente con el mismo identificador
ReplicationMode.EXCEPTION
: lanza una excepción si existe una fila de la base de datos con el mismo identificador
ReplicationMode.LATEST_VERSION
: sobrescribe la fila si su número de versión es anterior al número de versión del objeto, o de lo contrario ignora el objeto
Los casos de uso para esta funcionalidad incluyen reconciliar datos ingresados en instancias diferentes de bases de datos, actualizar información de configuración del sistema durante actualizaciones de producto, deshacer cambios realizados durante transacciones no-ACID y más.
A veces la Session
ejecutará las declaraciones SQL necesarias para sincronizar el estado de la conexión JDBC con el estado de los objetos en la menoria. Este proceso, denominado vaciado (flush), ocurre por defecto en los siguientes puntos:
antes de algunas ejecuciones de consulta
desde org.hibernate.Transaction.commit()
desde Session.flush()
Las declaraciones SQL se emiten en el siguiente orden:
todas las inserciones de entidades, en el mismo orden que los objetos correspondientes fueron guardados utilizando Session.save()
todas las actualizaciones de entidades
todas los borrados de colecciones
todos los borrados, actualizaciones e inserciones de elementos de colección
todas las inserciones de colecciones
todos los borrados de entidades, en el mismo orden que los objetos correspondientes fueron borrados usando Session.delete()
Una excepción es que los objetos que utilizan generación de ID native
se insertan cuando se guardan.
Excepto cuando llama explícitamente a flush()
, no hay en absoluto garantías sobre cuándo la Session
ejecuta las llamadas JDBC, sólamente sobre el orden en que se ejecutan. Sin embargo, Hibernate garantiza que los métodos Query.list(..)
nunca devolverán datos desactualizados o incorrectos.
It is possible to change the default behavior so that flush occurs less frequently. The FlushMode
class defines three different modes: only flush at commit time when the Hibernate Transaction
API is used, flush automatically using the explained routine, or never flush unless flush()
is called explicitly. The last mode is useful for long running units of work, where a Session
is kept open and disconnected for a long time (see Sección 13.3.2, “Sesión extendida y versionado automático”).
sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
Cat izi = (Cat) sess.load(Cat.class, id);
izi.setName(iznizi);
// might return stale data
sess.find("from Cat as cat left outer join cat.kittens kitten");
// change to izi is not flushed!
...
tx.commit(); // flush occurs
sess.close();
During flush, an exception might occur (e.g. if a DML operation violates a constraint). Since handling exceptions involves some understanding of Hibernate's transactional behavior, we discuss it in Capítulo 13, Transacciones y concurrencia.
Es absolutamente incómodo guardar, borrar, o reunir objetos individuales, especialmente si trata con un grafo de objetos asociados. Un caso común es una relación padre/hijo. Considere el siguiente ejemplo:
Si los hijos en una relación padre/hijo pudieran ser tipificados en valor (por ejemplo, una colección de direcciones o cadenas), sus ciclos de vida dependerían del padre y no se requeriría ninguna otra acción para el tratamiento apropiado en "cascada" de los cambios de estado. Cuando se guarda el padre, los objetos hijo tipificados en valor también se guardan, cuando se borra el padre, se borran los hijos, etc. Esto funciona incluso para operaciones tales como el retiro de un hijo de la colección. Hibernate detectará esto y ya que los objetos tipificados en valor no pueden tener referencias compartidas entonces borrará el hijo de la base de datos.
Ahora considere el mismo escenario con los objetos padre e hijos siendo entidades, no tipos de valor (por ejemplo, categorías e ítems, o gatos padres e hijos). Las entidades tienen su propio ciclo de vida y soportan referencias compartidas. El eliminar una entidad de una colección no significa que se pueda borrar, y no hay por defecto ningún tratamiento en "cascada" del estado de una entidad a otras entidades asociadas. Hibernate no implementa por defecto la persistencia por alcance.
Para cada operación básica de la sesión de Hibernate - incluyendo persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate()
- existe un estilo de cascada correspondiente. Respectivamente, los estilos de cascada se llaman create, merge, save-update, delete, lock, refresh, evict, replicate
. Si quiere que una operación sea tratada en cascada a lo largo de una asociación, debe indicar eso en el documento de mapeo. Por ejemplo:
<one-to-one name="person" cascade="persist"/>
Los estilos de cascada pueden combinarse:
<one-to-one name="person" cascade="persist,delete,lock"/>
Incluso puede utilizar cascade="all"
para especificar que todas las operaciones deben ser tratadas en cascada a lo largo de la asociación. La cascade="none"
predeterminada especifica que ninguna operación se tratará en cascada.
In case you are using annotatons you probably have noticed the cascade
attribute taking an array of CascadeType
as a value. The cascade concept in JPA is very is similar to the transitive persistence and cascading of operations as described above, but with slightly different semantics and cascading types:
CascadeType.PERSIST
: cascades the persist (create) operation to associated entities persist() is called or if the entity is managed
CascadeType.MERGE
: cascades the merge operation to associated entities if merge() is called or if the entity is managed
CascadeType.REMOVE
: cascades the remove operation to associated entities if delete() is called
CascadeType.REFRESH:
cascades the refresh operation to associated entities if refresh() is called
CascadeType.DETACH:
cascades the detach operation to associated entities if detach() is called
CascadeType.ALL
: all of the above
CascadeType.ALL also covers Hibernate specific operations like save-update, lock etc...
A special cascade style, delete-orphan
, applies only to one-to-many associations, and indicates that the delete()
operation should be applied to any child object that is removed from the association. Using annotations there is no CascadeType.DELETE-ORPHAN
equivalent. Instead you can use the attribute orphanRemoval as seen in
Ejemplo 11.4, “@OneToMany with orphanRemoval”. If an entity is removed from a @OneToMany
collection or an associated entity is dereferenced from a @OneToOne
association, this associated entity can be marked for deletion if orphanRemoval
is set to true.
Ejemplo 11.4. @OneToMany
with orphanRemoval
@Entity
public class Customer {
private Set<Order> orders;
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
public Set<Order> getOrders() { return orders; }
public void setOrders(Set<Order> orders) { this.orders = orders; }
[...]
}
@Entity
public class Order { ... }
Customer customer = em.find(Customer.class, 1l);
Order order = em.find(Order.class, 1l);
customer.getOrders().remove(order); //order will be deleted by cascade
Recomendaciones:
It does not usually make sense to enable cascade on a many-to-one or many-to-many association. In fact the @ManyToOne
and @ManyToMany
don't even offer a orphanRemoval
attribute. Cascading is often useful for one-to-one and one-to-many associations.
If the child object's lifespan is bounded by the lifespan of the parent object, make it a life cycle object by specifying cascade="all,delete-orphan"(
.@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
)
En otro caso, puede que usted no necesite tratamiento en cascada en absoluto. Pero si piensa que va a estar trabajando frecuentemente con padre e hijos juntos en la misma transacción, y quiere ahorrarse algo de escritura en computador, considere el utilizar cascade="persist,merge,save-update"
.
Mapear una asociación (ya sea una asociación monovaluada, o una colección) con cascade="all"
marca la asociación como una relación del estilo padre/hijo en donde guardar/actualizar/borrar (save/update/delete) el padre causa el guardar/actualizar/borrar del hijo o hijos.
Furthermore, a mere reference to a child from a persistent parent will result in save/update of the child. This metaphor is incomplete, however. A child which becomes unreferenced by its parent is not automatically deleted, except in the case of a one-to-many association mapped with cascade="delete-orphan"
. The precise semantics of cascading operations for a parent/child relationship are as follows:
Si un padre pasa a persist()
, se pasan todos los hijos a persist()
Si un padre pasa a merge()
, se pasan todos los hijos a merge()
Si se pasa un padre a save()
, update()
o saveOrUpdate()
, todos los hijos pasan a saveOrUpdate()
Si un hijo transitorio o separado se vuelve referenciado por un padre persistente, le es pasado a saveOrUpdate()
Si se borra un padre, se pasan todos los hijos a delete()
Si un hijo deja de ser referenciado por un padre persistente, no ocurre nada especial - la aplicación debe borrar explícitamente el hijo de ser necesario - a menos que cascade="delete-orphan"
, en cuyo caso se borra el hijo "huérfano".
Finalmente, note que las operaciones en cascadas se pueden aplicar a un grafo de objeto en tiempo de llamada o en tiempo de vaciado. Todas las operaciones, si se encuentran activadas se tratan en cascadas en entidades asociadas alcanzables cuando se ejecuta la operación. Sin embargo, save-upate
y delete-orphan
son transitivos para todas las entidades asociadas alcanzables durante el vaciado de la Session
.
Hibernate requiere de un modelo de meta-nivel muy rico de todas las entidades y tipos de valor. Este modelo puede ser útil para la aplicación misma. Por ejemplo, la aplicación podría utilizar los metadatos de Hibernate para implementar un algoritmo "inteligente" de copia en profundidad que entienda qué objetos deben ser copiados (por ejemplo, tipos de valor mutables) y cuáles no (por ejemplo, tipos de valor inmutables y posiblemente las entidades asociadas).
Hibernate expone los metadatos por medio de las interfaces ClassMetadata
y CollectionMetadata
y la jerarquía Type
. Las instancias de las interfaces de metadatos se pueden obtener de la SessionFactory
.
Cat fritz = ......;
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
Object[] propertyValues = catMeta.getPropertyValues(fritz);
String[] propertyNames = catMeta.getPropertyNames();
Type[] propertyTypes = catMeta.getPropertyTypes();
// get a Map of all properties which are not collections or associations
Map namedValues = new HashMap();
for ( int i=0; i<propertyNames.length; i++ ) {
if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {
namedValues.put( propertyNames[i], propertyValues[i] );
}
}
Copyright © 2004 Red Hat, Inc.