3

语境

多年来,Stackoverflow 对我来说一直是无价之宝,我想通过发布问题答案来回馈我最近花费大量时间的事情。

背景

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

这是我想从 JSON 反序列化并重新附加到休眠会话的一些 POJO 的大致定义:

@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>();
    ...
}

在我寻找这个问题的答案时,最接近的匹配项是:

4

1 回答 1

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;
    }

    ...
}

走过

如您所见,我们必须确保我们永远不会尝试重新附加当前会话中已经存在的实体,否则休眠将引发异常。这就是为什么我们必须getCurrentSession().contains(entity)reattach(). 此处必须小心使用contains(),因为 hibernate 不会使用entity.hachCode()来查找实体,而是使用 来System.identityHashCode(entity)确保它不仅是等效实例,而且与会话中可能已经存在的完全相同的实例。换句话说,您必须适当地管理重用实例。

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

  • 将所有者和快照键设置为拥有实体的 id
  • 将快照角色设置为完全限定的 entity.property 名称,正如 hibernate 所看到的那样
  • 将快照Serializable参数设置为现有集合的不可变副本
  • 将会话设置为null休眠状态不会认为我们尝试将其附加到现有会话两次

要完成重新连接,请调用session.lock(entity, LockMode.NONE)。在这一点上,据我的测试可知,hibernate 尊重这个实体,并在你调用saveOrUpdate().

注意事项

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

于 2013-04-25T14:34:50.837 回答