Hibernate:如何重新附加具有@ElementCollection的外化实体

时间:2013-04-24 18:19:45

标签: java hibernate

上下文

多年来,Stackoverflow对我来说非常宝贵,我想通过发布这个问题来回馈我最近花了大量时间的答案。

背景

在我的情况下,我将实体的序列化JSON存储在Memcached中。由于各种原因,我不喜欢hibernate的缓存(第二级和查询缓存)的工作方式,因此我决定编写自己的缓存层。但这会产生一个问题:似乎没有简单的方法可以将之前缓存的POJO重新连接回hibernate会话,特别是如果涉及@ElementCollection

这是我想从JSON反序列化的一些POJO的近似定义,并重新连接到一个hibernate会话:

@Entity
@Table
public class User extends AbstractModel {

    @ElementCollection
    @CollectionTable (name = "UserAttribute", joinColumns = { @JoinColumn (name = "UserId") })
    @MapKeyColumn (name = "Name")
    @Column (name = "Value")
    private Map<String, String> attributes = new HashMap<String, String>();
    ...
}

@Entity
@Table
public class Content extends AbstractModel {

    @ManyToOne(cascade = CascadeType.ALL) @JoinColumn (name = "UserId")
    private User user;

    @ElementCollection
    @CollectionTable(name = "ContentAttribute", joinColumns = { @JoinColumn(name = "ContentId") })
    @MapKeyColumn(name = "Name")
    @Column(name = "Value")
    private Map<String, String> attributes = new HashMap<String, String>();
    ...
}

在我搜索这个问题的答案时,最接近的匹配在哪里:

1 个答案:

答案 0 :(得分:1)

从问题中的链接中提取海报的位置,这是我设法重新附加的近似/相关代码。下面是对正在发生的事情的概述。

@Repository
public abstract class DaoHibernate<T> implements Dao<T> {

    @Override
    public T reattach(T entity) {
        if (getCurrentSession().contains(entity)) {
            return entity;
        }
        if (entity instanceof User) {
            return (T) reattachedUser((User) entity);
        }
        if (entity instanceof Content) {
            Content content = (Content) entity;
            User user = content.getUser();
            if (!currentSession().contains(user)) {
                content.setUser(reattachedUser(user));
            }
            content.setAttributes(persistentAttributesMap(content.getId(), content.getAttributes(), Content.class);
            getCurrentSession().lock(content, LockMode.NONE);
            return entity;
        }
        throw new UnsupportedOperationException("reattach is not supported for entity: " + entity.getClass().getName());
    }

    private User reattachedUser(User user) {
        user.setAttributes(persistentAttributesMap(user.getId(), user.getAttributes(), User.class));
        getCurrentSession().lock(user, LockMode.NONE);
        return user;
    }

    @SuppressWarnings ("unchecked")
    private Map<String, String> persistentAttributesMap(long id, Map<String, String> attributes, Class clazz) {
        SessionFactory sessionFactory = getSessionFactory();
        Session currentSession = sessionFactory.getCurrentSession();
        String role = clazz.getName() + ".attributes";
        CollectionPersister collectionPersister = ((SessionFactoryImplementor) sessionFactory).getCollectionPersister(role);
        MapType mapType = (MapType) collectionPersister.getCollectionType();
        PersistentMap persistentMap = (PersistentMap) mapType.wrap((SessionImplementor) currentSession, attributes);
        persistentMap.setOwner(id);
        persistentMap.setSnapshot(id, role, ImmutableMap.copyOf(attributes));
        persistentMap.setCurrentSession(null);
        return persistentMap;
    }

    ...
}

浏览

正如您所看到的,我们必须确保我们永远不会尝试重新附加已在当前会话中的实体,否则hibernate将抛出异常。这就是我们必须在getCurrentSession().contains(entity)reattach()的原因。必须谨慎使用contains(),因为hibernate不会使用entity.hachCode()来查找实体,而是使用System.identityHashCode(entity),这不仅可以确保它是一个等效的实例,还可以确保确切可能已在会话中的相同实例。换句话说,您必须适当地管理重用实例。

只要关联实体标有Cascade.ALL,hibernate应该做正确的事情。也就是说,除非你有像我们的@ElementCollection属性映射一样的hibernate托管集合。在这种情况下,我们必须手动创建PersistentCollection(确切地说PersistentMap)并在其上设置正确的属性,如persistentAttributesMap,否则hibernate将抛出异常。简而言之,在PersistentMap上,我们必须:

  • 将所有者和快照密钥设置为拥有实体的ID
  • 将快照角色设置为完全限定的entity.property名称,因为hibernate看到它
  • 将快照Serializable参数设置为现有集合的不可变副本
  • 将会话设置为null,以便hibernate认为我们不会尝试将其附加到现有会话两次

要完成重新连接,请致电session.lock(entity, LockMode.NONE)。此时,就我的测试而言,hibernate尊重此实体并在您调用saveOrUpdate()时正确保留所有更改。

注意事项

我意识到这不是所有情况下的通用解决方案。这只是我的具体问题的快速解决方案,其他人可以希望利用和改进。软件是迭代的。