2013-01-02 10 views
11

ConfigJPA utrzymują podmioty z jednego do wielu relacji

  • EcliplseLink 2.3.2
  • JPA 2.0
  • Podmioty są automatycznie tworzone ze schematu db od netbeans z Entity klas z bazy ... Czarodziej.
  • Klasy kontrolera są automatycznie tworzone z netbeans z JPA Controller klas z klas Entity ... kreatora

Krótka wersja pytanie

W klasycznym scenariuszu, dwa stoły z jednego do wiele relacji. Tworzę obiekt nadrzędny, a następnie element podrzędny i dołączam dziecko do kolekcji rodzica. Kiedy I utworzyć (metoda kontrolera) nadrzędnej jednostki, oczekuję, że obiekt podrzędny zostanie utworzony i powiązany z rodzica. Dlaczego tak się nie dzieje?

Długa wersja

klasa nadrzędna

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

klasa Child

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

Główna klasa

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

Ten cl ass rzuca NullPointerException w linii: device.getNetworkInterfaceCollection().add(net);

System wie, że nie jest to nowa jednostka device i ma element net w jego kolekcji. Spodziewałem się, że zapisze w db, device, otrzyma identyfikator urządzenia, dołącz go do net i zapisz go w db.

Zamiast tego, znalazłem, że są to kroki muszę zrobić:

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

Dlaczego muszę stworzyć dziecku, gdy klasa dominująca wie, że to dziecko i powinna stworzyć to dla mnie ?

Metoda tworzenia z DeviceJpaController (przepraszam za długie nazwy w polach, są one generowane automatycznie).

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

Odpowiedz

21

W końcu zrozumiałem logikę stojącą za utrzymaniem jednego do wielu obiektów. Proces jest:

  1. Tworzenie klasy nadrzędnej
  2. Utrzymują to
  3. Tworzenie klasy dziecięcej
  4. Associate dziecko z jego rodzicem
  5. Utrzymują dziecko (zbiory rodzic jest aktualizowany)

Z kodem:

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

Teraz mogę znaleźć urządzenia (z identyfikatorem na przykład) i mogę dostać wszystko to dziecko za pomocą

Collection<NetworkInterface> netCollection = device.getNetworkInterfaceCollection() 

w klasie encji urządzenie, które napisałem w pytaniu, nie ma potrzeby stosowania metod addNetworkInterface i removeNetwokrInterface.

+1

Zgodnie z tym, co przeczytałem z książki. 'cascade = CascadeType.Persist' powinien utrzymywać wszystkie relacje i wystarczy tylko zaktualizować jedną encję, JPA będzie nawigował przez relacje i aktualizował powiązane jednostki. Ale ... Nie mogłem tego zrobić ... –

2

Jest to znane zachowanie członków danych kolekcji. Najprostszym rozwiązaniem jest zmodyfikowanie kolekcji getter, aby leniwie utworzyć kolekcję.

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

Pamiętaj też, aby odnieść się do tego elementu danych tylko za pomocą metody getter.

+0

Odbywa się to w 'DeviceJpaController' stworzyć. Zobacz, jeśli na początku. Jeśli kolekcja ma wartość NULL, tworzy nową i ustawia ją za pomocą metody 'device.setNetworkInterfaceCollection'. Próbowałem Twojej sugestii i otrzymuję: 'IllegalArgumentException: Wystąpienie niepoprawnego PK zostało niepoprawnie podane dla tej operacji wyszukiwania." –

4

@Dima K ma rację w tym, co mówią.Gdy to zrobisz:

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

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

Kolekcja urządzenia nie została zainicjalizowana, więc otrzymujesz NPE podczas próby dodania. W swojej klasie Device, gdy deklarując swoją Collection, można również zainicjować go:

private Collection<NetworkInterface> networkInterfaceCollection = new CollectionType<>(); 

Co do utrzymującej Twoje założenia są prawidłowe, ale myślę, że wykonanie jest złe. Kiedy tworzysz swoje urządzenie, spraw, by było trwałe z JPA od razu (wykonując zarządzanie transakcjami tam, gdzie jest to konieczne).

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

Zrób to samo dla NetworkInterface:

NetworkInterface net = new NetworkInterface("eth0"); 
getEntityManager().persist(net); 

Teraz ponieważ oba wasze jednostki są zachowywane, można dodać jeden do drugiego.

device.getNetworkInterfaceCollection().add(net);

JPA powinna zająć się resztą bez konieczności zadzwonić innych ustąpi.

+1

Obaj macie rację co do inicjowania kolekcji. Ale to jest moje pytanie.Dlaczego muszę utrzymywać dwie jednostki, gdy wszystko, czego potrzebuję, to tworzenie kolekcji. Czy WZP nie powinien przechowywać wszystkich elementów w kolekcji i kojarzyć je z klasą nadrzędną? –

+0

JPA wie o encji i jakim są stanie. W ten sposób tworzy się encję urządzenia i element interfejsu sieciowego. WZP będzie wiedział o dwóch podmiotach. Twoim obowiązkiem jest powiedzieć mu relację między tymi dwoma. Rozumiem, co masz na myśli mówiąc, że jeśli utrzymasz urządzenie i dodasz elementy do jego kolekcji, JPA również powinno je utrwalić. Jeśli kolekcja zostanie zapełniona, gdy "utrzymasz" urządzenie (lub odwrotnie), urządzenie i jego elementy kolekcji będą utrwalone. –

0

Ten wyjątek oznacza, że ​​próbujesz zlokalizować encję (prawdopodobnie przez em.getReference()), która nie została jeszcze utrwalona. Nie możesz użyć em.getReference() lub em.find() dla obiektów, które nadal nie mają PK.

+0

Masz rację. Próbuje zlokalizować identyfikator sieci w wywołaniu em.getReference(). Identyfikator jest pusty, ponieważ podmiot nie został jeszcze utrwalony. Proszę zobaczyć komentarz na @Sotirios Delimanolis post. –

-1

Aby włączyć funkcję zapisywania w relacji @OneToMany, np.

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

Następnie trzeba powiedzieć do @ManyToOne stosunku, że może aktualizować mojatabela tak aktualizowalny = true

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

'updatable = true, insertable = true' są wartościami domyślnymi, więc to nie rozwiązuje problemu. –

Powiązane problemy