Hibernate ORM i J2EE: Znaczenie i różnica między atrybutem CASCADE i INVERSE na definicji relacji

Nieznajomość znaczenia atrybutów cascade oraz inverse nie uniemożliwia korzystania z Hibernate (znanego ORM dla J2EE). Sprawi jednak, że nieświadomie będziemy ofiarą mniejszej wydajności pracy naszej aplikacji z powodu zbędnych, dodatkowych zapytań do bazy danych.

Wyobraźmy sobie następującą sytuację. Mamy następujące 2 encje:

  • Zestaw
  • Przedmiot

Zestaw może składać się z wielu Przedmiotów. Przedmioty mogą wchodzić w skład różnych Zestawów. Mamy tu więc relację Wiele-do-Wielu. Konieczne są więc 3 tabele:

  • zestaw
  • przedmiot
  • zestaw_przedmiot

W Hibernate, zdefiniowalibyśmy 2 klasy odpowiadające naszym Encjom – Zestaw oraz Przedmiot. Skupmy się teraz na klasie Zestaw. Klasa miałaby kilka pól (np. id, nazwa, itd.). Miałaby również pole odpowiadające za relację, np. przedmioty. Całość mogła by wyglądać jak poniżej.

public class Zestaw {
    private int id;
    private String nazwa
    private Set przedmioty;
 
    //Gettery i Settery
}

Teraz skupmy się tylko na polu przedmioty klasy Zestaw. W skojarzonym pliku XML opisującym mapowania, pole to mogło by zostać opisane np. w poniższy sposób.

<set name="przedmioty" table="zestaw_przedmiot" cascade="save-update" inverse="true">
  <key column="zestaw_id">< /key>
  <many-to-many class="com.domain.Przedmiot" column=przedmiot_id"/>
</set>

Z grubsza, powyższy zapis sam się tłumaczy. Niezrozumiałymi mogą być tylko atrybuty cascade i inverse. Wszystko wyjaśni się za chwilę na przykładach, ja tylko tytułem wstępu napiszę, że:

  • cascade – określa, czy zapisywane mają być również inne encje połączone relacją z tą zapisywaną
  • inverse – określa, która strona relacji odpowiada za zapisywanie kluczy obcych do bazy danych

W powyższym przykładzie cascade i inverse mają już przypisane wartości, ale rozważmy różne kombinacje tych atrybutów. Nasze rozważania będą dotyczyć tego, co zadzieje się, gdy utworzymy 1 Zestaw, następnie utworzymy 2 Przedmioty i przypiszemy je do Zastawu, a na końcu zapiszemy Zestaw. Ilustruje to poniższy przykładowy kod.

Przedmiot przedmiot1 = new Przedmiot( "krzesło" );
Przedmiot przedmiot2 = new Przedmiot( "lustro" );
 
Zestaw zestaw = new Zestaw( "meble" );
zestaw.dodajPrzedmiot( przedmiot1 );
zestaw.dodajPrzedmiot( przedmiot2 );
session.save( zestaw );

I teraz zobaczymy, jaki zapytania będą szły do bazy w zależności od wartości w/w atrybutów.

1) CASCADE == NONE; INVERSE == false

W tym przypadku, klasa Zestaw nie zapisuje kaskadowo Przedmiotów.
Relacja nie jest odwrócona, więc to klasa Zestaw odpowiada za klucze obce.

Hibernate: INSERT INTO STUDENT (Name, stud_id) VALUES (?, ?)
Hibernate: INSERT INTO stud_phone (mapping_stud_id, mapping_phon_id) VALUES (?, ?)
Hibernate: INSERT INTO stud_phone (mapping_stud_id, mapping_phon_id) VALUES (?, ?)

Zapisana więc została tylko encja Zestaw oraz do tabeli pośredniej trafiły klucze obce relacji.

2) CASCADE == NONE; INVERSE == true

W tym przypadku, klasa Zestaw nie zapisuje kaskadowo Przedmiotów.
Relacja jest odwrócona, więc klasa Zestaw nie odpowiada za klucze obce.

Hibernate: INSERT INTO STUDENT (Name, stud_id) VALUES (?, ?)

Zapisana więc została tylko encja Zestaw i nic więcej.

3) CASCADE == save-update; INVERSE == false

W tym przypadku, klasa Zestaw zapisuje kaskadowo Przedmioty.
Relacja nie jest odwrócona, więc to klasa Zestaw odpowiada za klucze obce.

Hibernate: INSERT INTO STUDENT (Name, stud_id) VALUES (?, ?)
Hibernate: INSERT INTO phone (phone_num, phone_id) VALUES (?, ?)
Hibernate: INSERT INTO phone (phone_num, phone_id) VALUES (?, ?)
Hibernate: INSERT INTO stud_phone (mapping_stud_id, mapping_phon_id) VALUES (?, ?)
Hibernate: INSERT INTO stud_phone (mapping_stud_id, mapping_phon_id) VALUES (?, ?)

Zapisana została więc encja Zestaw oraz encje Przedmiotów. Zapisane zostały również wszystkie klucze obce.

4) CASCADE == save-update; INVERSE == true

W tym przypadku, klasa Zestaw zapisuje kaskadowo Przedmioty.
Relacja jest odwrócona, więc klasa Zestaw nie odpowiada za klucze obce.

Hibernate: INSERT INTO STUDENT (Name, stud_id) VALUES (?, ?)
Hibernate: INSERT INTO phone (phone_num, phone_id) VALUES (?, ?)
Hibernate: INSERT INTO phone (phone_num, phone_id) VALUES (?, ?)

Zapisane zostały więc wszystkie encje, ale żadne klucze obce.

PODSUMOWANIE

Jak widać w/w atrybuty współpracują ze sobą, aby określić co kto zapisuje. Powyższe przykłady zostały oparte na relacji typu MANY-TO-MANY, ale analogiczne stosują się do pozostałych typów relacji.

Artykuł został zainspirowany przez:
http://stackoverflow.com/questions/3667387/what-is-the-difference-between-cascade-inverse-in-hibernate-what-is-the-use-o