2008-10-07 11 views
20

W moim app mam te Hibernate odwzorowany typów (przypadek ogólny):Dlaczego program Hibernate próbuje usunąć podczas próby aktualizacji/wstawienia?

class RoleRule { 
    private Role role; 
    private PermissionAwareEntity entity; // hibernate-mapped entity for which permission is granted 
    private PermissionType permissionType; // enum 

    @ManyToOne 
    @JoinColumn(name = "ROLE_ID") 
    public Role getRole() { 
    return role; 
    } 
    public void setRole(Role role) { 
    this.role = role; 
    } 

} 

class Role { 
    private Set<RoleRule> rules = new HashSet<RoleRule>(0); 

    @OneToMany(cascade=CascadeType.ALL) 
    @JoinColumn(name="ROLE_ID") 
    public Set<RoleRule> getRules() { 
    return rules; 
    } 
    public void setRules(Set<RoleRule> rules) { 
    this.rules = rules; 
    } 

} 

Wszystkie klasy mają equals() & hashCode() przesłonięcia.

Moja aplikacja umożliwia podkręcanie ról (tylko przez sysadmins, nie martw się), a wśród innych pól, pozwala tworzyć nowe reguły ról. Po utworzeniu nowej reguły próbuję utworzyć nowy obiekt RoleRule i wstawić go do pola roli rules. Dzwonię pod numer session.update(role), aby zastosować zmiany w bazie danych.

Teraz przychodzi brzydkie część ... Hibernacja zdecyduje się wykonać następujące czynności przy zamykaniu transakcji i zaczerwienienie:

  1. Włóż nową regułę do bazy danych. Doskonały.
  2. Zaktualizuj inne pola ról (nie kolekcje). Jak na razie dobrze.
  3. Zaktualizuj istniejące reguły, nawet jeśli nic w nich się nie zmieniło. Mogę z tym żyć.
  4. Zaktualizuj istniejące zasady ponownie. Oto pasta z dziennika, łącznie z automatycznym komentarza:
/* delete one-to-many row Role.rules */ 
update ROLE_RULE set ROLE_ID=null where ROLE_ID=? and ROLE_RULE_ID=?

Oczywiście, nie wszystkie pola są NULL, a ta operacja nie powiedzie się spektakularnie.

Czy ktoś może próbować wyjaśnić, dlaczego Hibernate to zrobił? A jeszcze ważniejsze jest to, jak się z tym obchodzę ???

EDIT: Byłem więc pewien, że to było coś zrobić z map, a następnie mój szef, dla kaprysu, usunięty equals() i hashCode() w obu klasach, odtworzył je przy użyciu Eclipse i tajemniczo to rozwiązało problem .

Nadal jestem bardzo ciekawa mojego pytania. Czy ktoś może zasugerować, dlaczego Hibernate by to zrobił?

+0

Który sterownik i dialekt są używane? –

+0

aplikacja działa na Oracle 10g, ale to nie powinno mieć znaczenia. – Yuval

+0

Najważniejsze, że w twoim poście brakuje implementacji metod 'równy' i' hashCode'. Budowanie metod 'równy' i' hashCode' w oparciu o wartości tożsamości bazy danych ** generowane przez hibernację lub bazę danych ** jest złym pomysłem, przez co są one zależne od dowolnego pola, które można zaktualizować po jeszcze gorszym czasie budowy. –

Odpowiedz

3

Nie jestem pewien, czy jest to rozwiązanie, ale można też spróbować:

@OneToMany(mappedBy = "role") 

I nie ma adnotacji @JoinColumn? Sądzę, że obie jednostki próbują "posiadać" powiązanie, dlatego SQL może być zawalony?

Ponadto, jeśli chcesz mieć pewność, tylko dotknięte kolumny aktualizowane, można użyć adnotacji hibernacji na specyficzne klasy:

@Entity 
@org.hibernate.annotations.Entity(
    dynamicInsert = true, dynamicUpdate = true 
) 
7

