我有以下代码可以将地图保存到数据库。问题在于,虽然它保存了实体以及它们之间的关系,但它会默默地丢弃映射键值 - 没有错误,没有任何错误,数据库只会获得NULL。
代码:
package com.tester;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import lombok.Data;
import lombok.ToString;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
public class App {
@Entity
@Table (name = "test_items")
@Data
@ToString (exclude = "parent")
public static class Item {
@Id @GeneratedValue
private Long id;
int slot;
@ManyToOne
private Item parent;
@OneToMany (cascade = CascadeType.ALL, mappedBy = "parent")
@MapKey (name = "slot")
Map<Integer, Item> children;
}
private static SessionFactory buildSessionFactory() {
try {
return new AnnotationConfiguration().configure().buildSessionFactory();
} catch (final Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static void main(final String[] args) {
final SessionFactory sf = buildSessionFactory();
final Session session = sf.openSession();
session.beginTransaction();
final Item item100 = new Item();
final Item item110 = new Item();
session.save(item100);
session.save(item110);
session.flush();
item110.setParent(item100);
final HashMap<Integer, Item> children = new HashMap<Integer, Item>();
children.put(3, item110);
item100.setChildren(children);
session.saveOrUpdate(item100);
session.saveOrUpdate(item110);
session.getTransaction().commit();
session.close();
}
}
上述后的预期数据库结果:
id slot parent_id
1 0 NULL
2 3 1 [->]
实际结果:
id slot parent_id
1 0 NULL
2 0 1 [->]
正如您所看到的,即使Hibernate为密钥创建了一个列,它们也会填充NULL,而children.put(3, item110);
中的信息块“3”根本不会保留在数据库中。它完全丢失了。
想法?
注意:我也尝试了@MapKeyClass (Integer.class)
,但没有成功。
编辑:有问题的3并不是指任何其他内容,也不是对任何其他字段/列的引用。它是一个任意值,在此特定上下文中,应定义具有多个槽的容器中项的索引。但是对于我所关心的一切,你可以用“abcdefg”代替它(假设你把Map改为String而不是Integer)。
答案 0 :(得分:3)
JPA的一个(有时是苛刻的)规则是,实体之间的所有关系必须在持久性之前正确建立,如果您希望检索具有相同实体的相同实体关系。
所以,问问自己:&#39; 3&#39;来自,它意味着什么?它在哪里坚持?
这似乎是最不可能的,但问题的更新表明情况就是这样。当您检索实体时,它会在密钥中需要某些内容。它不会只是制造一些东西。
底线:在这种情况下你不能使用Map;请改用List<Item>
。您仍然可以通过某种索引访问。
如果map键是持久字段,则必须指定:
@MapKey(name=<keyfieldname>)
id
以外的字段作为地图密钥:根据您的情况,介绍一个新字段(您使用当前示例的选项已用完了!):
@Column
Integer slot;
并在地图上使用相应的@MapKey:
@OneToMany (cascade = CascadeType.ALL, mappedBy = "parent")
@MapKey(name="slot")
Map<Long, Item> children = new HashMap<Integer, Item>();
最后在您的代码中:
...
final Item parent = new Item();
final Item child = new Item();
child.setSlot(3);
child.setParent(parent);
final HashMap<Integer, Item> children = new HashMap<Integer, Item>();
children.put(child.getParentKey(), child);
parent.setChildren(children);
session.save(child);
session.save(parent);
id
作为地图密钥:使用id(一旦获得类型&#39;右)引入了一个新问题:如何在创建父级之前获取子级id
(自动生成)。鉴于JPA EntityManager em
:
@OneToMany (cascade = CascadeType.ALL, mappedBy = "parent")
@MapKey(name="id")
Map<Long, Item> children = new HashMap<Integer, Item>();
...
final Item parent = new Item();
final Item child = new Item();
child.setParent(parent);
em.persist(child);
em.flush(); // Force child's 'id' to be generated & populated
final HashMap<Integer, Item> children = new HashMap<Integer, Item>();
children.put(child.getId(), child);
parent.setChildren(children);
em.persist(parent);
这适用于JPA / EclipseLink,我只能假设它使用方法Session
和save
与Hibernate flush
一起工作。