Hibernate.org Community Documentation

- Anterior
- Siguiente

## Capítulo 21. Mejoramiento del rendimiento

## 21.1. Estrategias de recuperación

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.

### 21.1.1. Trabajo con asociaciones perezosas

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.

### 21.1.2. Afinación de las estrategias de recuperación

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.

### 21.1.3. Proxies de asociaciones de un sólo extremo

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();

## Nota

`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.

### 21.1.4. Inicialización de colecciones y proxies

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();

### 21.1.5. Utilización de recuperación de lotes

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.

### 21.1.6. Utilización de la recuperación por subselección

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.

### 21.1.7. Perfiles de recuperación

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 );

## Nota

`@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.

### 21.1.8. Utilización de la recuperación perezosa de propiedades

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.

## 21.2. El Caché de Segundo Nivel

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)

### 21.2.1. Mapeos de caché

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`

## Nota

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 region() default "";
        String include() 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)
| `include` (optional): all to include all properties, non-lazy to only include non lazy properties (default all).

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="RegionName"
        include="all|non-lazy"
    />

| `usage` especifica la estrategia de caché: `transactional`, `read-write`, `nonstrict-read-write` o `read-only`
| `region` (opcional: por defecto es el nombre del rol de la clase o colección): especifica el nombre de la región de caché de segundo nivel.
| `include` (opcional: por defecto es `all`) `non-lazy`: especifica que las propiedades de la entidad mapeadas con `lazy="true"` no se pueden poner en caché cuando se habilita la recuperación perezoza a nivel de atributos.

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

### 21.2.2. Estrategia: sólo lectura

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.

### 21.2.3. Estrategia: lectura/escritura (read/write)

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.

### 21.2.4. Estrategia: lectura/escritura no estricta

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()`.

### 21.2.5. Estrategia: transaccional

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`.

### 21.2.6. Compatibilidad de proveedor de caché/estrategia de concurrencia

## Importante

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

## 21.3. Gestión de cachés

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

## 21.4. El Caché de Consultas

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.

### 21.4.1. Habilitación del caché de peticiones

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.

## Importante

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.

## Nota

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).

### 21.4.2. Regiones de caché de consultas

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()`.

## 21.5. Comprensión del rendimiento de Colecciones

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.

### 21.5.1. Taxonomía

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.

### 21.5.2. Las listas, mapas, idbags y conjuntos son las colecciones más eficientes de actualizar

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.

### 21.5.3. Los Bags y las listas son las colecciones inversas más eficientes

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();

### 21.5.4. Borrado de un sólo tiro

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"`.

## 21.6. Control del rendimiento

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`.

### 21.6.1. Control de una 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()`.

### 21.6.2. Métricas

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.

- AnteriorCapítulo 20. Mapeo XML
- Subir
- Inicio
- SiguienteCapítulo 22. Manual del conjunto de herramientas