【问题标题】:JPA persist entities with one to many relationJPA 持久化具有一对多关系的实体
【发布时间】:2012-12-17 07:00:17
【问题描述】:

配置

  • EcliplseLink 2.3.2
  • JPA 2.0
  • 实体是使用 数据库中的实体类... 向导从 netbeans 的 db 架构自动创建的。
  • 控制器类是从 netbeans 自动创建的,JPA 控制器类来自实体类...向导

简短版问题

在经典场景中,两个具有一对多关系的表。我创建父实体,然后创建子实体,并将子实体附加到父实体的集合中。当我创建(控制器方法)父实体时,我希望子实体被创建并与父实体相关联。为什么没有发生?

加长版

父类

@Entity
@XmlRootElement
public class Device implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    private Integer id;
    @Column(unique=true)
    private String name;
    @Temporal(TemporalType.TIMESTAMP)
    private Date updated;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "deviceId")
    private Collection<NetworkInterface> networkInterfaceCollection;

    public Device() {
    }

    public Device(String name) {
        this.name = name;
        updated = new Date();
    }

    // setters and getters...

    @XmlTransient
    public Collection<NetworkInterface> getNetworkInterfaceCollection() {
        return networkInterfaceCollection;
    }

    public void setNetworkInterfaceCollection(Collection<NetworkInterface> networkInterfaceCollection) {
        this.networkInterfaceCollection = networkInterfaceCollection;
    }

    public void addNetworkInterface(NetworkInterface net) {
        this.networkInterfaceCollection.add(net);
    }

    public void removeNetworkInterface(NetworkInterface net) {
        this.networkInterfaceCollection.remove(net);
    }
    // other methods
}

儿童班

@Entity
@Table(name = "NETWORK_INTERFACE")
@XmlRootElement
public class NetworkInterface implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    private Integer id;
    private String name;
    @Temporal(TemporalType.TIMESTAMP)
    private Date updated;
    @JoinColumn(name = "DEVICE_ID", referencedColumnName = "ID")
    @ManyToOne(optional = false)
    private Device deviceId;

    public NetworkInterface() {
    }

    public NetworkInterface(String name) {
        this.name = name;
        this.updated = new Date();
    }

    // setter and getter methods...

    public Device getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(Device deviceId) {
        this.deviceId = deviceId;
    }
}

主类

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("wifi-dbPU");
        DeviceJpaController deviceController = new DeviceJpaController(emf);
        NetworkInterfaceJpaController netController = new NetworkInterfaceJpaController(emf);

        Device device = new Device("laptop");
        NetworkInterface net = new NetworkInterface("eth0");

        device.getNetworkInterfaceCollection().add(net);
        deviceController.create(device);
    }
}

此类在以下行中抛出 NullPointerException:device.getNetworkInterfaceCollection().add(net);

系统知道有一个新实体device,并且它的集合中有一个元素net。我希望它在db中写入device,获取设备的id,将其附加到net并将其写入db。

而不是这个,我发现这些是我必须做的步骤:

deviceController.create(device);
net.setDeviceId(device);
device.getNetworkInterfaceCollection().add(net);
netController.create(net);

当父类知道它是孩子并且应该为我创建它时,为什么我必须创建它?

DeviceJpaController 中的 create 方法(对不起,字段中的长名称,它们是自动生成的)。

public EntityManager getEntityManager() {
    return emf.createEntityManager();
}

public void create(Device device) {
    if (device.getNetworkInterfaceCollection() == null) {
        device.setNetworkInterfaceCollection(new ArrayList<NetworkInterface>());
    }
    EntityManager em = null;
    try {
        em = getEntityManager();
        em.getTransaction().begin();
        Collection<NetworkInterface> attachedNetworkInterfaceCollection = new ArrayList<NetworkInterface>();
        for (NetworkInterface networkInterfaceCollectionNetworkInterfaceToAttach : device.getNetworkInterfaceCollection()) {
            networkInterfaceCollectionNetworkInterfaceToAttach = em.getReference(networkInterfaceCollectionNetworkInterfaceToAttach.getClass(), networkInterfaceCollectionNetworkInterfaceToAttach.getId());
            attachedNetworkInterfaceCollection.add(networkInterfaceCollectionNetworkInterfaceToAttach);
        }
        device.setNetworkInterfaceCollection(attachedNetworkInterfaceCollection);
        em.persist(device);
        for (NetworkInterface networkInterfaceCollectionNetworkInterface : device.getNetworkInterfaceCollection()) {
            Device oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface = networkInterfaceCollectionNetworkInterface.getDeviceId();
            networkInterfaceCollectionNetworkInterface.setDeviceId(device);
            networkInterfaceCollectionNetworkInterface = em.merge(networkInterfaceCollectionNetworkInterface);
            if (oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface != null) {
                oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface.getNetworkInterfaceCollection().remove(networkInterfaceCollectionNetworkInterface);
                oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface = em.merge(oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface);
            }
        }
        em.getTransaction().commit();
    } finally {
        if (em != null) {
            em.close();
        }
    }
}

