Подтвердить что ты не робот

JPA сохраняют сущности с отношением один к одному

Конфигурация

  • EcliplseLink 2.3.2
  • JPA 2.0
  • Объекты автоматически создаются из схемы db из netbeans с помощью класса Entity Classes from Database....
  • Классы контроллера автоматически создаются из netbeans с классами контроллера JPA из классов сущностей... wizard

Короткий вариант вопроса

В классическом сценарии две таблицы с отношением друг к другу. Я создаю родительский объект, потом дочерний объект, и я присоединяю его к родительской коллекции. Когда я создаю (метод контроллера) родительский объект, я ожидаю, что дочерний объект будет создан и связан с родителем. Почему это не происходит?

Длинная версия

Родительский класс

@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. Я ожидал, что он напишет device в db, получит идентификатор устройства, привяжет его к net и напишет его в db.

Вместо этого я обнаружил, что это шаги, которые я должен выполнить:

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

Почему мне нужно создать дочерний элемент, когда родительский класс знает его дочерним, и он должен создать его для меня?

Создайте метод из DeviceJpaController (извините за длинные имена в полях, они автоматически сгенерированы).

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

Ответ 1

Наконец-то я понял логику сохранения одного-многих объектов. Процесс:

  • Создать родительский класс
  • Сохранять это
  • Создать дочерний класс
  • Связать с родителем дочерний элемент
  • Персистский дочерний элемент (обновлены родительские коллекции)

С кодом:

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

Теперь я могу найти устройство (например, с идентификатором), и я могу получить все его дочерние элементы, используя

Collection<NetworkInterface> netCollection = device.getNetworkInterfaceCollection()

В классе сущности устройства, который я разместил в вопросе, нет необходимости в методах addNetworkInterface и removeNetwokrInterface.

Ответ 2

@Dima K правильна в том, что они говорят. Когда вы это сделаете:

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

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

Сбор в устройстве не был инициализирован, поэтому вы пытаетесь добавить к нему 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 следует позаботиться об остальном, если вы не будете называть какие-либо другие проблемы.

Ответ 3

Это известное поведение членов данных коллекции. Самое простое решение - изменить ваш сборщик, чтобы лениво создать коллекцию.

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

Кроме того, не забудьте обратиться к этому элементу данных только через метод getter.

Ответ 4

Это исключение означает, что вы пытаетесь найти сущность (возможно, em.getReference()), которая еще не была сохранена. Вы не можете использовать em.getReference() или em.find() для объектов, которые до сих пор не имеют PK.

Ответ 5

Чтобы включить возможность сохранения в отношении @OneToMany, например.

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

Затем вы должны указать своему отношению @ManyToOne, что разрешено обновлять myTable, как this updatedatable = true

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