środa, 21 stycznia 2009

Encje, Managery i MBeany Część 1

Witam
Zauważyłem ze niektórzy mają z tym problem więc postaram się przybliżyć jakie elementy powinny się znaleźć w encjach, managerach, i mbeanach (managery i mbeany w następnych postach).

Encje

Są one elementami warstwy EJB i warto przechowywać je w osobnym pakiecie (np pl.dzmitrow.projekt.encje).
W encjach umieszczamy definicje tabel i powiązania między nimi.
Tworzymy je jako zwykłe klasy javowe implementujące Serializable, z odpowiednimi adnotacjami.

Każda encja musi posiadać adnotacje @Entity i implementować interfejs Serializable
@Entity
public class Kategoria implements Serializable{
  private static final long serialVersionUID = 8211793733544613374L; /* dowolna liczba z L na koncu */
...
}
Oczywiście, by były przechowywane jakieś dane musimy zdefiniować odpowiednie pola np. wcześniej podana klasa Kategoria ma pola:
...
private int id;
private String nazwa;
private List<Produkt> produkty; /*definicja tej encji nie obchodzi nas w tym miejscu */
...
Pola powinny być prywatne ze względów bezpieczeństwa, a skoro są prywatne to nie ma do nich dostępu z zewnątrz bez setterów/getterów.
Możemy je wpisać ręcznie... , albo użyć Eclipsowego narzędzia. Aby wygenerować w otwartym pliku klikamy prawym w dowolnym miejscu i wybieramy Source -> Generate Getters and Setters.
Wybieramy pola dla których chcemy je utworzyć, klikamy OK i gotowe.

Musimy teraz zdefiniować, które pole jest kluczem głównym naszej encji. Zapewne jest to id więc przy getId() dodajemy:
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public int getId() {
  return id;
}
Ogólnie większość definicji w encji podaje się przy getterach i tylko one będą się pojawiać w poście.
Co zrobiliśmy? Powiedzieliśmy, że id ma być kluczem głównym (@Id) oraz klucze mają by unikalne i generowane automatycznie ( @GeneratedValue(strategy=GenerationType.IDENTITY) )

Możemy ustawić również unikalność pól. Dzięki czemu choćbyśmy chcieli nie będzie dwóch kategorii i takiej samej nazwie.
Są na to dwa sposoby. Pierwszy to:
@Entity
@Table(
name="Kategoria",
uniqueConstraints={@UniqueConstraint(columnNames={"id"}),@UniqueConstraint(columnNames={"nazwa"})})
public class Kategoria implements Serializable{
...
Przy czym warto zauważyć, że @UniqueConstraint(columnNames={"id", "nazwa"}) oznaczałoby unikalność pary a nie nazwy i id z osobna.
Drugi to dodać odpowiednią adnotacje przy getterze pola:
@Column(unique=true) /* takich opcji jest dużo więcej! */
public String getNazwa() {
  return nazwa;
}
Ważne jest by od razu zdefiniować wszystkie unikalności i inne właściwości, gdyż zmiany wejdą w życie tylko przy ponownym utworzeniu tabeli.

Jeszcze jedna drobna uwaga zanim przejdziemy do relacji między encjami.
Gdy tworzymy jakieś metody w encjach nie będące setterami/getterami, a nie chcemy dostawać warningów, należy oznaczyć je adnotacją @Transient będą one ignorowane przez EntityManager-a.

Teraz najgorszy koszmar definicje powiązań między encjami ;).
Do dyspozycji mamy następujące powiązania:
  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany
Definiujemy je jak poprzednio przy getterach np. w encji Osoba mogłoby się znaleźć coś takiego:
@OneToOne(cascade={CascadeType.ALL}, fetch=FetchType.LAZY)
public Adres getAdres() {
  return adres;
}
Rzecz jasna wystarczyłoby samo @OneToOne, ale przy okazji wytłumaczę co to jest fetch (cascade zostało wytłumaczone w tym poście).
fetch ma dwie możliwe wartości FetchType.LAZY i FetchType.EAGER . Są one odpowiednio predefiniowane dla pewnych połączeń, jednak nie będę teraz tu wszystkich wymieniał i odeśle do dokumentacji Hibernate-a.
Pierwszy pozwala na odroczenie pobrania danych pola do wybranego przez nas momentu, a drugi ustala by dane były pobierane od razu z całym obiektem. Przy czym przy używaniu Lazy trzeba w odpowiedni sposób zainicjalizować pole (o czym będzie w poście o managerach).

