【问题标题】:Hibernate: how to reattach an externalized entity that has an @ElementCollectionHibernate:如何重新附加具有@ElementCollection 的外部化实体
【发布时间】:2013-04-18 10:34:26
【问题描述】:

上下文

多年来,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>();
    ...
}

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

【问题讨论】:

    标签: java hibernate


    【解决方案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;
        }
    
        ...
    }
    

    浏览

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

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

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

    要完成重新附加,请致电session.lock(entity, LockMode.NONE)。在这一点上,据我的测试可以看出,当您调用 saveOrUpdate() 时,hibernate 尊重此实体并正确保留所有更改。

    注意事项

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

    【讨论】:

      猜你喜欢
      • 2011-09-29
      • 2019-04-13
      • 1970-01-01
      • 1970-01-01
      • 2014-04-14
      • 2012-06-09
      • 2017-10-31
      • 1970-01-01
      • 2015-02-27
      相关资源
      最近更新 更多