2012-03-06 16 views
6

我剛剛意識到當一個對象從Hibernate緩存中被驅逐時,dependant collections, if cached, have to be evicted separately與父實體一起驅逐依賴集合

對我來說這是一個很大的WTF:

  • 它是很容易忘記驅逐集合(例如,當一個新的被添加到對象的映射);
  • 驅逐依賴集合的代碼是醜陋和龐大的,例如,

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

這是很明顯的,如果父對象變了,這是毫無意義的保持它周圍的藏品,因爲他們無論如何,最有可能來自該父母。

我在這裏錯過了什麼嗎?如果沒有手動編寫所有代碼,是否真的沒有辦法將對象與其所有子實體一起刷新?

+2

by'dependent collections'你的意思是他們配置了像「all-delete-orphan」這樣的級聯? – 2012-03-06 16:04:24

+0

@Nathan Hughes - 是的。要添加到我的參數列表中 - 當驅逐集合時,無論如何都必須傳遞父代碼。 – mindas 2012-03-06 16:05:30

回答

4

這是一箇舊的issue。在插入,更新或刪除集合引用實體時,有一種方法可以掛鉤到hibernate中以驅逐收集緩存。我有supplied a fix for hibernate。解決方法是安排在休眠4.3.0.Beta5,將由物業被激活:

hibernate.cache.auto_evict_collection_cache=true 

只要此修復程序不realeased你可以通過解決方法只是SessionFactory的註冊CollectionCacheInvalidator注入驅逐邏輯和SessionFactoryServiceRegistry由你自己。

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

這只是一個緩存。緩存只是應該減少數據庫訪問。當你驅逐一個對象時,你通常不會對子對象做任何修改,而只是下次可以從緩存中加載它們。另外經常發生子對象仍然被其他父對象使用(在這種情況下,名稱'child'不正確,因爲它是n:1或m:n關係)。在子女身體仍在使用的地方,讓孩子們流血可能會引發很奇怪的錯誤。

所以,如果它是好的驅逐孩子只是取決於您的應用程序和數據庫設計。因此hibernate默認不會驅逐子對象。

如果您希望自動清除子對象,請在映射文件中使用cascade =「evict」。

驅逐所有對象的更多rabiate方法是關閉會話並打開一個新的對象。然後會話的所有對象都被驅逐出去。

+1

我不同意你的觀點。從緩存中逐出兒童將無效。如果不存在對子對象的引用,則這不是問題 - 必要時可以根據需要加載它。或者,如果另一個類保留對緩存集合的引用,那麼它又不是一個問題,因爲硬引用仍然存在。但是謝謝cascade =「evict」的提示,我會看看這個。 – mindas 2012-03-27 13:15:44

0

使用Hibernate 4的Ehcache爲2級緩存提供者,我能夠刪除收藏單位乾脆宣稱:

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

當調用刪除禮品,然後保存父一切工作順利進行。

+0

一切正常,因爲你有註釋「fetch = FetchType.EAGER」 – 2017-04-13 15:29:01