Blog coding and discussion of coding about JavaScript, PHP, CGI, general web building etc.

Saturday, August 6, 2016

JPA persist entities with one to many relation

JPA persist entities with one to many relation


Config

  • EcliplseLink 2.3.2
  • JPA 2.0
  • The entities are auto created from the db schema from netbeans with Entity Classes from Database... wizard.
  • The controller classes are auto created from netbeans with JPA Controller Classes from Entity Classes... wizard

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 networkInterfaceCollection;        public Device() {      }        public Device(String name) {          this.name = name;          updated = new Date();      }        // setters and getters...        @XmlTransient      public Collection getNetworkInterfaceCollection() {          return networkInterfaceCollection;      }        public void setNetworkInterfaceCollection(Collection 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);

The system knows that there is a new entity device and it has an element net in it's collection. I expected it to write device in db, get device's id, attach it to net and write it in 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).

public EntityManager getEntityManager() {      return emf.createEntityManager();  }    public void create(Device device) {      if (device.getNetworkInterfaceCollection() == null) {          device.setNetworkInterfaceCollection(new ArrayList());      }      EntityManager em = null;      try {          em = getEntityManager();          em.getTransaction().begin();          Collection attachedNetworkInterfaceCollection = new ArrayList();          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();          }      }  }  

Answer by Dima K for JPA persist entities with one to many relation


This is a known behavior of collection data members. The easiest solution is to modify your collection getter to lazily create the collection.

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

Also, remember to refer to this data member only through the getter method.

Answer by Sotirios Delimanolis for JPA persist entities with one to many relation


@Dima K is correct in what they say. 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. In your Device class, when declaring your Collection, you can also initialize it:

private Collection 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).

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

Do the same for the 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.

Answer by Dima K for JPA persist entities with one to many relation


This exception means you're trying to locate an entity (probably by em.getReference()) that hasn't been persisted yet. You cannot you em.getReference() or em.find() on entities which still don't have a PK.

Answer by Aris F. for JPA persist entities with one to many relation


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 it's parent
  5. Persist child (the parent collections 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 it's child using

Collection netCollection = device.getNetworkInterfaceCollection()  

In the device entity class which I posted in the question, there is no need for the methods addNetworkInterface and removeNetwokrInterface.

Answer by Ilan M for JPA persist entities with one to many relation


In order to enable save ability on a @OneToMany relation e.g.

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

Then you have to tell to your @ManyToOne relation that it is allowed to update myTable like this updatable = true

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


Fatal error: Call to a member function getElementsByTagName() on a non-object in D:\XAMPP INSTALLASTION\xampp\htdocs\endunpratama9i\www-stackoverflow-info-proses.php on line 72

0 comments:

Post a Comment

Popular Posts

Powered by Blogger.