【问题讨论】:

    标签: java jpa netbeans persistence


    【解决方案1】:

    这是集合数据成员的已知行为。 最简单的解决方案是修改您的集合 getter 以延迟创建集合。

    @XmlTransient
    public Collection<NetworkInterface> getNetworkInterfaceCollection() {
        if (networkInterfaceCollection == null) {
            networkInterfaceCollection = new Some_Collection_Type<NetworkInterface>();
        }
        return networkInterfaceCollection;
    }
    

    另外,请记住仅通过 getter 方法引用此数据成员。

    【讨论】:

    • 这是在 DeviceJpaController 创建中完成的。看看开头的if。如果集合为空,则它会创建一个新集合并使用device.setNetworkInterfaceCollection 方法对其进行设置。我试过你的建议,我得到:IllegalArgumentException: An instance of a null PK has been incorrectly provided for this find operation.
    【解决方案2】:

    @Dima K 的说法是正确的。当你这样做时:

        Device device = new Device("laptop");
        NetworkInterface net = new NetworkInterface("eth0");
    
        device.getNetworkInterfaceCollection().add(net);
        deviceController.create(device);
    

    设备中的集合尚未初始化,因此在尝试添加时会收到 NPE。在您的Device 类中,当声明您的Collection 时,您也可以对其进行初始化:

    private Collection<NetworkInterface> networkInterfaceCollection = new CollectionType<>();
    

    至于坚持,你的假设是正确的,但我认为执行是错误的。创建设备时,立即使用 JPA 使其持久化(在需要时进行事务管理)。

    Device device = new Device("laptop");
    getEntityManager().persist(device);
    

    对 NetworkInterface 执行相同的操作:

    NetworkInterface net = new NetworkInterface("eth0");
    getEntityManager().persist(net);
    

    现在,由于您的两个实体都已持久化,您可以将一个添加到另一个。

    device.getNetworkInterfaceCollection().add(net);

    JPA 应该负责其余的工作,而您不必调用任何其他持久化对象。

    【讨论】:

    • 您对集合初始化的看法都是正确的。但这是我的问题。当我只需要创建集合时,为什么我必须保留两个实体。 JPA 不应该将集合中的所有实体都持久化并将它们与父类相关联吗?
    • JPA 知道实体以及它们所处的状态。因此,创建设备实体和网络接口实体就是这样做的。 JPA 将了解两个实体。你有责任告诉它两者之间的关系。我理解你的意思,如果你持久化设备并在之后将元素添加到它的集合中,那么 JPA 也应该持久化这些。如果在您 persist 设备时填充集合(反之亦然),则设备及其集合元素将被持久化。
    【解决方案3】:

    此异常意味着您正在尝试定位尚未持久化的实体(可能通过 em.getReference())。 您不能在仍然没有 PK 的实体上使用 em.getReference() 或 em.find()。

    【讨论】:

    • 你是对的。它试图在 em.getReference() 调用中定位网络的 Id。 id 为空,因为该实体尚未持久化。请参阅@Sotirios Delimanolis 帖子中的评论。
    【解决方案4】:

    我终于明白了持久化一对多实体背后的逻辑。流程是:

    1. 创建父类
    2. 坚持下去
    3. 创建子类
    4. 将孩子与父母关联
    5. Persist child(父集合更新)

    带代码:

    public class Main {
        public static void main(String[] args) {
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("wifi-dbPU");
            DeviceJpaController deviceController = new DeviceJpaController(emf);
            NetworkInterfaceJpaController netController = new NetworkInterfaceJpaController(emf);
    
            Device device = new Device("laptop");                 // 1
            deviceController.create(device);                      // 2
    
            NetworkInterface net = new NetworkInterface("eth0");  // 3
            net.setDeviceId(device.getId());                      // 4
            netController.create(net);                            // 5 
            // The parent collection is updated by the above create     
        }
    }
    

    现在,我可以找到一个设备(例如带有 id 的设备),我可以使用它的所有子设备

    Collection<NetworkInterface> netCollection = device.getNetworkInterfaceCollection()
    

    在我在问题中发布的设备实体类中,不需要addNetworkInterfaceremoveNetwokrInterface 方法。

    【讨论】:

    • 根据我从书中读到的。 cascade=CascadeType.Persist 应该保留所有关系,您只需要更新一个实体,JPA 将通过关系导航并更新关联的实体。但是......我无法让它工作......
    【解决方案5】:

    为了在 @OneToMany 关系上启用保存功能,例如

    @OneToMany(mappedBy="myTable", cascade=CascadeType.ALL) 
    private List<item> items;
    

    然后你必须告诉你的 @ManyToOne 关系,它允许像这样更新 myTable 可更新 = true

    @ManyToOne @JoinColumn(name="fk_myTable", nullable = false, updatable = true, insertable = true)
    

    【讨论】:

    • updatable = true, insertable = true 无论如何都是默认值,所以这并不能解决问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-28
    • 2015-09-05
    • 2016-06-10
    • 2011-11-28
    • 2023-03-20
    • 1970-01-01
    • 2021-07-22
    相关资源
    最近更新 更多