2011-11-27 10 views
8

Mam Parent podmiot z Child podmiotu w ManyToOne zależność:Jak uniknąć duplikatów z kaskadami JPA?

@Entity class Parent { 
    // ... 
    @ManyToOne((cascade = {CascadeType.ALL}) 
    private Child child; 
    // ... 
} 

Dziecko posiada unikalny murawę:

@Entity class Child { 
    // ... 
    @Column(unique = true) 
    private String name; 
    // ... 
} 

Kiedy potrzebny jest nowy Dziecko, pytam o jodłę ChildDAO st:

Child child = childDao.findByName(name); 
if(child == null) { 
    child = new Child(name); 
} 

Parent parent = new Parent(); 
parent.setChild(child); 

Problem jest, jeśli zrobić jak wyżej dwukrotnie (o tej samej nazwie na dziecka), a tylko utrwalić Parent na koniec, mam ograniczenie wyjątku. Co wydaje się normalne, ponieważ początkowo w bazie danych nie było dziecka o określonej nazwie.

Problem polega na tym, że nie jestem pewien, jaki byłby najlepszy sposób na uniknięcie tej sytuacji.

+2

Jesteś pewien, że Twój model jest w porządku? Dziecko może mieć wielu Rodziców, a Rodzic może mieć tylko jedno Dziecko? –

+0

Moja odpowiedź zakłada związek OneToOne - powyższy komentarz jest absolutnie poprawny, jest to dziwna nazwa, jeśli jest poprawna. –

+0

Tak, model jest poprawny. Zmieniłem nazwy obiektów na przykład i wygląda na to, że mogłem zrobić to lepiej. – Cos64

Odpowiedz

9

Tworzysz dwie nietrwałe instancje dziecka z new Child() dwa razy, a następnie umieszczasz je u dwóch różnych Rodziców. Po utrwaleniu obiektów nadrzędnych każda z dwóch nowych instancji potomnych zostanie utrwalona/wstawiona za pomocą kaskady, każda z inną wersją @Id. Unikalne ograniczenie nazwy następnie zostanie przerwane. Jeśli robisz CascadeType.ALL, za każdym razem, gdy robisz new Child(), możesz otrzymać oddzielny obiekt trwały.

Jeśli naprawdę chcesz, aby dwie instancje podrzędne były traktowane jako jeden obiekt trwały o tym samym identyfikatorze, musisz zachować go oddzielnie, aby powiązać go z kontekstem/sesją utrwalania. Kolejne wywołania do childDao.findByName będą następnie wypłukiwać wkładkę i zwracać nowo utworzone dziecko, aby dwukrotnie nie wykonywać new Child().

1

Ustawiasz obiekt podrzędny, jeśli pozostanie rodzicem przechowujesz rejestr w bazie danych, który wskazuje na nieistniejące dziecko.

Podczas tworzenia nowego obiektu musi on być zarządzany przez element entityManager w taki sam sposób, gdy "odnajdzie" obiekt za pomocą DAO, musi pobrać rejestr z bazy danych i umieścić obiekt w kontekście entityManager.

Spróbuj najpierw utrwalić lub scalić obiekt potomny.

+0

W polu potomnym nadrzędnego znajduje się zestaw "CascadeType.ALL". Nie jest wymagane wyraźne scalanie. –

6

Otrzymujesz to, ponieważ próbujesz utrwalić obiekt, który już istnieje (ten sam identyfikator). Twoja kaskada prawdopodobnie nie jest trwała to MERGE/UPDATE/REMOVE/DETACH. Najlepszym sposobem na uniknięcie tej sytuacji jest prawidłowe ustawienie kaskady lub ręczne zarządzanie kaskadami. Zasadniczo kaskadowe uporczywy jest zwykłym winowajcą.

Ty prawdopodobne chce coś takiego:

//SomeService--I assume you create ID's on persist 
saveChild(Parent parent, Child child) 
{ 
    //Adding manually (using your cascade ALL) 
    if(child.getId() == null) //I don't exist persist 
    persistence.persist(child); 
    else 
    persistence.merge(child); //I'm already in the DB, don't recreate me 

    parent.setChild(child); 
    saveParent(parent); //using a similar "don't duplicate me" approach. 
} 

Kaskady może być niezwykle frustrujące jeśli pozwolisz ramy zarządzania nim, ponieważ będziesz czasami przegap aktualizacji jeśli jest buforowanie (szczególnie z kolekcji w ManyToOne relacje). Jeśli nie zapisujesz jawnie obiektu nadrzędnego i nie zezwalasz frameworkowi na obsługę kaskad. Zasadniczo zezwalam na Cascade.ALL od mojego rodzica w związku i kaskadę wszystkich przez DELETE/PERSIST od moich dzieci. Jestem użytkownikiem Eclipselink, więc moje doświadczenie z Hibernate/other jest ograniczone i nie mogę mówić do buforowania. * Naprawdę, pomyśl o tym - jeśli oszczędzasz dziecko, czy naprawdę chcesz zapisać wszystkie obiekty z nim związane? Ponadto, jeśli dodajesz dziecko, czy nie powinieneś powiadomić rodzica? *

W twoim przypadku po prostu miałbym metodę "saveParent" i "saveChild" w moim Dao/Service, która upewniała się, że pamięć podręczna jest poprawna, a baza danych jest poprawna, aby uniknąć bólu głowy. Zarządzanie ręczne oznacza, że ​​będziesz mieć absolutną kontrolę i nie będziesz musiał polegać na kaskadach, aby wykonać brudną robotę. W jednym kierunku są fantastyczne, z chwilą, gdy nadejdą "w górę", będziesz miał nieoczekiwane zachowania.

3

Jeśli utworzysz dwa obiekty i pozwolisz, aby JPA je automatycznie zatrzymywał, to dostaniesz dwa wiersze w bazie danych. Masz dwie opcje: nie twórz dwóch obiektów lub nie pozwól, aby JPA je automatycznie utrzymywał.

Aby uniknąć tworzenia dwóch obiektów, musisz ułożyć kod, więc jeśli dwóch Rodziców spróbuje stworzyć Dzieci o tej samej nazwie, otrzymają one tę samą rzeczywistą instancję. Prawdopodobnie będziesz potrzebował WeakHashMap (z zakresem na bieżące żądanie, być może przez odwoływanie się do zmiennych lokalnych), z kluczowaniem według nazwy, gdzie rodzice mogą sprawdzić swoje nowe imię dziecka, aby sprawdzić, czy dziecko już istnieje. Jeśli nie, mogą stworzyć obiekt i umieścić go na mapie.

Aby uniknąć sytuacji, w której JPA będzie automatycznie utrzymywał obiekty, upuść kaskadę i użyj persist, aby ręcznie dodać obiekty do kontekstu natychmiast po utworzeniu.

Ponieważ kontekst utrwalania jest po prostu podstępem WeakHashMap dołączonym do bazy danych, podejścia te są bardzo podobne, jeśli chodzi o to.

Powiązane problemy