ja generalnie używane dwie metody aktualizowania kolekcji (wielu strona jeden-do-wielu) w trybie hibernacji. Brutalny sposób polega na wyczyszczeniu kolekcji, zapisaniu połączenia na rodzica, a następnie wywołanie koloru. Następnie dodaj ponownie wszystkich członków kolekcji i ponownie zapisz połączenie na rodzica. Spowoduje to usunięcie wszystkich, a następnie wstawienie wszystkich. Wypłukanie w środku jest kluczowe, ponieważ wymusza usunięcie przed wstawieniem. Najlepiej jest używać tej metody tylko w małych kolekcjach, ponieważ wszystkie one są ponownie wstawiane.

Drugi sposób jest trudniejszy do kodowania, ale bardziej wydajny.Przeglądasz nowy zestaw elementów podrzędnych i modyfikujesz te, które jeszcze istnieją, usuniesz te, które nie istnieją, a następnie dodajesz nowe. W pseudo-kodu, który będzie:

copy the list of existing records to a list_to_delete 
for each record from the form 
    remove it from the list_to_delete 
    if the record exists (based on equals()? key?) 
    change each field that the user can enter 
    else if the record doesn't exist 
    add it to the collection 
end for 
for each list_to_delete 
    remove it 
end for 
save 

Przeszukałem forum Hibernate przez wiele godzin, próbując znaleźć właściwą drogę, aby rozwiązać ten problem. Powinieneś móc po prostu zaktualizować swoją kolekcję, aby była dokładna, a następnie zapisać rodzica, ale jak już się dowiedziałeś, Hibernate próbuje odłączyć dzieci od rodzica przed ich usunięciem, a jeśli klucz obcy nie jest-null, to zawiedzie.

+2

Czy to nadal jedyne rozwiązanie? Sądziłem, że celem hibernacji jest uniknięcie konieczności kodowania tego osobno dla każdego obiektu i ułatwienie utrzymywania kodu. Czy jest jakiś sposób, aby to zrobić bez pisania metody dla każdego obiektu, na przykład opcji hibernacji, którą należy ustawić? – pete

4

Zobacz odpowiedź na pytanie "Overriding equals and hashCode in Java".

Wyjaśnia, w jaki sposób zastąpić metody równości i hashCode, które wydawały się być problemem, ponieważ zadziałały po przepisaniu ich ponownie.

Niewłaściwe ich przesłonięcie może doprowadzić do hibernacji, aby usunąć swoje kolekcje i ponownie je zainstalować. (jako że klucz hash używany jest jako klucze na mapach)

+4

Doskonale zdaję sobie sprawę z tego, jak ważne są równe i hashcode oraz jak je przesłonić. Wielka tajemnica polega na tym, że stary kod równy + hash jest identyczny z nowo wygenerowanym ... a jednak to rozwiązało mój problem. Czy możesz to wyjaśnić? – Yuval

0

Odpowiedź Briana Deterlinga pomogła mi przezwyciężyć fantomowe usunięcie. Szkoda, że ​​nie podał prawdziwego kodu. Oto co otrzymałem z jego sugestii 1. Publikowanie postu dla kogoś, kto go użyje lub skomentuje mój kod.

// snFile and task share many to many relationship 

@PersistenceContext 
private EntityManager em; 

public SnFile merge(SnFile snFile) { 
     log.debug("Request to merge SnFile : {}", snFile); 

     Set<Task> tasks = taskService.findBySnFilesId(snFile.getId()); 
     if(snFile.getTasks() != null) { 
      snFile.getTasks().clear(); 
     } 
     em.merge(snFile); 
     em.flush(); 
     if(tasks != null) { 
      if(snFile.getTasks() != null) 
       snFile.getTasks().addAll(tasks); 
      else 
       snFile.setTasks(tasks); 
     } 

     return em.merge(snFile); 
    } 
Powiązane problemy