2012-03-06 11 views
6

Me acabo de dar cuenta de que cuando se expulsa un objeto de la memoria caché de Hibernate, dependant collections, if cached, have to be evicted separately.Expulsar las colecciones dependientes junto con la entidad principal

Para mí esto es un un gran WTF:

  • es muy fácil olvidar a desalojar a una colección (por ejemplo, cuando se añade una nueva para el mapeo objeto);
  • código para desalojar colecciones dependientes es feo y voluminoso, p.

    MyClass myObject = ...;
    getHibernateTemplate().evict(myObject);
    Cache cache = getHibernateTemplate().getSessionFactory().getCache();
    cache.evictCollection("my.package.MyClass.myCollection1, id);
    ...
    cache.evictCollection("my.package.MyClass.myCollectionN, id);

Es bastante obvio que si el objeto principal ha cambiado, no tiene mucho sentido para mantener su colecciones en torno a medida que es más probable que se deriven de ese padre de todos modos.

¿Falta algo aquí? ¿Realmente no hay forma de eliminar un objeto junto con todas sus entidades secundarias sin escribir todo este código manualmente?

+2

por 'colecciones dependientes' te refieres a que están configuradas con una cascada como "all-delete-huérfano"? –

+0

@Nathan Hughes - sí. Para agregar a mi lista de argumentos: al desalojar colecciones, uno debe pasar el id. Principal de todos modos. – mindas

Respuesta

4

Esto es un viejo issue. Hay una forma de enganchar en hibernación para desalojar cachés de colección al insertar, actualizar o eliminar la entidad referida de colecciones. Tengo supplied a fix for hibernate. La solución está prevista para Hibernate 4.3.0.Beta5 y se activará en el hotel:

hibernate.cache.auto_evict_collection_cache=true 

Mientras esta revisión no se puede solucionar realeased para inyectar el desalojo-lógica con sólo registrar el CollectionCacheInvalidator con su SessionFactory y SessionFactoryServiceRegistry por su cuenta.

import javax.persistence.OneToMany; 
import java.io.Serializable; 
import java.util.HashMap; 
import java.util.Map; 
import java.util.Set; 

import my.own.library.BeanInformationFromClass; 
import my.own.library.PropertyInformationFromClass; 
import org.apache.commons.lang.StringUtils; 
import org.apache.log4j.Logger; 

import org.hibernate.engine.spi.SessionFactoryImplementor; 
import org.hibernate.event.service.spi.EventListenerRegistry; 
import org.hibernate.event.spi.EventSource; 
import org.hibernate.event.spi.EventType; 
import org.hibernate.event.spi.PostInsertEvent; 
import org.hibernate.event.spi.PostInsertEventListener; 
import org.hibernate.event.spi.PreDeleteEvent; 
import org.hibernate.event.spi.PreDeleteEventListener; 
import org.hibernate.event.spi.PreUpdateEvent; 
import org.hibernate.event.spi.PreUpdateEventListener; 
import org.hibernate.persister.collection.CollectionPersister; 
import org.hibernate.persister.entity.EntityPersister; 
import org.hibernate.persister.entity.Joinable; 
import org.hibernate.service.spi.SessionFactoryServiceRegistry; 

/** 
* @author Andreas Berger (latest modification by $Author$) 
* @version $Id$ 
* @created 27.08.13 - 17:49 
*/ 
public class CollectionCacheInvalidator 
     implements PostInsertEventListener, PreDeleteEventListener, PreUpdateEventListener { 

    private static final Logger LOGGER = Logger.getLogger(CollectionCacheInvalidator.class); 

    private Map<String, String> mappedByFieldMapping; 

    public void integrate(SessionFactoryImplementor sf, SessionFactoryServiceRegistry registry) { 
     EventListenerRegistry eventListenerRegistry = registry.getService(EventListenerRegistry.class); 
     eventListenerRegistry.appendListeners(EventType.POST_INSERT, this); 
     eventListenerRegistry.appendListeners(EventType.PRE_DELETE, this); 
     eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, this); 

     mappedByFieldMapping = new HashMap<String, String>(); 

     Map<String, CollectionPersister> persiters = sf.getCollectionPersisters(); 
     if (persiters != null) { 
      for (CollectionPersister collectionPersister : persiters.values()) { 
       if (!collectionPersister.hasCache()) { 
        continue; 
       } 
       if (!(collectionPersister instanceof Joinable)) { 
        continue; 
       } 
       String oneToManyFieldName = collectionPersister.getNodeName(); 
       EntityPersister ownerEntityPersister = collectionPersister.getOwnerEntityPersister(); 
       Class ownerClass = ownerEntityPersister.getMappedClass(); 

       // Logic to get the mappedBy attribute of the OneToMany annotation. 
       BeanInformationFromClass bi = new BeanInformationFromClass(ownerClass); 
       PropertyInformationFromClass prop = bi.getProperty(oneToManyFieldName); 
       OneToMany oneToMany = prop.getAnnotation(OneToMany.class); 
       String mappedBy = null; 
       if (oneToMany != null && StringUtils.isNotBlank(oneToMany.mappedBy())) { 
        mappedBy = oneToMany.mappedBy(); 
       } 
       mappedByFieldMapping.put(((Joinable) collectionPersister).getName(), mappedBy); 
      } 
     } 
    } 

    @Override 
    public void onPostInsert(PostInsertEvent event) { 
     evictCache(event.getEntity(), event.getPersister(), event.getSession(), null); 
    } 

    @Override 
    public boolean onPreDelete(PreDeleteEvent event) { 
     evictCache(event.getEntity(), event.getPersister(), event.getSession(), null); 
     return false; 
    } 

    @Override 
    public boolean onPreUpdate(PreUpdateEvent event) { 
     evictCache(event.getEntity(), event.getPersister(), event.getSession(), event.getOldState()); 
     return false; 
    } 

    private void evictCache(Object entity, EntityPersister persister, EventSource session, Object[] oldState) { 
     try { 
      SessionFactoryImplementor factory = persister.getFactory(); 

      Set<String> collectionRoles = factory.getCollectionRolesByEntityParticipant(persister.getEntityName()); 
      if (collectionRoles == null || collectionRoles.isEmpty()) { 
       return; 
      } 
      for (String role : collectionRoles) { 
       CollectionPersister collectionPersister = factory.getCollectionPersister(role); 
       if (!collectionPersister.hasCache()) { 
        continue; 
       } 
       if (!(collectionPersister instanceof Joinable)) { 
        continue; 
       } 
       String mappedBy = mappedByFieldMapping.get(((Joinable) collectionPersister).getName()); 
       if (mappedBy != null) { 
        int i = persister.getEntityMetamodel().getPropertyIndex(mappedBy); 
        Serializable oldId = null; 
        if (oldState != null) { 
         oldId = session.getIdentifier(oldState[i]); 
        } 
        Object ref = persister.getPropertyValue(entity, i); 
        Serializable id = null; 
        if (ref != null) { 
         id = session.getIdentifier(ref); 
        } 
        if (id != null && !id.equals(oldId)) { 
         evict(id, collectionPersister, session); 
         if (oldId != null) { 
          evict(id, collectionPersister, session); 
         } 
        } 
       } 
       else { 
        LOGGER.debug("Evict CollectionRegion " + role); 
        collectionPersister.getCacheAccessStrategy().evictAll(); 
       } 
      } 
     } 
     catch (Exception e) { 
      LOGGER.error("", e); 
     } 
    } 

    private void evict(Serializable id, CollectionPersister collectionPersister, EventSource session) { 
     LOGGER.debug("Evict CollectionRegion " + collectionPersister.getRole() + " for id " + id); 
     collectionPersister.getCacheAccessStrategy().evict(
       session.generateCacheKey(
         id, 
         collectionPersister.getKeyType(), 
         collectionPersister.getRole() 
       ) 
     ); 
    } 
} 
0

Es solo un caché. Una memoria caché solo debería reducir el acceso a la base de datos. Cuando expulsa un objeto, a menudo no hace ninguna modificación con los objetos secundarios, y solo pueden cargarse desde el caché la próxima vez. También sucede a menudo que los objetos secundarios todavía son utilizados por otros objetos parentales (en este caso, el nombre 'hijo' no es correcto, porque es una relación n: 1 o m: n). Expulsar a los niños podría provocar errores muy extraños en otro lugar donde los objetos secundarios aún están en uso.

Así que si es bueno expulsar a los niños solo depende de su aplicación y diseño de la base de datos. Por lo tanto, hibernate no desaloja objetos secundarios por defecto.

Si desea que los objetos secundarios sean desalojados automáticamente, entonces use cascade = "evict" en su archivo de mapeo.

Un método más racional para desalojar todos los objetos es cerrar la sesión y abrir una nueva. Entonces todos los objetos de la sesión son desalojados.

+1

No estoy de acuerdo con sus puntos. Expulsar a los niños del caché no tendría ningún efecto. Si no existen referencias a objetos secundarios, esto no es un problema, se puede cargar bajo demanda cuando sea necesario. O si otra clase mantiene una referencia a la colección en caché, tampoco es un problema, ya que la referencia continúa. Pero gracias por la sugerencia de cascade = "evict", voy a echar un vistazo a esto. – mindas

0

usando Hibernate 4 y Ehcache como proveedor de memoria caché de segundo nivel I fue capaz de eliminar entidad colección simplemente declarando:

@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @OneToMany(mappedBy = 'sender', cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true) Set<Gift> sentGifts = [] 

Cuando se llama a borrar el regalo y luego guardar todo padre funciona sin problemas.

+0

todo funciona porque tiene la anotación "fetch = FetchType.EAGER" –

Cuestiones relacionadas