2015-06-22 44 views
5

Podano poniżej relację jeden-do-wielu z Department do Employee.Scalanie zarządzanego obiektu po stronie @ManyToOne

Department (rodzic):

@OneToMany(mappedBy = "department", fetch = FetchType.LAZY, cascade = CascadeType.ALL) 
private List<Employee> employeeList = new ArrayList<Employee>(0); 

pracownika (dziecko):

@JoinColumn(name = "department_id", referencedColumnName = "department_id") 
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH}) 
private Department department; 

Scalanie zarządzanego podmiotu (child) Podobnie jak następuje (używając CMT w EJB),

Employee employee = entityManager.find(Employee.class, 1L); 
employee.setDepartment(department); // department is supplied by a client. 
employee.setEmployeeName("xyz"); 
entityManager.merge(employee); 

nie aktualizuje odpowiedniego wiersza pracownika w tabeli bazy danych. Zdarza się tylko wtedy, gdy CascadeType.MERGE zostanie usunięty z relacji podrzędnej @ManyToOne w Employee.

Dlaczego wiersz w tabeli nie jest aktualizowany? Jaki jest wyłącznie cel CascadeType.MERGE dotyczący tego przykładu?

Aktualnie używam EclipseLink 2.6.0 z JPA 2.1.

+0

Myślę, że jest to błąd w EclipseLink. Czy próbowałeś już innych wersji EclipseLink lub Hibernate? Celem 'CascadeType.MERGE' w tym przykładzie jest, jak zwykle, wyzwolenie również' CascadeType.MERGE' w polu 'department'. –

+0

Nawet usunięcie tego wiersza 'entityManager.merge (pracownik);' powinno zaktualizować wiersz w bazie danych, ponieważ jest to obiekt zarządzany.Usunięcie tej linii powoduje jednak, że cała lista pracowników powiązanych z 'Wydziałem' (' employee.setDepartment (department);) zostanie ponownie wstawiona, chyba że 'CascadeType.PERSIST' zostanie usunięty z' @ ManyToOne' w 'Pracownik '. ** W Hibernate działa to doskonale. ** Dlatego jest to błąd na pewno w EclipseLink. – Tiny

+0

Mimo to stworzyłem błąd na [Bugzilla] (https://bugs.eclipse.org/bugs/show_bug.cgi?id=470697). – Tiny

Odpowiedz

2

Kaskadowanie zawsze powinno być propagate from a Parent to a Child, a nie odwrotnie.

W twoim przypadku trzeba usunąć kaskady z dziecięcą strony:

@JoinColumn(name = "department_id", referencedColumnName = "department_id") 
@ManyToOne(fetch = FetchType.LAZY) 
private Department department; 

i upewnij się, że ustawione po obu stronach stowarzyszenia:

Employee employee = entityManager.find(Employee.class, 1L); 
employee.setDepartment(department); 
employee.setEmployeeName("xyz"); 
department.getEmployeeList().add(employee); 
entityManager.merge(department); 
1

Ten kod jest ryzykowne, ponieważ kojarzysz odłączoną instancję działu, która prawdopodobnie ma oddzielną kolekcję pracowników. Jeśli aktualny pracownik o nowej nazwie Xyz znajduje się na tej liście, jego zmiany zostaną zastąpione przez nazwę odłączonej instancji.

na przykład po wywołaniu employee.setDepartment (department); pracownik (1L) -> dział "-> pracownik (1L)"

Wywołanie scalenia na instancji pracownika (1L) nic nie zmieni, ponieważ zmiana nazwy jest już widoczna, ale zostanie przeniesiona do działu. Dział scalania następnie kaskaduje do instancji pracownika (1L), która ma starą nazwę. Jeśli sprawdzisz wartość employee.getEmployeeName(), zobaczysz, że scalenie spowodowało jej zresetowanie, co prawdopodobnie powoduje, że nie widzisz aktualizacji bazy danych.

Nie wywoływanie scalania nie jest opcją, ponieważ nadal pracownik odwołuje się do oderwanego oddziału, co ma być wyjątkowym przypadkiem. Właśnie dlatego dostawca wydaje inserty.

Prawdopodobnie masz zestaw kaskadowo scalony na relacjach, ponieważ obiekt działu dostarczony przez klienta może również zawierać zmiany pracownika. Jeśli tak, aby zsynchronizować te zmiany i zmienić EmployeeName, użyłbyś:

Department managedDepartment = entityManager.merge(department); 
Employee employee = entityManager.find(Employee.class, 1L); 
employee.setDepartment(managedDepartment); 
managedDepartment.getEmployeeList().add(employee); 
employee.setEmployeeName("xyz"); 

to oba mogą dodać pracownika do działu jeśli go tam nie ma, a jeszcze dokonać zmiany nazwy, jeżeli jest.

+0

Dzięki, Chris. Udało mi się złapać techniczny błąd w kodzie. – Tiny

+0

'employee.setDepartment (department);' should be 'employee.setDepartment (managedDepartment);'. Myślę, że powinien to być typograficzny błąd. – Tiny

1

Czytam również inne odpowiedzi i problem z bugzillą, ale nadal jestem przekonany, że to zachowanie jest błędem, a przynajmniej brakiem funkcji.

Proszę śledzić moje myślenie:

Employee employee = entityManager.find(Employee.class, 1L); 

pracownik jest zarządzany podmiot;

employee.setDepartment(department); // department is supplied by a client. 

oddział jest jednostką odłączoną;

ponieważ pracownik jest już zarządzany, łączenie pracownika wywoła tylko kaskadę łączenia w dziale.
Więc:

merge(department) 

Teraz depatment zarządzany jest zbyt; a to spowoduje kaskadę na pracowników:

merge(employee1) 
merge(employee2) 
... 
merge(employeeN) 

ale istnieją dwa różne scenariusze:

  1. department.getEmployeeList jest już pobrane
    to zawiera oderwane podmioty: scalanie dołączy pracownika # i nie powinien być wywoływać kaskady w dziale, ponieważ został już wywołany (w przeciwnym razie generowanie nieskończonego cyklu).
    Należy pamiętać, że pracownik nie jest zawarty w EmployList.pl pod numerem.
    W tym przypadku wszystko musi działać.
  2. department.getEmployeeList jest jeszcze idące i jest leniwy załadowane teraz
    to zawiera zarządzane podmioty: scalanie powinien robić nic od pracowników # są już udało i kaskada na wydziale została już wywołana.
    Ale ..

W tym przypadku pracownik jest zawarty w EmployerList?

trzy możliwości (w zależności od innych czynników, takich jak podtynkowego trybie auto-commit, ...):

  1. no: wszystko powinno działać
  2. tak, i to jest ta sama instancja: wszystko powinno działać
  3. tak, ale to inna instancja: prawdopodobnie prowadzi do zmiany nadpisać

Myślę, że „błąd” może być tutaj, gdy leniwy ładowania: nie powinno być possibl e mieć dwie zarządzane instancje tego samego obiektu w tej samej jednostce EntityManager.

Nie mogę myśleć o jednym kontrprzykładu.

+1

Niezależnie od tego, czy jest to błąd, czy nie, społeczność programistów dodaje nowe funkcje dość szybko, ale nie ma problemu z naprawianiem błędów, jeśli takie istnieją :) – Tiny

+1

Masz rację, w szczególności dla EclipseLink. Może pewnego dnia zawali się w czarnej dziurze, połykając wszystkie nasze aplikacje ... –