piątek, 18 września 2009

Kaskadowość w hibernate

Postaram się przeanalizować dosyć nieintuicyjną (według mnie) kwestię kaskadowego zapisywania relacji w Hibernate. Posłużę się wykorzystywanym już wcześniej przykładem dwóch tabel: Produkt i Kategoria.

Na początek: brak kaskadowości, mapowanie jednostronne.
Mapowania i klasy POJO:


<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="main">
<class name="Produkt" table="produkt">
<id name="id" unsaved-value="null" column="produkt_id">
<generator class="sequence">
<param name="sequence">prod_seq</param>
</generator>
</id>
<property name="name" />
<property name="value" />
</class>
</hibernate-mapping>

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="main">
<class name="Kategoria" table="kategoria">
<id name="id" unsaved-value="null" column="kategory_id">
<generator class="sequence">
<param name="sequence">kat_seq</param>
</generator>
</id>
<property name="name" />
<property name="type" />
<property name="description" />
<set name="products" lazy="true" inverse="false" cascade="none">
<key column="kategory_id" />
<one-to-many class="Produkt" />
</set>
</class>
</hibernate-mapping>


package main;

public class Produkt {
Integer id;
String name;
Integer value;


public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getValue() {
return value;
}

public void setValue(Integer value) {
this.value = value;
}
}

package main;

import java.util.HashSet;
import java.util.Set;

public class Kategoria {
Integer id;
String name;
String type;
String description;
Set<Produkt> products = new HashSet<Produkt>();

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public Set<Produkt> getProducts() {
return products;
}

public void setProducts(Set<Produkt> products) {
this.products = products;
}
}


Zapisanie do bazy danych kategorii i produktu wygląda następująco:


package main;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class Main {
public static void main(String[] args) {
Session session = null;
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
session = sessionFactory.openSession();

Kategoria kat = new Kategoria();
Produkt prod1 = new Produkt();
Produkt prod2 = new Produkt();
kat.setName("kat");
prod1.setName("prod1");
prod2.setName("prod2");
kat.getProducts().add(prod1);
kat.getProducts().add(prod2);
session.beginTransaction();
session.save(prod1);
session.save(prod2);
session.save(kat);
session.flush();
session.getTransaction().commit();
session.close();
System.out.println("zapisane!");
session = sessionFactory.openSession();
session.beginTransaction();
System.out.println("Kategorie: "+session.createQuery("from Kategoria").list().size());
System.out.println("Produkty: "+session.createQuery("from Produkt").list().size());
session.clear();
session.delete(prod1);
session.flush();
session.getTransaction().commit();
session.close();
System.out.println("produkt usuniety");
session = sessionFactory.openSession();
session.beginTransaction();
System.out.println("Kategorie: "+session.createQuery("from Kategoria").list().size());
System.out.println("Produkty: "+session.createQuery("from Produkt").list().size());
session.clear();
session.delete(kat);
session.flush();
session.getTransaction().commit();
session.close();
System.out.println("kategoria usunieta");
session = sessionFactory.openSession();
session.beginTransaction();
System.out.println("Kategorie: "+session.createQuery("from Kategoria").list().size());
System.out.println("Produkty: "+session.createQuery("from Produkt").list().size());
System.out.println("end");
}
}


Bardzo ważne jest, aby zapisać zarówno produkt jak i kategorię. Jeśli tego nie zrobimy, pojawi się błąd: exception in thread "main" org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: main.Produkt. Kolejność zapisywania nie jest istotna.
W kodzie musiałem wyczyścić sesję (session.clear()), żeby uniknąć błędu: org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session.
Wynikiem działania programiku jest:
zapisane!
Kategorie: 1
Produkty: 2
produkt usuniety
Kategorie: 1
Produkty: 1
kategoria usunieta
Kategorie: 0
Produkty: 1
end


Niby ok, ale dlaczego istnieje produkt bez kategorii? Jeśli spojrzymy do bazy danych, to faktycznie zobaczymy, że w tabeli Produkty istnieje wpis "prod2" z pustą wartością klucza obcego kategory_id. Trochę to nieintuicyjne. Może można zrobić tak, że obiekty zależne (produkty) zapisywane i usuwane są razem z obiektem nadrzędnym (kategorią)? Oczywiście, że można. Trzeba się tylko trochę namęczyć:

