配置
问题的简短版本
在经典场景中,两个表具有一对多关系。我创建父实体,然后创建子实体,并将子项附加到父项的集合。当我创建(控制器方法)父实体时,我希望将子实体创建到父级并与之关联。为什么不发生?
长版
父类
@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();
}
}
}
答案 0 :(得分:22)
我终于理解了坚持一个到多个实体的逻辑。这个过程是:
使用代码:
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()
在我在问题中发布的设备实体类中,不需要方法addNetworkInterface
和removeNetwokrInterface
。
答案 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)