【问题标题】:@OneToMany Map silently deletes Map's keys@OneToMany Map 静默删除 Map 的键
【发布时间】:2013-02-25 03:21:58
【问题描述】:

我有以下代码应该将地图保存到数据库。问题是,当它保存实体及其关系时,它会默默地丢弃映射键值 - 没有错误,什么都没有,数据库只是获取 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)。

【问题讨论】:

    标签: java sql hibernate jakarta-ee


    【解决方案1】:

    JPA 的一个(有时是苛刻的)规则是,如果您希望检索具有相同关系。

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

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

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

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

    选项:Map Key 是一个持久化字段

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

    @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,我只能假设它适用于带有方法 saveflush 的 Hibernate Session

    【讨论】:

    • 但如果 Cascade = all,则只需将子级添加到父级容器中,它们也会被持久化。反之,当从父容器中移除子时,子记录也会被删除。
    • 是的,但是:这里的重点是您根本不能将子容器添加到父容器,因为子容器(尚不存在),因此尚未为其分配主键( id 字段)。鉴于在父级中使用Map 来引用子级,这是有问题的。如果使用了简单的List,那么是的..您可以将所有内容链接起来并将父级持久化一次。
    • 对,完全跳过了特殊容器。为什么我们使用它而不是列表?
    • 我只是输入了 3 in。在这种情况下它没有任何意义,也没有引用任何其他列。我更新了问题以使其更清楚。
    • 我相应地更新了我的回复:如果它“没有任何意义”,那就摆脱它。使用List&lt;Item&gt;。我实际上对这个回应有点失望!如果它没有任何意义,如果它不引用任何列,那么除了返回 NULL 之外,您希望 JPA 在这里为您做什么 did 吗?!
    猜你喜欢
    • 1970-01-01
    • 2015-12-05
    • 2023-04-01
    • 2018-05-26
    • 2018-01-02
    • 2011-01-22
    • 2015-06-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多