Mapowania:

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="main">
<class name="Kategoria" table="kategoria">
<id name="id" unsaved-value="null" column="kategory_id">
<generator class="sequence">
<param name="sequence">kat_seq</param>
</generator>
</id>
<property name="name" />
<property name="type" />
<property name="description" />
<set name="products" lazy="true" inverse="true" cascade="all">
<key column="kategory_id" />
<one-to-many class="Produkt"/>
</set>
</class>
</hibernate-mapping>

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="main">
<class name="Produkt" table="produkt">
<id name="id" unsaved-value="null" column="produkt_id">
<generator class="sequence">
<param name="sequence">prod_seq</param>
</generator>
</id>
<property name="name" />
<property name="value" />
<many-to-one name="kategoria" class="Kategoria" not-null="true" column="kategory_id"/>
</class>
</hibernate-mapping>


Klasy POJO:


package main;

import java.util.HashSet;
import java.util.Set;

public class Kategoria {
Integer id;
String name;
String type;
String description;
Set<Produkt> products = new HashSet<Produkt>();

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public Set<Produkt> getProducts() {
return products;
}

public void setProducts(Set<Produkt> products) {
this.products = products;
}

public void addProduct(Produkt product) {
if (product.getKategoria()==null)
product.setKategoria(this);
products.add(product);
}
}
package main;

public class Produkt {
Integer id;
String name;
Integer value;
Kategoria kategoria;

public Kategoria getKategoria() {
return kategoria;
}

public void setKategoria(Kategoria kategoria) {
this.kategoria=kategoria;
this.kategoria.getProducts().add(this);
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getValue() {
return value;
}

public void setValue(Integer value) {
this.value = value;
}
}


W mapowaniach i POJO kluczowe są opcje "cascade", "inverse" oraz settery (setKatgoria, setProdukt, addProdukt) - bez nich nie proste operacje na javovych obiektach nie miałyby bezpośredniego przełożenia na sytuację w bazie danych.

No i main:


package main;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;


public class Main {

public static void main(String[] args) {

Session session = null;
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
session = sessionFactory.openSession();

session.beginTransaction();

Kategoria kat = new Kategoria();
Produkt prod1 = new Produkt();
Produkt prod2 = new Produkt();
prod1.setName("prod1");
prod2.setName("prod2");
kat.addProduct(prod1);
prod2.setKategoria(kat);

session.save(kat);
session.flush();
session.getTransaction().commit();
session.close();
System.out.println("zapisane!");


session = sessionFactory.openSession();
session.beginTransaction();
System.out.println("Kategorie: "+session.createQuery("from Kategoria").list().size());
System.out.println("Produkty: "+session.createQuery("from Produkt").list().size());
session.clear();
session.delete(prod1);
kat.getProducts().remove(prod1);
session.flush();
session.getTransaction().commit();
session.close();
System.out.println("produkt usuniety");


session = sessionFactory.openSession();
session.beginTransaction();
System.out.println("Kategorie: "+session.createQuery("from Kategoria").list().size());
System.out.println("Produkty: "+session.createQuery("from Produkt").list().size());
session.clear();
System.out.println("produkty: "+kat.getProducts().size());
session.delete(kat);
session.flush();
session.getTransaction().commit();
session.close();
System.out.println("kategoria usunieta");
session = sessionFactory.openSession();
session.beginTransaction();
System.out.println("Kategorie: "+session.createQuery("from Kategoria").list().size());
System.out.println("Produkty: "+session.createQuery("from Produkt").list().size());
System.out.println("end");
}
}


Wynik działania programu:

zapisane!
Kategorie: 1
Produkty: 2
produkt usuniety
Kategorie: 1
Produkty: 1
produkty: 1
kategoria usunieta
Kategorie: 0
Produkty: 0
end

Brak komentarzy:

Prześlij komentarz