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
@EntityOczywiście, by były przechowywane jakieś dane musimy zdefiniować odpowiednie pola np. wcześniej podana klasa Kategoria ma pola:
public class Kategoria implements Serializable{
private static final long serialVersionUID = 8211793733544613374L; /* dowolna liczba z L na koncu */
...
}
...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.
private int id;
private String nazwa;
private List<Produkt> produkty; /*definicja tej encji nie obchodzi nas w tym miejscu */
...
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:
@IdOgólnie większość definicji w encji podaje się przy getterach i tylko one będą się pojawiać w poście.
@GeneratedValue(strategy=GenerationType.IDENTITY)
public int getId() {
return id;
}
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:
@EntityPrzy czym warto zauważyć, że
@Table(
name="Kategoria",
uniqueConstraints={@UniqueConstraint(columnNames={"id"}),@UniqueConstraint(columnNames={"nazwa"})})
public class Kategoria implements Serializable{
...
@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! */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.
public String getNazwa() {
return nazwa;
}
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
@OneToOne(cascade={CascadeType.ALL}, fetch=FetchType.LAZY)Rzecz jasna wystarczyłoby samo
public Adres getAdres() {
return adres;
}
@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ą:@OneToManyGdy chcemy zdefiniować dwustronnie takie połączenie to (jak przy każdym dwustronnym) musimy zdefiniować stronę posiadającą.
@JoinTable(name="KATEGORIA_PRODUKT",
joinColumns={@JoinColumn(name="KATEGORIA_ID")},
inverseJoinColumns={@JoinColumn(name="PRODUKT_ID")})
public ListgetProdukty() {
return produkty;
}
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)A w encji Kategoria:
@JoinColumn(name="kategoria_fk")
public Kategoria getKategoria() {
return kategoria;
}
@OneToMany(mappedBy="kategoria", fetch=FetchType.LAZY)Co to jest to
public ListgetProdukty() {
return produkty;
}
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ń.
A jak udało Wam się wstawić taki kolorowy kod źródłowy do blogspota?
OdpowiedzUsuńPotrzebne są 2 pliki: http://gen2.org/~dogrizz/prettify.css oraz http://gen2.org/~dogrizz/prettify.js
OdpowiedzUsuń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 :)
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.
OdpowiedzUsuń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 :)
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ń