在我的情况下,我将实体的序列化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>();
...
}
在我搜索这个问题的答案时,最接近的匹配在哪里:
答案 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
上,我们必须:
Serializable
参数设置为现有集合的不可变副本null
,以便hibernate认为我们不会尝试将其附加到现有会话两次要完成重新连接,请致电session.lock(entity, LockMode.NONE)
。此时,就我的测试而言,hibernate尊重此实体并在您调用saveOrUpdate()
时正确保留所有更改。
我意识到这不是所有情况下的通用解决方案。这只是我的具体问题的快速解决方案,其他人可以希望利用和改进。软件是迭代的。