Hibernate.orgCommunity Documentation
Hibernate utiliza una estrategia de recuperación para recuperar los objetos asociados cuando la aplicación necesita navegar la asociación. Las estrategias de recuperación se pueden declarar en los metadatos de mapeo O/R, o se pueden sobrescribir por medio de una HQL particular o una petición Criteria
.
Hibernate3 define las siguientes estrategias de recuperación:
Recuperación por unión (join fetching): Hibernate recupera la instancia asociada o la colección en el mismo SELECT
, utilizando un OUTER JOIN
.
Recuperación por selección (select fetching): se utiliza un segundo SELECT
para recuperar la entidad o colección asocidas. A menos que deshabilite explícitamente la recuperación perezosa especificando lazy="false"
, la segunda selección sólo será ejecutada cuando acceda a la asociación.
Recuperación por subselección (subselect fetching): se utiliza un segundo SELECT
para recuperar las colecciones asociadas de todas las entidades recuperadas en una consulta o recuperación previa. A menos de que deshabilite explícitamente la recuperación perezosa especificando lazy="false"
, esta segunda selección sólo se ejecutará cuando acceda a la asociación.
Recuperación en lote: una estrategia de optimización para la recuperación por selección. Hibernate recupera un lote de instancias de entidad o colecciones en un solo SELECT
, especificando una lista de claves principales o de claves foráneas.
Hibernate también distingue entre:
Recuperación inmediata: una asociación, colección o atributo se recupera inmediatamente cuando se carga el dueño.
Recuperación perezosa de colecciones: una colección se recupera cuando la aplicación invoca una operación sobre esa colección. Este es el valor predeterminado para las colecciones.
Recuperación de colección "extra-perezoza" : se accede a elementos individuales desde la base de datos cuando se necesita. Hibernate intenta no recuperar toda la colección en la memoria a menos de que sea absolutamente necesario. Esto es apropiado para colecciones muy grandes.
Recuperación por proxy: una asociación monovaluada se recupera cuando se invoca un método que no sea el getter del identificador sobre el objeto asociado.
Recuperación "no-proxy" : una asociación monovaluada se recupera cuando se accede a la variable de la instancia. Comparado con la recuperación por proxy, este enfoque es menos perezozo; la asociación se recupera cuando se accede sólamente al identificador. También es más transparente ya que para la aplicación no hay proxies visibles. Este enfoque requiere instrumentación del código byte del tiempo estimado de construcción y se necesita muy raramente.
Recuperación perezosa de atributos: un atributo o una asociación monovaluada se recuperan cuando se accede a la variable de la instancia. Este enfoque requiere instrumentación del código byte en tiempo estimado de construcción y se necesita muy raramente.
Aquí tenemos dos nociones ortogonales: cuándo se recupera la aplicación, y cómo se recupera. Es importante que no las confunda. Utilizamos fetch
para afinar el rendimiento. Podemos usar lazy
para definir un contrato sobre qué datos están siempre disponibles en cualquier instancia separada de una clase en particular.
Por defecto, Hibernate3 usa una recuperación perezosa por selección para colecciones y una recuperación por proxy perezosa para asociaciones monovaluadas. Estas políticas predeterminadas tienen sentido para casi todas las asociaciones en la mayoría de las aplicaciones.
Si configura hibernate.default_batch_fetch_size
, Hibernate utilizará la optimización de recuperación en lotes para recuperación perezosa. Esta optimización también se puede habilitar en un nivel más detallado.
Note que el acceder a una asociación perezosa fuera del contexto de una sesión de Hibernate abierta resultará en una excepción. Por ejemplo:
s = sessions.openSession();
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName")
.setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!
Ya que la colección de permisos no fue inicializada cuando se cerró la Session
, la colección no será capaz de cargar su estado. Hibernate no soporta la inicialización perezosa de objetos separados. La solución es mover el código que lee de la colección a justo antes de que se guarde la transacción.
Opcionalmente puede utilizar una colección no perezosa o asociación, especificando lazy="false"
para el mapeo de asociación. Sin embargo, el propósito de la inicialización perezosa es que se utilice para casi todas las colecciones y asociaciones. ¡Si define demasiadas asociaciones no perezosas en su modelo de objetos, Hibernate recuperará la base de datos entera en toda transacción.
Por otro lado, puede utilizar la recuperación por unión, la cual no es perezosa por naturaleza, en lugar de la recuperación por selección en una transacción en particular. Veremos ahora cómo personalizar la estrategia de recuperación. En Hibernate3, los mecanismos para elegir una estrategia de recuperación son idénticas para las de las asociaciones monovaluadas y las colecciones.
La recuperación por selección (la preestablecida) es extremadamente vulnerable a problemas de selección N+1, de modo que puede que queramos habilitar la recuperación por unión (join fetching) en el documento de mapeo:
<set name="permissions"
fetch="join">
<key column="userId"/>
<one-to-many class="Permission"/>
</set
<many-to-one name="mother" class="Cat" fetch="join"/>
La estrategia de recuperación
definida en el documento de mapeo afecta a:
las recuperaciones por medio de get()
o load()
las recuperaciones que ocurren implícitamente cuando se navega una asociación (recuperación perezosa)
las consultas de Criteria
las consultas HQL si se utiliza la recuperación subselect
Sin importar que estrategia de recuperación utilice, se garantiza que la gráfica no-perezoza definida será cargada en la memoria. Sin embargo, esto puede causar la utilización de varias selecciones inmediatas para ejecutar una consulta HQL en particular.
Usualmente, no utilizamos el documento de mapeo para personalizar la recuperación. En cambio, mantenemos el comportamiento por defecto y lo sobrescribimos para una transacción en particular, utilizando left join fetch
en HQL. Esto le dice a Hibernate que recupere la asociación tempranamente en la primera selección, usando una unión externa. En la API de consulta de Criteria
, usted utilizaría setFetchMode(FetchMode.JOIN)
.
Si quiere cambiar la estrategia de recuperación utilizada por get()
o load()
; utilice una consulta Criteria
. Por ejemplo:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
Esto es el equivalente de Hibernate de lo que otras soluciones ORM denominan un "plan de recuperación".
Un enfoque completamente diferente de evitar problemas con selecciones N+1 es usar el caché de segundo nivel.
La recuperación perezosa de colecciones está implementada utilizando la implementación de colecciones persistentes propia de Hibernate. Sin embargo, se necesita un mecanismo diferente para un comportamiento perezoso en las asociaciones de un sólo extremo. La entidad destino de la asociación se debe tratar con proxies. Hibernate implementa proxies de inicialización perezosa para objetos persistentes utilizando la mejora del código byte en tiempo de ejecución por medio de la biblioteca CGLIB).
En el arranque, Hibernate3 genera proxies por defecto para todas las clases persistentes y los usa para habilitar la recuperación perezosa de asociaciones muchos-a-uno
y uno-a-uno
.
El archivo de mapeo puede declarar una interfaz a utilizar como interfaz de proxy para esa clase, con el atributo proxy
. Por defecto, Hibernate usa una subclase de la clase. La clase tratada con proxies debe implementar un constructor por defecto con al menos visibilidad de paquete. Recomendamos este constructor para todas las clases persistentes.
Hay problemas potenciales que se deben tener en cuenta al extender este enfoque a las clases polimórficas. Por ejemplo:
<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat">
.....
</subclass>
</class>
Primero, las instancias de Cat
nunca serán objeto de un cast a DomesticCat
, incluso aunque la instancia subyacente sea una instancia de DomesticCat
:
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db)
if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy
DomesticCat dc = (DomesticCat) cat; // Error!
....
}
Segundo, es posible romper el proxy ==
:
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
System.out.println(cat==dc); // false
Sin embargo, la situación no es en absoluto tan mala como parece. Aunque tenemos ahora dos referencias a objetos proxy diferentes, la instancia subyacente será aún el mismo objeto:
cat.setWeight(11.0); // hit the db to initialize the proxy
System.out.println( dc.getWeight() ); // 11.0
Tercero, no puede usar un proxy CGLIB para una clase final
o una clase con algún método final
.
Finalmente, si su objeto persistente adquiere cualquier recurso bajo instanciación (por ejemplo, en inicializadores o constructores por defecto), entonces esos recursos serán adquiridos también por el proxy. La clase del proxy es una subclase real de la clase persistente.
Estos problemas se deben a limitaciones fundamentales en el modelo de herencia única de Java. Si desea evitar estos problemas cada una de sus clases persistentes deben implementar una interfaz que declare sus métodos de negocio. Debe especificar estas interfaces en el archivo de mapeo en donde CatImpl
implementa la interfaz Cat
y DomesticCatImpl
implementa la interfaz DomesticCat
. Por ejemplo:
<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>
Entonces los proxies para las instancias de Cat
y DomesticCat
pueden ser retornadas por load()
o iterate()
.
Cat cat = (Cat) session.load(CatImpl.class, catid);
Iterator iter = session.createQuery("from CatImpl as cat where cat.name='fritz'").iterate();
Cat fritz = (Cat) iter.next();
list()
usualmente no retorna proxies.
Las relaciones también son inicializadas perezosamente. Esto significa que debe declarar cualquier propiedad como de tipo Cat
, no CatImpl
.
Ciertas operaciones no requieren inicialización de proxies:
equals()
, si la clase persistente no sobrescribe equals()
hashCode()
, si la clase persistente no sobrescribe hashCode()
El método getter del identificador
Hibernate detectará las clases persistentes que sobrescriban equals()
o hashCode()
.
Al escoger lazy="no-proxy"
en vez del lazy="proxy"
predeterminado, podemos evitar los problemas asociados con conversión de tipos (typecasting). Sin embargo, requiere la instrumentación de código byte en tiempo estimado de construcción y todas las operaciones resultarán en una inicialización de proxies inmediata.
Hibernate lanzará una LazyInitializationException
si se accede a una colección o proxy sin acceder fuera del ámbito de la Session
, por ejemplo, cuando la entidad que posee la colección o que tiene la referencia al proxy esté en el estado separado.
A veces es necesario inicializar un proxy o una colección antes de cerrar la Session
. Puede forzar la inicialización llamando a cat.getSex()
o cat.getKittens().size()
, por ejemplo. Sin embargo, esto puede ser confuso para los lectores del código y no es conveniente para el código genérico.
Los métodos estáticos Hibernate.initialize()
y Hibernate.isInitialized()
proporcionan a la aplicación una forma conveniente de trabajar con colecciones o proxies inicializados perezosamente. Hibernate.initialize(cat)
forzará la inicialización de un proxy, cat
, en tanto su Session
esté todavía abierta. Hibernate.initialize( cat.getKittens() )
tiene un efecto similar para la colección de gatitos.
Otra opción es mantener la Session
abierta hasta que todas las colecciones y proxies necesarios hayan sido cargados. En algunas arquitecturas de aplicación, particularmente en aquellas donde el código que accede a los datos usando Hibernate, y el código que los utiliza están en capas de aplicación diferentes o procesos físicos diferentes, puede ser un problema asegurar que la Session
esté abierta cuando se inicializa una colección. Existen dos formas básicas para abordar este tema:
En una aplicación basada en la web se puede utilizar un filtro de servlets para cerrar la Session
sólamente al final de una petición del usuario, una vez que la entrega de la vista esté completa (el patrón sesión abierta en vista (open session in view)). Por supuesto, estos sitios requieren una fuerte demanda de corrección del manejo de excepciones de la infraestructura de su aplicación. Es de una vital importancia que la Session
esté cerrada y la transacción terminada antes de volver al usuario, incluso cuando ocurra una excepción durante le entrega de la vista. Refiérase a la Wiki de Hibernate para ver ejemplos de este patrón "Open Session in View" (sesión abierta en vista).
En una aplicación con una capa de negocios separada, la lógica empresarial tiene que "preparar" todas las colecciones que la capa web va a necesitar antes de retornar. Esto significa que la capa empresarial debe cargar todos los datos y devolver a la capa web/presentación todos los datos ya inicializados que se requieran para un caso de uso en particular. Usualmente, la aplicación llama a Hibernate.initialize()
para cada colección que se necesitará en la capa web (esta llamada debe tener lugar antes de que se cierre la sesión) o recupera la colección tempranamente utilizando una consulta de Hibernate con una cláusula FETCH
o una FetchMode.JOIN
en Criteria
. Usualmente, esto es más fácil si adopta el patrón Comando en vez de una Fachada de Sesión.
También puede adjuntar un objeto cargado previamente a una nueva Session
con merge()
o lock()
antes de acceder a colecciones no inicializadas u otros proxies. Hibernate no y ciertamente no debe hacer esto automáticamente ya que introduciría semánticas de transacción improvisadas.
A veces no quiere inicializar una colección grande, pero todavía necesita alguna información sobre ella como por ejemplo, su tamaño o un subconjunto de los datos.
Puede utilizar un filtro de colecciones para obtener el tamaño de una colección sin inicializarla:
( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()
El método createFilter()
también se utiliza para recuperar eficientemente subconjuntos de una colección sin necesidad de inicializar toda la colección:
s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();
Usando la recuperación por lotes, Hibernate puede cargar varios proxies sin inicializar si se accede a un proxy. La recuperación en lotes es una optimización de la estrategia de recuperación por selección perezosa. Hay dos formas en que puede configurar la recuperación en lotes: a nivel de la clase y a nivel de colección.
La recuperación en lotes para clases/entidades es más fácil de entender. Considere el siguiente ejemplo: en tiempo de ejecución tiene 25 instancias de Cat
cargadas en una Session
y cada Cat
tiene una referencia a su owner
, una Person
. La clase Person
está mapeada con un proxy, lazy="true"
. Si ahora itera a través de todos los cats y llama a getOwner()
para cada uno, Hibernate por defecto, ejecutará 25 declaraciones SELECT
para recuperar los dueños proxies. Puede afinar este comportamiento especificando un batch-size
en el mapeo de Person
:
<class name="Person" batch-size="10">...</class>
Hibernate ahora ejecutará sólamente tres consultas: el patrón es 10, 10, 5.
También puede habilitar la recuperación en lotes para colecciones. Por ejemplo, si cada Person
tiene una colección perezosa de Cat
s y hay 10 personas actualmente cargadas en la Session
, iterar a través de las 10 personas generará 10 SELECT
s, uno para cada llamada a getCats()
. Si habilita la recuperación en lotes para la colección de cats
en el mapeo de Person
, Hibernate puede recuperar por adelantado las colecciones:
<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>
Con un batch-size
de 3, Hibernate cargará las colecciones 3, 3, 3, 1 en cuatro SELECT
s. Una vez más, el valor del atributo depende del número esperado de colecciones sin inicializar en una Session
en particular.
La recuperación de colecciones en lotes es particularmente útil si tiene un árbol anidado de ítems, por ejemplo, el típico patrón de cuenta de materiales. Sin embargo, un conjunto anidado o una ruta materializada podría ser una mejor opción para árboles que sean de lectura en la mayoría de los casos.
Si una colección perezosa o proxy monovaluado tiene que ser recuperado, Hibernate los carga a todos, volviendo a ejecutar la consulta original en una subselección. Esto funciona de la misma forma que la recuperación en lotes, sin carga fragmentaria.
Another way to affect the fetching strategy for loading associated objects is through something called a fetch profile, which is a named configuration associated with the org.hibernate.SessionFactory
but enabled, by name, on the org.hibernate.Session
. Once enabled on a org.hibernate.Session
, the fetch profile will be in affect for that org.hibernate.Session
until it is explicitly disabled.
So what does that mean? Well lets explain that by way of an example which show the different available approaches to configure a fetch profile:
Ejemplo 21.1. Specifying a fetch profile using @FetchProfile
@Entity
@FetchProfile(name = "customer-with-orders", fetchOverrides = {
@FetchProfile.FetchOverride(entity = Customer.class, association = "orders", mode = FetchMode.JOIN)
})
public class Customer {
@Id
@GeneratedValue
private long id;
private String name;
private long customerNumber;
@OneToMany
private Set<Order> orders;
// standard getter/setter
...
}
Ejemplo 21.2. Specifying a fetch profile using <fetch-profile>
outside <class>
node
<hibernate-mapping>
<class name="Customer">
...
<set name="orders" inverse="true">
<key column="cust_id"/>
<one-to-many class="Order"/>
</set>
</class>
<class name="Order">
...
</class>
<fetch-profile name="customer-with-orders">
<fetch entity="Customer" association="orders" style="join"/>
</fetch-profile>
</hibernate-mapping>
Ejemplo 21.3. Specifying a fetch profile using <fetch-profile>
inside <class>
node
<hibernate-mapping>
<class name="Customer">
...
<set name="orders" inverse="true">
<key column="cust_id"/>
<one-to-many class="Order"/>
</set>
<fetch-profile name="customer-with-orders">
<fetch association="orders" style="join"/>
</fetch-profile>
</class>
<class name="Order">
...
</class>
</hibernate-mapping>
Now normally when you get a reference to a particular customer, that customer's set of orders will be lazy meaning we will not yet have loaded those orders from the database. Normally this is a good thing. Now lets say that you have a certain use case where it is more efficient to load the customer and their orders together. One way certainly is to use "dynamic fetching" strategies via an HQL or criteria queries. But another option is to use a fetch profile to achieve that. The following code will load both the customer andtheir orders:
Ejemplo 21.4. Activating a fetch profile for a given Session
Session session = ...;
session.enableFetchProfile( "customer-with-orders" ); // name matches from mapping
Customer customer = (Customer) session.get( Customer.class, customerId );
@FetchProfile
definitions are global and it does not matter on which class you place them. You can place the @FetchProfile
annotation either onto a class or package (package-info.java). In order to define multiple fetch profiles for the same class or package @FetchProfiles
can be used.
Actualmente solo se soportan los perfiles de recuperación de estilo unido pero se planear soportar estilos adicionales. Consulte HHH-3414 para obtener mayores detalles.
Hibernate3 soporta la recuperación perezosa de propiedades individuales. Esta técnica de optimización también es conocida como grupos de recuperación (fetch groups). Por favor, note que éste es principalmente un aspecto de marketing, ya que en la práctica, optimizar las lecturas de filas es mucho más importante que la optimización de lectura de columnas. Sin embargo, cargar sólo algunas propiedades de una clase podría ser útil en casos extremos. Por ejemplo, cuando las tablas heredadas tienen cientos de columnas y el modelo de datos no puede ser mejorado.
Para habilitar la carga perezosa de propiedades, establezca el atributo lazy
en sus mapeos de propiedades:
<class name="Document">
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" length="50"/>
<property name="summary" not-null="true" length="200" lazy="true"/>
<property name="text" not-null="true" length="2000" lazy="true"/>
</class>
La carga perezosa de propiedades requiere la instrumentación del código byte en tiempo de construcción. Si sus clases persistentes no se mejoran, Hibernate ignorará la configuración perezosa de propiedades y retornará a la recuperación inmediata.
Para la instrumentación del código byte, utilice la siguiente tarea Ant:
<target name="instrument" depends="compile">
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
<classpath path="${jar.path}"/>
<classpath path="${classes.dir}"/>
<classpath refid="lib.class.path"/>
</taskdef>
<instrument verbose="true">
<fileset dir="${testclasses.dir}/org/hibernate/auction/model">
<include name="*.class"/>
</fileset>
</instrument>
</target>
Una forma diferente de evitar lecturas innecesarias de columnas, al menos para transacciones de sólo lectura es utilizar las funcionalidades de proyección de consultas HQL o Criteria. Esto evita la necesidad de procesar el código byte en tiempo de construcción y ciertamente es la solución preferida.
Puede forzar la usual recuperación temprana de propiedades utilizando fetch all properties
en HQL.
Una Session
de Hibernate es un caché de datos persistentes a nivel de transacción. Es posible configurar un clúster o caché a nivel de MVJ (a nivel de SessionFactory
) sobre una base de clase-por-clase o colección-por-colección. Incluso puede enchufar un caché en clúster. Tenga en cuenta de que los cachés nunca están al tanto de los cambios que otra aplicación haya realizado al almacén persistente. Sin embargo, se pueden configurar para que los datos en caché expiren regularmente.
You have the option to tell Hibernate which caching implementation to use by specifying the name of a class that implements org.hibernate.cache.CacheProvider
using the property hibernate.cache.provider_class
. Hibernate is bundled with a number of built-in integrations with the open-source cache providers that are listed in Tabla 21.1, “Proveedores de Caché”. You can also implement your own and plug it in as outlined above. Note that versions prior to Hibernate 3.2 use EhCache as the default cache provider.
Tabla 21.1. Proveedores de Caché
Caché | Clase del Provedor | Tipo | Clúster Seguro | Caché de Consultas Soportado |
---|---|---|---|---|
Hashtable (no fue pensado para la utilización en producción) | org.hibernate.cache.HashtableCacheProvider | memoria | yes | |
EHCache | org.hibernate.cache.EhCacheProvider | memory, disk, transactional, clustered | yes | yes |
OSCache | org.hibernate.cache.OSCacheProvider | memoria, disco | yes | |
SwarmCache | org.hibernate.cache.SwarmCacheProvider | en clúster (ip multicast) | sí (invalidación en clúster) | |
JBoss Cache 1.x | org.hibernate.cache.TreeCacheProvider | en clúster (ip multicast), transaccional | sí (replicación) | sí (requiere sincronización de reloj) |
JBoss Cache 2 | org.hibernate.cache.jbc.JBossCacheRegionFactory | en clúster (ip multicast), transaccional | sí (replicación o invalidación) | sí (requiere sincronización de reloj) |
As we have done in previous chapters we are looking at the two different possibiltites to configure caching. First configuration via annotations and then via Hibernate mapping files.
By default, entities are not part of the second level cache and we recommend you to stick to this setting. However, you can override this by setting the shared-cache-mode
element in your persistence.xml
file or by using the javax.persistence.sharedCache.mode
property in your configuration. The following values are possible:
ENABLE_SELECTIVE
(Default and recommended value): entities are not cached unless explicitly marked as cacheable.
DISABLE_SELECTIVE
: entities are cached unless explicitly marked as not cacheable.
ALL
: all entities are always cached even if marked as non cacheable.
NONE
: no entity are cached even if marked as cacheable. This option can make sense to disable second-level cache altogether.
The cache concurrency strategy used by default can be set globaly via the hibernate.cache.default_cache_concurrency_strategy
configuration property. The values for this property are:
read-only
read-write
nonstrict-read-write
transactional
It is recommended to define the cache concurrency strategy per entity rather than using a global one. Use the @org.hibernate.annotations.Cache
annotation for that.
Ejemplo 21.5. Definition of cache concurrency strategy via @Cache
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Forest { ... }
Hibernate also let's you cache the content of a collection or the identifiers if the collection contains other entities. Use the @Cache
annotation on the collection property.
Ejemplo 21.6. Caching collections using annotations
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public SortedSet<Ticket> getTickets() {
return tickets;
}
Ejemplo 21.7, “@Cache annotation with attributes”shows the @org.hibernate.annotations.Cache
annotations with its attributes. It allows you to define the caching strategy and region of a given second level cache.
Ejemplo 21.7. @Cache
annotation with attributes
@Cache( CacheConcurrencyStrategy usage(); String reg
ion() default ""; String inc
lude() default "all"; )
usage: the given cache concurrency strategy (NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL) | |
region (optional): the cache region (default to the fqcn of the class or the fq role name of the collection) | |
|
Let's now take a look at Hibernate mapping files. There the <cache>
element of a class or collection mapping is used to configure the second level cache. Looking at Ejemplo 21.8, “The Hibernate <cache> mapping element” the parallels to anotations is obvious.
Ejemplo 21.8. The Hibernate <cache>
mapping element
<cache usage="transactional|read-write|nonstrict-read-write|read-only" region="Re
gionName" include="a
ll|non-lazy" />
| |
| |
|
Alternatively to <cache>
, you can use <class-cache>
and <collection-cache>
elements in hibernate.cfg.xml
.
Let's now have a closer look at the different usage strategies
Si su aplicación necesita leer pero no modificar las instancias de una clase persistente, puede utilizar un caché read-only
(de sólo lectura). Esta es la mejor estrategia y la más simple. Incluso es totalmente segura para utilizar en un clúster.
Si la aplicación necesita actualizar datos, un caché read-write
puede ser apropiado. Esta estrategia de caché nunca se debe utilizar si se requiere un nivel de aislamiento serializable de transacciones. Si el caché se usa en un entorno JTA, tiene que especificar la propiedad hibernate.transaction.manager_lookup_class
, mencionando una estrategia para obtener el TransactionManager
de JTA. En otros entornos, debe asegurarse de que la transacción esté completada cuando se llame a Session.close()
o Session.disconnect()
. Si desea utilizar esta estrategia en un clúster, debe asegurarse de que la implementación de caché subyacente soporta bloqueos. Los provedores de caché internos no soportan bloqueos.
Si la aplicación necesita sólo ocasionalmente actualizar datos (es decir, es extremadamente improbable que dos transacciones intenten actualizar el mismo ítem simultáneamente) y no se requiere de un aislamiento de transacciones estricto, un caché nonstrict-read-write
podría ser apropiado. Si se utiliza el caché en un entorno JTA, tiene que especificar hibernate.transaction.manager_lookup_class
. En otros entornos, debe asegurarse que se haya completado la transacción cuando se llame a Session.close()
o Session.disconnect()
.
La estrategia de caché transactional
brinda soporte a provedores de cachés completamente transaccionales como JBoss TreeCache. Un caché así, sólo se puede utilizar en un entorno JTA y tiene que especificar hibernate.transaction.manager_lookup_class
.
Ninguno de los provedores de caché soporta todas las estrategias de concurrencia al caché.
La siguiente tabla muestra qué provedores son compatibles con qué estrategias de concurrencia.
Tabla 21.2. Soporte a Estrategia de Concurrencia a Caché
Caché | read-only | nonstrict-read-write | read-write | transactional |
---|---|---|---|---|
Hashtable (no fue pensado para la utilización en producción) | yes | yes | yes | |
EHCache | yes | yes | yes | yes |
OSCache | yes | yes | yes | |
SwarmCache | yes | yes | ||
JBoss Cache 1.x | yes | yes | ||
JBoss Cache 2 | yes | yes |
Siempre que pase un objeto a save()
, update()
o saveOrUpdate()
y siempre que recupere un objeto utilizando load()
, get()
, list()
, iterate()
o scroll()
, ese objeto se agrega al caché interno de la Session
.
Cuando luego se llame a flush()
, el estado de ese objeto será sincronizado con la base de datos. Si no quiere que ocurra esta sincronización o si está procesando un número enorme de objetos y necesita gestionar la memoria eficientemente, puede utilizar el método evict()
para quitar el objeto y sus colecciones del caché de primer nivel.
Ejemplo 21.9. Explcitly evicting a cached instance from the first level cache using Session.evict()
ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
Cat cat = (Cat) cats.get(0);
doSomethingWithACat(cat);
sess.evict(cat);
}
La Session
también proporciona un método contains()
para determinar si una instancia pertenece al caché de la sesión.
Para expulsar todos los objetos del caché de sesión, llame a Session.clear()
.
Para el caché de segundo nivel, hay métodos definidos en SessionFactory
para explusar el estado en caché de una instancia, clase entera, instancia de colección o rol entero de colección.
Ejemplo 21.10. Second-level cache eviction via SessionFactoty.evict()
and SessionFacyory.evictCollection()
sessionFactory.evict(Cat.class, catId); //evict a particular Cat
sessionFactory.evict(Cat.class); //evict all Cats
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections
El CacheMode
controla la manera en que interactúa una sesión en particular con el caché de segundo nivel:
CacheMode.NORMAL
: lee ítems desde y escribe ítems hacia el caché del segundo nivel
CacheMode.GET
: lee ítems del caché del segundo nivel. No escribe al caché de segundo nivel excepto cuando actualiza datos
CacheMode.PUT
: escribe ítems al caché de segundo nivel. No lee del caché de segundo nivel
CacheMode.REFRESH
: escribe ítems al caché de segundo nivel. No lee del caché de segundo nivel, saltándose el efecto de hibernate.cache.use_minimal_puts
, forzando la actualización del caché de segundo nivel para todos los ítems leídos de la base de datos
Para navegar por los contenidos de una región de caché de segundo nivel o de consultas, use la API de Statistics
:
Ejemplo 21.11. Browsing the second-level cache entries via the Statistics
API
Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
.getEntries();
Necesitará habilitar las estadísticas y, opcionalmente, forzar a Hibernate para que guarde las entradas del caché en un formato más fácil de entender para humanos:
Ejemplo 21.12. Enabling Hibernate statistics
hibernate.generate_statistics true hibernate.cache.use_structured_entries true
Los conjuntos de resultados de peticiones también pueden ponerse en caché. Esto sólamente es útil para consultas que se ejecutan frecuentemente con los mismos parámetros.
El poner en caché los resultados de una petición introduce algunos sobrecostos en términos del procesamiento transaccional normal de sus aplicaciones. Por ejemplo, si pone en caché los resultados de una petición frente a Person, Hibernate necesitará rastrear cuando se deben invalidar esos resultados debido a los cambios que se han guardado en Person. Eso más el hecho de que la mayoría de las aplicaciones simplemente no ganan beneficio de poner los resultados en caché, lleva a Hibernate a deshabilitar el caché de los resultados de una petición por defecto. Para utilizar el caché de peticiones primero necesita habilitar el caché de peticiones:
hibernate.cache.use_query_cache true
Esta configuración crea dos nuevas regiones de caché:
org.hibernate.cache.StandardQueryCache
, mantiene los resultados de la petición en caché
org.hibernate.cache.UpdateTimestampsCache
, mantiene los sellos de fecha de las actualizaciones más recientes a las tablas de peticiones. Estas se utilizan para validar los resultados ya que se sirven desde el caché de peticiones.
If you configure your underlying cache implementation to use expiry or timeouts is very important that the cache timeout of the underlying cache region for the UpdateTimestampsCache be set to a higher value than the timeouts of any of the query caches. In fact, we recommend that the the UpdateTimestampsCache region not be configured for expiry at all. Note, in particular, that an LRU cache expiry policy is never appropriate.
Como lo mencionamos anteriormente, la mayoría de las consultas no se benefician del caché o de sus resultados; de modo que por defecto las consultas individuales no se ponen en caché incluso después de habilitar el caché para peticiones. Para habilitar el caché de resultados para una petición en particular, llame a org.hibernate.Query.setCacheable(true)
. Esta llamada permite que la consulta busque resultados existentes en caché o que agregue sus resultados al caché cuando se ejecuta.
El caché de peticiones no pone en caché el estado real de las entidades en el caché; pone en caché solo los valores del identificador y los resultados de tipo valor. Por esta razón, el caché de peticiones siempre se debe utilizar en conjunto con el caché de segundo nivel para aquellas entidades que se esperan poner en caché como parte de un caché de resultados de una petición (así como con el caché de colección).
Si necesita un control muy detallado sobre las políticas de expiración del caché de consultas, puede especificar una región de caché con nombre para una consulta en particular llamando a Query.setCacheRegion()
.
List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
.setEntity("blogger", blogger)
.setMaxResults(15)
.setCacheable(true)
.setCacheRegion("frontpages")
.list();
Si quiere forzar que el caché de peticiones actualice una de sus regiones (olvídese de cualquier resultado en caché que se encuentre allí) puede utilizar org.hibernate.Query.setCacheMode(CacheMode.REFRESH)
. Junto con la región que ha definido para la petición dada, Hibernate forzará selectivamente los resultados en caché en esa región en particular que se va a actualizar. Esto es particularmente útil en casos donde los datos subyacentes pueden haber sido actualizados por medio de un proceso separado y esta es una alternativa más eficiente que la expulsión en masa de una región por medio de org.hibernate.SessionFactory.evictQueries()
.
En las secciones anteriores hemos abordado las colecciones y sus aplicaciones. En esta sección exploramos algunos puntos en relación con las colecciones en tiempo de ejecución.
Hibernate define tres tipos básicos de colecciones:
colecciones de valores
Asociaciones uno-a-muchos
Aociaciones muchos-a-muchos
Esta clasificación distingue las varias tablas y relaciones de clave foránea pero no nos dice absolutamente todo lo que necesitamos saber sobre el modelo relacional. Para entender completamente la estructura relacional y las características de rendimiento, debemos considerar la estructura de la clave primaria que Hibernate utiliza para actualizar o borrar filas de colección. Esto sugiere la siguiente clasificación:
colecciones indexadas
conjuntos (sets)
bolsas (bags)
Todas las colecciones indexadas (mapas, listas y arrays) tienen una clave principal que consiste de las columnas <key>
e <index>
. En este caso las actualizaciones de colecciones son extremadamente eficientes. La clave principal puede ser indexada eficientemente y una fila en particular puede ser localizada cuando Hibernate intenta actualizarla o borrarla.
Los conjuntos tienen una clave principal que consiste de <key>
y columnas de elementos. Esto puede ser menos eficiente para algunos tipos de elementos de colección, particularmente elementos compuestos o texto largo o campos binarios ya que la base de datos puede no ser capaz de indexar una clave principal compleja eficientemente. Sin embargo, para asociaciones uno a muchos o muchos a muchos, particularmente en el caso de los identificadores sintéticos, es probable que sólo sea igual de eficiente. Si quiere que SchemaExport
realmente cree la clave principal de un <set>
, tiene que declarar todas las columnas como not-null="true"
.
Los mapeos de <idbag>
definen una clave delegada, de modo que siempre resulten eficientes de actualizar. De hecho, son el mejor caso.
Los bags son el peor caso ya que un bag permite valores de elementos duplicados y no tiene ninguna columna índice, no puede definirse ninguna clave principal. Hibernate no tiene forma de distinguir entre filas duplicadas. Hibernate resuelve este problema quitando por completo con un sólo DELETE
y recreando la colección siempre que cambia. Esto puede ser muy ineficiente.
Para una asociación uno-a-muchos, la "clave principal" puede no ser la clave principal física de la tabla de la base de datos. Incluso en este caso, la clasificación anterior es útil todavía. Refleja cómo Hibernate "localiza" filas individuales de la colección.
De la discusión anterior, debe quedar claro que las colecciones indexadas y los conjuntos permiten una operación más eficiente en términos de agregar, quitar y actualizar elementos.
Discutiblemente, hay una ventaja más de las colecciones indexadas sobre otros conjuntos para las asociaciones muchos a muchos o colecciones de valores. Debido a la estructura de un Set
, Hibernate ni siquiera actualiza una fila con UPDATE
cuando se "cambia" un elemento. Los cambios a un Set
siempre funcionan por medio de INSERT
y DELETE
de filas individuales. Una vez más, esta consideración no se aplica a las asociaciones uno a muchos.
Después de observar que los arrays no pueden ser perezosos, podríamos concluir que las listas, mapas e idbags son los tipos más eficientes de colecciones (no inversas), con los conjuntos (sets) no muy atrás. Se espera que los sets sean el tipo más común de colección en las aplicaciones de Hibernate. Esto se debe a que la semántica de los sets es la más natural en el modelo relacional.
Sin embargo, en modelos de dominio de Hibernate bien dieñados, usualmente vemos que la mayoría de las colecciones son de hecho asociaciones uno-a-muchos con inverse="true"
. Para estas asociaciones, la actualización es manejada por el extremo muchos-a-uno de la asociación, y las consideraciones de este tipo sobre el rendimiento de la actualización de las colecciones simplemente no se aplican.
Hay un caso en particular en el que los bags y también las listas son mucho más eficientes que los conjuntos. Para una colección con inverse="true"
, por ejemplo, el idioma estándar de relaciones uno-a-muchos bidireccionales, podemos agregar elementos a un bag o lista sin necesidad de inicializar (recuperar) los elementos del bag. Esto se debe a que, a manera opuesta de Collection.add()
o Collection.addAll()
siempre deben retornar verdadero para un bag o List
(no como un Set
). Esto puede hacer el siguiente código común mucho más rápido:
Parent p = (Parent) sess.load(Parent.class, id);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c); //no need to fetch the collection!
sess.flush();
Borrar los elementos de una colección uno por uno a veces puede ser extremadamente ineficiente. Hibernate sabe que no debe hacer eso, en el caso de una colección nueva-vacía (si ha llamado a list.clear()
, por ejemplo). En este caso, Hibernate publicará un sólo DELETE
.
Suponga que agrega un solo elemento a una colección de tamaño veinte y luego quitamos dos elementos. Hibernate publicará una declaración INSERT
y dos declaraciones DELETE
a menos que la colección sea un bag. Esto ciertamente es deseable.
Sin embargo, supónga que quitamos dieciocho elementos, dejando dos y luego añadimos tres elementos nuevos. Hay dos formas posibles de proceder
borrar dieciocho filas una a una y luego insertar tres filas
quitar toda la colección en un sólo DELETE
de SQL e insertar todos los cinco elementos actuales uno por uno
Hibernate no sabe que la segunda opción es probablemente la más rápida. Probablemente no sería deseable que Hibernate fuese tan intuitivo ya que tal comportamiento podría confundir a disparadores de la base de datos, etc.
Afortunadamente, puede forzar este comportamiento (por ejemplo, la segunda estrategia) en cualquier momento descartando (por ejemplo, desreferenciando) la colección original y retornando una colección nuevamente instanciada con todos los elementos actuales.
El borrado-de-un-sólo-tiro no se aplica a las colecciones mapeadas inverse="true"
.
La optimización no es de mucho uso sin el monitoreo y el acceso a números de rendimiento. Hibernate brinda un rango completo de números sobre sus operaciones internas. Las estadísticas en Hibernate están disponibles por SessionFactory
.
Puede acceder a las métricas de SessionFactory
de dos formas. Su primera opción es llamar a sessionFactory.getStatistics()
y leer o mostrar por pantalla la Statistics
por sí mismo.
Hibernate también puede utilizar JMX para publicar las métricas si habilita el MBean StatisticsService
. Puede habilitar un sólo MBean para todas sus SessionFactory
o una por fábrica. Véa el siguiente código para ver ejemplos de configuración minimalistas:
// MBean service registration for a specific SessionFactory
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "myFinancialApp");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
server.registerMBean(stats, on); // Register the Mbean on the server
// MBean service registration for all SessionFactory's
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "all");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
server.registerMBean(stats, on); // Register the MBean on the server
Puede activar y desactivar el monitoreo de una SessionFactory
en tiempo de configuración, establezca hibernate.generate_statistics
como false
en tiempo de ejecución: sf.getStatistics().setStatisticsEnabled(true)
o hibernateStatsBean.setStatisticsEnabled(true)
Las estadísticas pueden ser reajustadas programáticamente utilizando el método clear()
. Puede enviarse un resumen a un registro (a nivel de información) utilizando el método logSummary()
.
Hibernate proporciona un número de métricas, desde información muy básica hasta la más especializada sólamente relevante en ciertos escenarios. Todos los contadores disponibles se describen en la API de la interfaz Statistics
, en tres categorías:
Métricas relacionadas al uso general de Session
usage, tales como número de sesiones abiertas, conexiones JDBC recuperadas, etc,
Métricas relacionadas con las entidades, colecciones, consultas y cachés como un todo (también conocidas como métricas globales).
Métricas detalladas relacionadas con una entidad, colección, consulta o región de caché en particular.
Por ejemplo, puede comprobar el acceso, pérdida y radio de colecciones de entidades y consultas en el caché, y el tiempo promedio que necesita una consulta. Tenga en cuenta que el número de milisegundos está sujeto a una aproximación en Java. Hibernate está vinculado a la precisión de la MVJ, en algunas plataformas esto podría tener incluso una exactitud de 10 segundos.
Se usan getters simples para acceder a la métrica global (por ejemplo, no vinculadas en particular a una entidad, colección, región de caché, etc). Puede acceder a las métricas de una entidad, colección, región de caché en particular a través de su nombre y a través de su representación HQL o SQL para las consultas. Por favor refiérase al Javadoc de la API de Statistics
, EntityStatistics
, CollectionStatistics
, SecondLevelCacheStatistics
, y QueryStatistics
para obtener más información. El siguiente código es un ejemplo sencillo:
Statistics stats = HibernateUtil.sessionFactory.getStatistics();
double queryCacheHitCount = stats.getQueryCacheHitCount();
double queryCacheMissCount = stats.getQueryCacheMissCount();
double queryCacheHitRatio =
queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
log.info("Query Hit ratio:" + queryCacheHitRatio);
EntityStatistics entityStats =
stats.getEntityStatistics( Cat.class.getName() );
long changes =
entityStats.getInsertCount()
+ entityStats.getUpdateCount()
+ entityStats.getDeleteCount();
log.info(Cat.class.getName() + " changed " + changes + "times" );
Para trabajar sobre todas las entidades, colecciones, consultas y regiones de cachés, recuperando la lista de nombres de entidades, colecciones, consultas y regiones de cachés con los siguientes métodos: getQueries()
, getEntityNames()
, getCollectionRoleNames()
y getSecondLevelCacheRegionNames()
.
Copyright © 2004 Red Hat, Inc.