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

时间:2013-01-02 22:11:31

标签: java jpa netbeans persistence

配置

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

5 个答案:

答案 0 :(得分:22)

我终于理解了坚持一个到多个实体的逻辑。这个过程是:

  1. 创建父类
  2. 坚持下去
  3. 创建子类
  4. 将孩子与父母联系
  5. Persist child(父系列已更新)
  6. 使用代码:

    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

答案 1 :(得分:4)

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

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

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

设备中的集合尚未初始化,因此您在尝试添加NPE时会获得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应该负责其余的工作而不必再打电话给任何其他人。

答案 2 :(得分:2)

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

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

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

答案 3 :(得分:0)

此异常意味着您正在尝试查找尚未保留的实体(可能是em.getReference())。 你不能在没有PK的实体上使用em.getReference()或em.find()。

答案 4 :(得分:-1)

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

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

然后您必须告诉您的@ManyToOne关系,允许更新myTable,如此updatable = true

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