2012-05-15 18 views
7

Estoy implementando un mecanismo de persistencia basado en el atributo de entidad. Todo el acceso a la base de datos se realiza a través de Hibernate. Tengo una tabla que contiene rutas para nodos, es extremadamente simple, solo una identificación, y una ruta (cadena) Las rutas serían pequeñas en número, alrededor de algunos miles.¿Cómo uso la memoria caché de segundo nivel de Hibernate con JPA?

La tabla principal tiene millones de filas, y en lugar de repetir las rutas, he normalizado las rutas hacia su propia tabla. El siguiente es el comportamiento que quiero, cuando se inserta en la tabla principal

1) Comprobar si existe la ruta de acceso en la tabla caminos (consulta a través del gestor de la entidad, utilizando valor de ruta como parámetro)

2) si no existe , inserte y obtenga ID (persista vía administrador de entidad)

3) coloque el ID como valor de clave foránea en la fila de la tabla principal e insértelo en la tabla principal.

Esto va a suceder miles de veces para un conjunto de objetos de dominio, que corresponden a muchas filas en la tabla principal y algunas otras tablas. Por lo que los pasos anteriores se repiten usando una sola transacción como esta:

EntityTransaction t = entityManager.getTransaction(); 
    t.begin(); 
    //perform steps given above, check, and then persist etc.. 
    t.commit(); 

Al realizar el paso 2, se introduce una enorme caída de rendimiento de la operación total. Está pidiendo el almacenamiento en caché, porque después de un tiempo esa mesa tendrá como mucho 10-20.000 entradas con inserciones nuevas muy raras. Intenté hacer esto con Hibernate y perdí casi 2 días.

Estoy usando Hibernate 4.1, con anotaciones JPA y ECache. He tratado de activar la caché de consultas, incluso con el mismo objeto de consulta a través de los insertos, como se muestra a continuación:

Query call = entityManager.createQuery("select pt from NodePath pt " + 
       "where pt.path = :pathStr)"); 
     call.setHint("org.hibernate.cacheable", true); 
     call.setParameter("pathStr", pPath); 
     List<NodePath> paths = call.getResultList(); 
     if(paths.size() > 1) 
      throw new Exception("path table should have unique paths"); 
     else if (paths.size() == 1){ 
      NodePath path = paths.get(0); 
      return path.getId(); 
     } 
     else {//paths null or has zero size 
      NodePath newPath = new NodePath(); 
      newPath.setPath(pPath); 
      entityManager.persist(newPath); 
      return newPath.getId(); 
     } 

La entidad NodePath está anotado como sigue:

@Entity 
@Cacheable 
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 
@Table(name = "node_path", schema = "public") 
public class NodePath implements java.io.Serializable { 

La caché de consultas está siendo acostumbrado, por lo que yo puedo ver en las estadísticas, pero no sirve para la caché de segundo nivel se informa:

queries executed to database=1 
query cache puts=1 
query cache hits=689 
query cache misses=1 
.... 
second level cache puts=0 
second level cache hits=0 
second level cache misses=0 
entities loaded=1 
.... 

una tabla hash simple, escrita a mano como una caché, funciona como se espera, d cortar propio tiempo total drásticamente. Supongo que no estoy activando el almacenamiento en caché de Hibernate debido a la naturaleza de mis operaciones.

¿Cómo uso la memoria caché de segundo nivel de hibernate con esta configuración? Para el registro, este es mi persistencia xml:

http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" versión = "2.0">

<provider>org.hibernate.ejb.HibernatePersistence</provider> 
<class>...</class> 
<exclude-unlisted-classes>true</exclude-unlisted-classes> 
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode> 

    <properties> 
    <property name="hibernate.connection.driver_class" value="org.postgresql.Driver" /> 
    <property name="hibernate.connection.password" value="zyx" /> 
    <property name="hibernate.connection.url" value="jdbc:postgresql://192.168.0.194:5432/testdbforml" /> 
    <property name="hibernate.connection.username" value="postgres"/> 
    <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/> 
    <property name="hibernate.search.autoregister_listeners" value="false"/> 
    <property name="hibernate.jdbc.batch_size" value="200"/> 
    <property name="hibernate.connection.autocommit" value="false"/> 
    <property name="hibernate.generate_statistics" value="true"/> 
    <property name="hibernate.cache.use_structured_entries" value="true"/> 

    <property name="hibernate.cache.use_second_level_cache" value="true"/> 
    <property name="hibernate.cache.use_query_cache" value="true"/>   

    <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>    

    </properties> 

+0

Muy interesante! Gracias por su pregunta y respuesta;) +1 – MychaL

Respuesta

2

Ok, lo he encontrado. Mi problema fue que, consulta en caché se mantiene sólo ID de resultados de la consulta en la caché, y era (probablemente) que se remonta a db para obtener los valores reales, en lugar de lo que les desde el segundo nivel de caché.

El problema es, por supuesto, que la consulta no puso esos valores en el caché de segundo nivel, ya que no fueron seleccionados por el ID principal. Entonces, la solución es usar un método que colocará valores en la memoria caché de segundo nivel y con hibernación 4.1, he logrado hacer esto con una identificación natural. Aquí está la función que inserta o devuelve el valor de la caché, en caso de que ayude a alguien más:

private UUID persistPath(String pPath) throws Exception{ 
     org.hibernate.Session session = (Session) entityManager.getDelegate(); 
     NodePath np = (NodePath) session.byNaturalId(NodePath.class).using("path", pPath).load(); 
     if(np != null) 
      return np.getId(); 
     else {//no such path entry, so let's create one 
      NodePath newPath = new NodePath(); 
      newPath.setPath(pPath); 
      entityManager.persist(newPath); 
      return newPath.getId(); 
     } 


    } 
Cuestiones relacionadas