Config
Version courte de la question
Dans un scénario classique, deux tables avec une relation à plusieurs. Je crée l'entité parent, puis l'entité enfant et j'attache l'enfant à la collection du parent. Lorsque je crée (méthode du contrôleur) l'entité parent, je m'attends à ce que l'entité enfant soit créée et associée à un parent. Pourquoi ça n'arrive pas?
Version longue
Classe parent
@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
}
Classe enfant
@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;
}
}
Classe principale
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);
}
}
Cette classe lève une exception NullPointerException en ligne: device.getNetworkInterfaceCollection().add(net);
Le système sait qu'il existe une nouvelle entité device
et qu'il contient un élément net
dans sa collection. Je m'attendais à ce qu'il écrive device
dans la base de données, récupère l'identifiant du périphérique, l'attache à net
et l'écrit dans la base de données.
Au lieu de cela, j'ai constaté que ce sont les étapes que je dois faire:
deviceController.create(device);
net.setDeviceId(device);
device.getNetworkInterfaceCollection().add(net);
netController.create(net);
Pourquoi dois-je créer l'enfant quand la classe de parents sait que c'est son enfant et qu'elle devrait le créer pour moi?
La méthode create de DeviceJpaController (désolé pour les noms longs dans les champs, ils sont générés automatiquement).
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();
}
}
}
J'ai finalement compris la logique derrière la persistance d'une à plusieurs entités. Le processus est:
Avec 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
}
}
Maintenant, je peux trouver un appareil (avec un identifiant par exemple) et je peux obtenir tous ses enfants en utilisant
Collection<NetworkInterface> netCollection = device.getNetworkInterfaceCollection()
Dans la classe d'entité de périphérique, que j'ai publiée dans la question, les méthodes addNetworkInterface
et removeNetwokrInterface
ne sont pas nécessaires.
@Dima K est correct dans ce qu'ils disent. Quand tu fais ça:
Device device = new Device("laptop");
NetworkInterface net = new NetworkInterface("eth0");
device.getNetworkInterfaceCollection().add(net);
deviceController.create(device);
La collection dans le périphérique n'a pas été initialisée et vous obtenez donc un NPE lorsque vous essayez de l'ajouter. Dans votre classe Device
, lorsque vous déclarez votre Collection
, vous pouvez également l'initialiser:
private Collection<NetworkInterface> networkInterfaceCollection = new CollectionType<>();
En ce qui concerne la persistance, vos hypothèses sont correctes mais je pense que l'exécution est fausse. Lorsque vous créez votre appareil, rendez-le immédiatement persistant avec JPA (en effectuant la gestion des transactions si nécessaire).
Device device = new Device("laptop");
getEntityManager().persist(device);
Faites la même chose pour la NetworkInterface:
NetworkInterface net = new NetworkInterface("eth0");
getEntityManager().persist(net);
Maintenant, puisque vos deux entités sont persistantes, vous pouvez en ajouter une à l’autre.
device.getNetworkInterfaceCollection().add(net);
JPA devrait s'occuper du reste sans que vous ayez à appeler aucun autre persiste.
Il s'agit d'un comportement connu des membres de données de collection . La solution la plus simple consiste à modifier votre getter de collection pour créer la collection paresseusement.
@XmlTransient
public Collection<NetworkInterface> getNetworkInterfaceCollection() {
if (networkInterfaceCollection == null) {
networkInterfaceCollection = new Some_Collection_Type<NetworkInterface>();
}
return networkInterfaceCollection;
}
En outre, n'oubliez pas de faire référence à ce membre de données uniquement via la méthode getter.
Cette exception signifie que vous essayez de localiser une entité (probablement par em.getReference ()) qui n'a pas encore été persistée . Vous ne pouvez pas utiliser em.getReference () ou em.find () sur des entités qui ne ne pas avoir un PK.