简体   繁体   English

JPA持有具有一对多关系的实体

[英]JPA persist entities with one to many relation

Config 配置

  • EcliplseLink 2.3.2 EcliplseLink 2.3.2
  • JPA 2.0 JPA 2.0
  • The entities are auto created from the db schema from netbeans with Entity Classes from Database... wizard. 这些实体是从带有Entity Classes from Database ...向导的netbeans的db模式自动创建的。
  • The controller classes are auto created from netbeans with JPA Controller Classes from Entity Classes... wizard 控制器类是从netbeans自动创建的,具有来自Entity Classes ...向导的JPA Controller Classes

Short version of question 问题的简短版本

In a classic scenario, two tables with one to many relation. 在经典场景中,两个表具有一对多关系。 I create the parent entity, then the child entity and I attach the child to the parent's collection. 我创建父实体,然后创建子实体,并将子项附加到父项的集合。 When I create (controller method) the parent entity, I expect the child entity to be created to and associated with parent. 当我创建 (控制器方法)父实体时,我希望子实体被创建并与父关联。 Why doesn't it happen? 为什么不发生?

Long version 长版

Parent class 家长班

@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
}

Child class 儿童班

@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;
    }
}

Main class 主要课程

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);
    }
}

This class throws a NullPointerException in line: device.getNetworkInterfaceCollection().add(net); 此类在行中抛出NullPointerException: device.getNetworkInterfaceCollection().add(net);

The system knows that there is a new entity device and it has an element net in it's collection. 系统知道有一个新的实体device并且它的集合中有一个元素net I expected it to write device in db, get device's id, attach it to net and write it in db. 我希望它在db中编写device ,获取设备的id,将其附加到net并在db中写入。

Instead of this, I found that these are the steps I have to do: 而不是这样,我发现这些是我必须要做的步骤:

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

Why do I have to create the child when the parent class knows it's child and it should create it for me? 为什么我必须在父类知道它的孩子时创建孩子并且应该为我创建它?

The create method from DeviceJpaController (sorry for the long names in fields, they are auto generated). 来自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();
        }
    }
}

I finally understood the logic behind persisting one to many entities. 我终于理解了坚持一个到多个实体的逻辑。 The process is: 过程是:

  1. Create parent class 创建父类
  2. Persist it 坚持下去
  3. Create child class 创建子类
  4. Associate child with parent 将孩子与父母联系起来
  5. Persist child (the parent collection is updated) 持续孩子(父母集合更新)

With code: 使用代码:

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     
    }
}

Now, I can find a device (with id for example) and I can get all its children using 现在,我可以找到一个设备(例如id),我可以让它的所有孩子都使用

Collection<NetworkInterface> netCollection = device.getNetworkInterfaceCollection()

In the device entity class, which I posted in the question, there is no need for the methods addNetworkInterface and removeNetwokrInterface . 在我在问题中发布的设备实体类中,不需要方法addNetworkInterfaceremoveNetwokrInterface

@Dima K is correct in what they say. @Dima K说的很正确。 When you do this: 当你这样做:

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

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

The collection in device hasn't been initialized and so you get a NPE when trying to add to it. 设备中的集合尚未初始化,因此您在尝试添加NPE时会获得NPE。 In your Device class, when declaring your Collection , you can also initialize it: 在您的Device类中,在声明Collection ,您也可以初始化它:

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

As for persisting, your assumptions are correct but I think the execution is wrong. 至于坚持,你的假设是正确的,但我认为执行是错误的。 When you create your device, make it persistent with JPA right away (doing transaction management wherever needed). 创建设备时,立即使用JPA使其保持持久性(在需要的地方进行事务管理)。

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

Do the same for the NetworkInterface: 对NetworkInterface执行相同的操作:

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

Now since both your entities are persisted, you can add one to the other. 现在,由于两个实体都是持久的,因此您可以将其中一个添加到另一个实体。

device.getNetworkInterfaceCollection().add(net);

JPA should take care of the rest without you having to call any other persists. JPA应该照顾其余的,而不必再打电话给任何其他人。

This is a known behavior of collection data members. 这是集合数据成员的已知行为。 The easiest solution is to modify your collection getter to lazily create the collection. 最简单的解决方案是修改您的集合getter以懒洋洋地创建集合。

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

Also, remember to refer to this data member only through the getter method. 另外,请记住仅通过getter方法引用此数据成员。

This exception means you're trying to locate an entity (probably by em.getReference()) that hasn't been persisted yet. 此异常意味着您正在尝试查找尚未保留的实体(可能是em.getReference())。 You cannot you em.getReference() or em.find() on entities which still don't have a PK. 对于仍然没有PK的实体,你不能em.getReference()或em.find()。

In order to enable save ability on a @OneToMany relation eg 为了在@OneToMany关系上启用保存能力,例如

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

Then you have to tell to your @ManyToOne relation that it is allowed to update myTable like this updatable = true 然后你必须告诉你的@ManyToOne关系,它允许更新myTable,就像这个updatable = true

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM