2012-03-06 24 views
6

Tôi vừa nhận ra rằng khi một đối tượng được gỡ bỏ khỏi bộ nhớ cache Hibernate, dependant collections, if cached, have to be evicted separately.Loại bỏ các bộ sưu tập phụ thuộc cùng với thực thể cha mẹ

Đối với tôi đây là một WTF lớn:

  • nó là rất dễ dàng để quên đi để đuổi một bộ sưu tập (ví dụ khi một cái mới được thêm vào các bản đồ đối tượng);
  • mã để loại bỏ các bộ sưu tập phụ thuộc là xấu xí và cồng kềnh, ví dụ:

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

Nó là khá rõ ràng rằng nếu đối tượng cha mẹ đã thay đổi, có rất ít tinh thần để giữ nó bộ sưu tập xung quanh khi họ có nhiều khả năng được bắt nguồn từ bố mẹ đó.

Tôi có thiếu gì đó ở đây không? Có thực sự không có cách nào để tuôn ra một đối tượng cùng với tất cả các thực thể con của nó mà không cần viết tất cả các mã này bằng tay?

+2

bởi 'bộ sưu tập phụ thuộc' bạn có nghĩa là chúng được định cấu hình với một thác như "all-delete-orphan"? –

+0

@Nathan Hughes - vâng. Để thêm vào danh sách đối số của tôi - khi loại bỏ các bộ sưu tập, người ta vẫn phải chuyển id cha mẹ. – mindas

Trả lời

4

Đây là số cũ issue. Có một cách để móc vào hibernate để gỡ bỏ bộ nhớ cache trên chèn, cập nhật hoặc loại bỏ các bộ sưu tập được gọi thực thể. Tôi có supplied a fix for hibernate. Khắc phục sự cố được lên kế hoạch cho Hibernate 4.3.0.Beta5 và sẽ được kích hoạt bởi bất động sản:

hibernate.cache.auto_evict_collection_cache=true 

Chừng nào sửa chữa này không realeased bạn có thể workaround để tiêm đuổi logic bằng cách chỉ cần đăng ký CollectionCacheInvalidator với SessionFactory của bạn và SessionFactoryServiceRegistry của riêng bạn.

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

Đó chỉ là bộ nhớ cache. Bộ nhớ cache chỉ nên giảm quyền truy cập cơ sở dữ liệu. Khi bạn gỡ bỏ một đối tượng, sau đó thường bạn không thực hiện bất kỳ sửa đổi nào với các đối tượng con, và chúng chỉ có thể được nạp từ bộ nhớ cache vào lần sau. Ngoài ra nó thường xảy ra đối tượng con vẫn được sử dụng bởi các đối tượng cha mẹ khác (trong trường hợp này tên 'con' là không chính xác, bởi vì nó là một quan hệ n: 1 hoặc m: n). Việc trục xuất trẻ em có thể gây ra những lỗi rất lạ ở một nơi khác, nơi mà các vật thể con vẫn đang được sử dụng.

Vì vậy, nếu nó là tốt để đuổi các trẻ em chỉ phụ thuộc vào ứng dụng của bạn và thiết kế cơ sở dữ liệu. Do đó, hibernate không loại bỏ các đối tượng con theo mặc định.

Nếu bạn muốn có các đối tượng con bị trục xuất tự động, sau đó sử dụng cascade = "evict" trong tệp ánh xạ của bạn.

Một phương pháp rabiate khác để loại bỏ tất cả các đối tượng là đóng phiên và mở một phiên mới. Sau đó tất cả các đối tượng của phiên được đuổi ra.

+1

Tôi không đồng ý với điểm của bạn. Việc đuổi con khỏi bộ nhớ cache sẽ không có hiệu lực. Nếu không có tham chiếu đến các đối tượng con tồn tại, đây không phải là một vấn đề - nó có thể được nạp theo yêu cầu khi cần thiết. Hoặc nếu một lớp khác giữ một tham chiếu đến bộ sưu tập được lưu trong bộ nhớ cache, thì lại không phải là vấn đề khi tham chiếu cứng vẫn tồn tại. Nhưng cảm ơn vì gợi ý về cascade = "evict", tôi sẽ xem xét điều này. – mindas

0

Sử dụng Hibernate 4 và EHCache là nhà cung cấp bộ nhớ cache cấp 2 tôi đã có thể loại bỏ tổ chức bộ sưu tập chỉ đơn giản tuyên bố:

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

Khi gọi xóa trên quà tặng và sau đó tiết kiệm cha mẹ mọi thứ hoạt động trơn tru.

+0

mọi thứ hoạt động vì bạn có chú thích "fetch = FetchType.EAGER" –

Các vấn đề liên quan