Gdy chcemy zdefiniować połączenie jednostronne @OneToMany warto zrobić tabele łączącą:
@OneToMany
@JoinTable(name="KATEGORIA_PRODUKT",
 joinColumns={@JoinColumn(name="KATEGORIA_ID")},
 inverseJoinColumns={@JoinColumn(name="PRODUKT_ID")})
public List getProdukty() {
  return produkty;
}
Gdy chcemy zdefiniować dwustronnie takie połączenie to (jak przy każdym dwustronnym) musimy zdefiniować stronę posiadającą.
Najłatwiej (w tym przypadku) jest zrobić jako stronę posiadającą @ManyToOne .
Jako, że chcę połączyć produkty z kategoriami wpierw strona trzymająca czyli produkty:
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="kategoria_fk")
public Kategoria getKategoria() {
  return kategoria;
}
A w encji Kategoria:
@OneToMany(mappedBy="kategoria", fetch=FetchType.LAZY)
public List getProdukty() {
  return produkty;
}
Co to jest to mappedBy? Kompilator przez to rozumie, że definicja połączenia znajduje się po drugiej stronie (w encji Produkt) i jest określona dla pola kategoria. Fetch został ustawiony jako lazy bo pobieranie wszystkich produktów z danej kategorii zabiera sporo czasu a nie zawsze potrzebujemy mieć do nich dostęp.

W drugą stronę robi się zupełnie inaczej i ponownie odsyłam do dokumentacji hibernate-a.

Po zdefiniowaniu encji nigdzie więcej nie musimy ponownie ustawiać zależności i innych właściwości w managerach czy mbeanach.

Wszystkie adnotacje wykorzystane w tym poście są wytłumaczone i wszystkie ich właściwości podane w powyższej dokumentacji.
Zachęcam do komentowania i zadawania pytań.

4 komentarze:

  1. A jak udało Wam się wstawić taki kolorowy kod źródłowy do blogspota?

    OdpowiedzUsuń
  2. Potrzebne są 2 pliki: http://gen2.org/~dogrizz/prettify.css oraz http://gen2.org/~dogrizz/prettify.js
    Później przechodzimy do układu strony->edytuj kod html. W sekcji <head> </head> wstawiamy
    <script src='<adres_naszego_serwera>/prettify.js' type='text/javascript'/>
    <link href='<adres_naszego_serwera>/prettify.css' rel='stylesheet' type='text/css'/>
    Zaś sekcja body musi zaczynać sie w ten sposób:
    <body onload='prettyPrint()'>
    Teraz pisząc posty wystarczy wstawic <pre class="prettyprint> kod </pre> i będziemy mieli ładne kolorowanie :)

    OdpowiedzUsuń
  3. No dobra standardowo robiłem i przestało wychodzić :) Otóż jak się okazuje w 1 encji możemy użyć fetch=FetchType.EAGER tylko raz, gdyż przy próbie 2krotnego użycia hibernate wypluje cannot fetch multiple bags bla bla exception.

    Pod tym linkiem mamy opisane dlaczego tak się dzieje (nie chce się rozpisywać:P):

    http://www.jroller.com/eyallupu/entry/hibernate_exception_simultaneously_fetch_multiple

    Także albo nie robić wielu relacji w 1 encji, albo używać LAZY ;) Powodzenia :)

    OdpowiedzUsuń
  4. W tym samym artykule niżej jest dział Workaround. w którym zaproponowane korzystanie z adnotacji @IndexColumn, lub zastąpienie java.util.List przez java.util.Set. Oraz dwie odpowiedzi na ten artykuł również podające jakieś rozwiązania. Więc niekoniecznie trzeba korzystac z lazy fetchingu.

    OdpowiedzUsuń