2

我有以下代码应该将地图保存到数据库。问题是,虽然它保存了实体及其关系,但它默默地丢弃了映射键值——没有错误,什么都没有,数据库只是得到 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 填充,并且来自的信息片段“3”children.put(3, item110);根本不会持久保存到数据库中。它完全丢失了。

想法?

注意:我也尝试过@MapKeyClass (Integer.class),但没有成功。

编辑:有问题的 3 并不意味着引用其他任何内容,也不是对任何其他字段/列的引用。这是一个任意值,在这个特定的上下文中,它应该定义具有多个插槽的容器中的项目的索引。但就我而言,您可以将其替换为“abcdefg”(当然,假设您将 Map 更改为采用 String 而不是 Integer)。

4

1 回答 1

3

JPA 的一个(有时是苛刻的)规则是,如果您希望检索具有相同关系的相同实体,则必须在持久性之前正确建立实体之间的所有关系。

所以,问问自己:“3”是从哪里来的,它的意思是什么?它在哪里坚持?

选项:映射键不是持久字段

这似乎是最不可能的,但问题的更新表明情况确实如此。当你检索一个实体时,它需要在键中包含一些东西。它不会只是编造一些东西。

底线:在这种情况下你不能使用地图;改用 a List<Item>。您应该仍然可以通过某些索引进行访问。

选项:映射键是一个持久字段

如果映射键是持久字段,则必须指定:

@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(一旦你获得了正确的类型)引入了一个新问题:如何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,我只能假设它适用于 HibernateSession的方法saveflush.

于 2013-03-09T19:17:17.050 回答