Aris F. Aris F. - 4 months ago 18
Java Question

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


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

Answer

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