Hibernate annotációk - entitás asszociációk/kapcsolatok
Bevezetés
[szerkesztés]A Hibernate annotációk - entitás asszociációk/kapcsolatok a Hibernate objektum-relációs leképezést (ORM) megvalósító programkönyvtár használatakor az entitások közötti adatbázis szintű kapcsolatok definiálására használhatóak a forráskód részeként. Két entitás között lehet egy-egy, egy-több, több-egy vagy több-több kapcsolat. Mind a négy lehetőségnél több módszer áll rendelkezésünkre a tényleges fizikai összeköttetés kiválasztására (pl. kapcsolótábla, külső kulcsok felvétele a tartalmazó entitáshoz, ... ). Egy-több vagy több-egy esetben választhatunk az egyirányú és kétirányú esetek közül. Egyirányú esetben csak az egyik entitásnál van jelölve a kapcsolat, kétirányú esetben mindkettőnél.
Egy-több és több-több esetben a hivatkozott entitásokat kollekcióként kezeljük. Ilyenkor a tartalmazó entitás a másik típusú entitás egy csoportjához kapcsolódik, így a hivatkozott entitásokat Java nyelv esetén valamely a Collection interfészt megvalósító osztály segítségével kell eltároljuk.
Alap típusok és beágyazható objektumok esetén az @ElementCollection annotáció egyéb beállítási lehetőségeket kínál.
Indexelt kollekciókat használhatunk az adatok adatbázis szintű rendezésének biztosítására.
A kaszkádolás beállítására szolgáló annotációk lehetővé teszik az adatokon végzett műveletek továbbgyűrűzését a hivatkozott entitásokra.
A fetcheléssel a kapcsolódó entitások adatbázisból való letöltését szabályozhatjuk, csökkentve a letöltött adatmennyiséget vagy az elérési időt.
Egy-egy kapcsolat
[szerkesztés]A @OneToOne annotáció segítségével az entitások között egy-egy típusú kapcsolat hozható létre. Ennek három típusa van:
- az entitások ugyanazokat az elsődleges kulcs értékeket tartalmazzák,
- az egyik entitás egy külső kulcsot tartalmaz(ez egyedi kell legyen, hogy tényleg egy-egy kapcsolat valósuljon meg, vagyis két különböző entitásnál nem szerepelhet azonos érték),
- egy táblában tároljuk az entitások közötti kapcsolatot(a külső kulcsok szintén egyediek kell legyenek az egy-egy kapcsolat biztosításához). Ennek használata ritka.
Példa egy-egy kapcsolat leképezésére az elsődleges kulcsok értékeinek megosztásával
[szerkesztés]@Entity
public class Body {
@Id
public Long getId() { return id; }
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public Heart getHeart() {
return heart;
}
...
}
@Entity
public class Heart {
@Id
public Long getId() { ...}
}
A @PrimaryKeyJoinColumn annotáció adja meg, hogy az entitás elsődleges kulcs értékét használjuk a kapcsolódó entitás külső kulcs értékeként.
Példa egy-egy kapcsolat leképezésére külső kulcsot használva
[szerkesztés]@Entity
public class Customer implements Serializable {
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="passport_fk")
public Passport getPassport() {
...
}
@Entity
public class Passport implements Serializable {
@OneToOne(mappedBy = "passport")
public Customer getOwner() {
...
}
A Customer a Customer táblában lévő passport_fk külső kulcs oszlopon keresztül kapcsolódik a Passporthoz. Az oszlopok kapcsolatát a @JoinColumn annotáció deklarálja, ami hasonló a @Column annotációhoz azzal a különbséggel, hogy előbbinek referencedColumnName nevű paramétere is van. Ez a paraméter deklarálja a kapcsolódásra használt oszlopot a cél entitásban. Ha a referencedColumnName-et nem elsődleges kulcsra alkalmazzuk, akkor a kapcsolódó osztálynak sorozatosíthatónak (Serializable) kell lennie. Továbbá az is fontos, hogy egy nem elsődleges kulcshoz kapcsolódva a tulajdonságnak csak egy oszlopa lehet, ellenkező esetben nem működik a kapcsolat.
A kapcsolódás kétirányú is lehet. Ez esetben az egyik oldal a tartalmazó fél, aki felelős a kapcsoló oszlopok frissítéséért. A nemtartalmazó oldalon a mappedBy asszociáció használatos, amely a tartalmozó oldali kapcsolótulajdonságot adja meg. A példában ez a passport (a Customer passport adattagja). Ez esetben nem szabad ezen az oldalon kapcsoló oszlopot definiálni, mert a tartalmazó oldalon már megtörtént.
Ha nincs @JoinColumn a tartalmazó oldalon, alapértelmezés szerint kapcsoló oszlopok lesznek létrehozva a tartalmazó táblájában, melyek elnevezései: a tartalmazott neve a tartalmazó oldalán, _ (aláhúzás), a tarlmazott elsődleges kulcsainak nevei. A példában passport_id, mert a tulajdonság neve passport a Customeren belül, és a Passport azonosító oszlopa az id.
Példa egy-egy kapcsolatra kapcsolótáblát használva
[szerkesztés]@Entity
public class Customer implements Serializable {
@OneToOne(cascade = CascadeType.ALL)
@JoinTable(name = "CustomerPassports",
joinColumns = @JoinColumn(name="customer_fk"),
inverseJoinColumns = @JoinColumn(name="passport_fk")
)
public Passport getPassport() {
...
}
@Entity
public class Passport implements Serializable {
@OneToOne(mappedBy = "passport")
public Customer getOwner() {
...
}
A Customer a CustomerPassports kapcsolótáblán keresztül kapcsolódik a Passporthoz. Ez egy passport_fk külső kulccsal rendelkezik, ami a Passport táblára mutat (inverseJoinColumns attribútumban megadva), és egy customer_fk külső kulccsal, ami a Customer táblára mutat (joinColumns attribútumban megadva). Látható, hogy ez esetben a tábla és az oszlopok nevét is meg kell adni.
Több-egy kapcsolat
[szerkesztés]Több-egy kapcsolatot a @ManyToOne annotációval definiálhatunk. Példa:
@Entity()
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}
A @JoinColumn annotáció opcionális, az alapértelmezett értékek ugyanazok mint az egy-egy esetben: kapcsolat neve a tartalmazó oldalon, _ (aláhúzás), elsődleges kulcs neve a tartalmazott oldalon. A példában company_id. (Az attribútum neve company a Flight osztályon belül, és a Company osztály azonosító (@Id-vel jelzett) oszlopa az id).
@ManyToOne esetén használható a targetEntity paraméter, így megadható a cél entitás neve. Alapértelmezésként ez a cél entitás típusa (osztálya). Akkor hasznos a használata, ha a visszatérési érték interfész.
@Entity
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=CompanyImpl.class )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}
public interface Company {
...
}
Több-egy kapcsolat esetén is használható kapcsolótábla. Ezt a @JoinTable annotáció adja meg: tartalmazza a több oldali tábla külső kulcsát (joinColumnsban megadva) és az egy oldali tábla külső kulcsát (inverseJoinColumnsban megadva).
@Entity
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinTable(name="Flight_Company",
joinColumns = @JoinColumn(name="FLIGHT_ID"),
inverseJoinColumns = @JoinColumn(name="COMP_ID")
)
public Company getCompany() {
return company;
}
...
}
Kollekciók
[szerkesztés]Egy entitás és egy másik entitást tartalmazó kollekció(Collection, List, Map vagy Set) viszonya leképezhető egy-több vagy több-több kapcsolatként a @OneToMany vagy a @ManyToMany annotációk megfelelő használatával. Ha a kollekció egy alap vagy beágyazható típus, az @ElementCollection használatos.
Egy-több
[szerkesztés]@OneToMany annotációval jelöljük. Lehet egyirányú és kétirányú.
Kétirányú
[szerkesztés]A JPA specifikákcó szerint a tartalmazó entitás majdnem mindig a kétirányú több-egy kapcsolat egy oldalán van. Így a több oldalon elég a @OneToMany(mappedBy=...) annotációval jelezni, hogy a másik entitás gondoskodik a külső kulcs kezeléséről.
@Entity
public class Troop {
@OneToMany(mappedBy="troop")
public Set<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk")
public Troop getTroop() {
...
}
Troop kétirányú egy-több módon kapcsolódik a Soldierhez a troop tulajdonságon keresztül.
Ha azt szeretnénk, hogy a több oldal lehessen a tartalmazó oldal, el kell távolítani a mappedBy attribútumot, és a másik oldalon a @JoinColumn insertable és updatable tulajdonságait falsera kell állítani. Ez nem optimalizált megoldás, és sok UPDATE utasítást eredményezhet.
@Entity
public class Troop {
@OneToMany
@JoinColumn(name="troop_fk") //we need to duplicate the physical information
public Set<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
Egyirányú
[szerkesztés]Egyirányú egy-több kapcsolat a tartalmazott oldalon tárolt külső kulcs használatával nem ajánlott módszer. Erre a @JoinColumn használható. Jobb megoldás kapcsolótábla használata (ami a következő részben kerül bemutatásra).
@Entity
public class Customer implements Serializable {
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
public Set<Ticket> getTickets() {
...
}
@Entity
public class Ticket implements Serializable {
... //nem kétirányú, nincs jelölve a kapcsolat
}
Customer egyirányú kapcsolatot definiál a Tickettel a CUST_ID oszlopon keresztül.
Egyirányú kapcsolat kapcsolótábla használatával
[szerkesztés]Ez egy jobb megoldás az előzőhöz képest. A @JoinTable annotációval definiálható.
@Entity
public class Trainer {
@OneToMany
@JoinTable(
name="TrainedMonkeys",
joinColumns = @JoinColumn( name="trainer_id"),
inverseJoinColumns = @JoinColumn( name="monkey_id")
)
public Set<Monkey> getTrainedMonkeys() {
...
}
@Entity
public class Monkey {
... //nem kétirányú
}
Trainer egy egyirányú kapcsolatot definiál a Monkeyval a TraindMonkeys tábla segítségével. A külső kulcsok: trainer_id a Trainerre (joinColumns), és monkey_id a Monkeyra (inversejoinColumns) hivatkozva.
Alapértelmezések
[szerkesztés]Ha nem adjuk meg a leképezés típusát, az alapértelmezett egyirányú egy-több eset lesz kapcsolótáblával. A tábla neve: tartalmazó tábla neve , _, tartalmazott tábla neve. Tartalmazó táblára hivatkozó külső kulcs(ok) neve(i): tartalmazó tábla neve, _, tartalmazó oldali elsődleges kulcs(ok). Tartalmazott táblára hivatkozó külső kulcs(ok) neve(i): tartalmazó oldalon a tulajdonság neve, _, tartalmazott oldali elsődleges kulcs(ok). Az egy oldalon a külső kulcsok egyediek (unique). Így a következő példa esetén nem szerepelhet több Trainernél is ugyanaz a Tiger.
@Entity
public class Trainer {
@OneToMany
public Set<Tiger> getTrainedTigers() {
...
}
@Entity
public class Tiger {
... //nem kétirányú
}
Trainer egy egyirányú kapcsolatot definiál a Tiger entitással kapcsolótáblaként a Trainer_Tigert használva. Külső kulcsok: trainer_id a Trainerhez és trainedTigers_id a Tigerhez (tulajdonság neve a Trainer osztályban, _,Tiger elsődleges kulcs).
Több-több
[szerkesztés]Definíció
[szerkesztés]A több-több kapcsolat jelölésére a @ManyToMany annotáció használható. Itt szükséges a @JoinTablevel a kapcsolótábla és a kapcsolódási feltételek megadása. Kétirányú esetben az egyik fél a tartalmazó, és a másik az inverze vég (amely figyelmen kívül lesz hagyva a kapcsolótábla értékeinek frissítésekor). Például:
@Entity
public class Employer implements Serializable {
@ManyToMany(
targetEntity=org.hibernate.test.metadata.manytomany.Employee.class,
cascade={CascadeType.PERSIST, CascadeType.MERGE}
)
@JoinTable(
name="EMPLOYER_EMPLOYEE",
joinColumns=@JoinColumn(name="EMPER_ID"),
inverseJoinColumns=@JoinColumn(name="EMPEE_ID")
)
public Collection getEmployees() {
return employees;
}
...
}
@Entity
public class Employee implements Serializable {
@ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
mappedBy = "employees",
targetEntity = Employer.class
)
public Collection getEmployers() {
return employers;
}
}
A @JoinTable definiál egy nevet a kapcsolótábla, egy tömböt a kapcsoló oszlop(ok), és egy másik tömböt az inverz kapcsoló oszlop(ok) számára. Utóbbi(ak) a másik tábla elsődleges kulcsa(i). A tömb megadható {A , B , C} formában, ahol a tömb három méretű, elemei: A, B, C.
A másik oldalon (Employee) nem szabad hasonló formában jelölni a fizikai kapcsolatot, csak a mappedBy argumentummal megadni az összeköttetés létezését.
Alapértelmezett értékek
[szerkesztés]A fizikai kapcsolat leírása nélkül egy egyirányú több-több kapcsolat esetén a következő szabályok érvényesülnek: A tábla neve: tartalmazó tábla neve, _, másik oldali tábla neve. A tartalmazó táblára mutató külső kulcs(ok) neve(i): tartalmazó tábla neve, _, a tartalmazó oldali elsődleseg kulcs(ok) neve(i). A másik táblára mutató külső kulcs(ok) neve(i): tartalmazó oldalon a tulajdonság neve, _, a másik oldali elsődleges kulcs(ok) nevei. Ezek a szabályok megegyeznek az egyirányú egy-több esetben alkalmazottakkal.
@Entity
public class Store {
@ManyToMany(cascade = CascadeType.PERSIST)
public Set<City> getImplantedIn() {
...
}
}
@Entity
public class City {
... //nem kétirányú, ezért nincs jelölve a kapcsolat
}
A kapcsolótábla neve: Store_City. A Store táblára hivatkozó külső kulcs neve: Store_id. A City táblára hivatkozó külső kulcs neve: implantedIn_id.
Alapértelmezésként a kétirányú több-több esetben a következők érvényesülnek: tábla neve: tartalmazó tábla neve, _, másik oldali tábla neve. A tartalmazó táblára mutató külső kulcs(ok) neve(i): a másik oldali entitásban a tulajdonság neve, _, a tartalmazó oldali elsődleseg kulcs(ok) neve(i). A másik táblára mutató külső kulcs(ok) neve(i): tartalmazó oldalon a tulajdonság neve, _, a másik oldali elsődleges kulcs(ok). Ezek megegyeznek a kétirányú egy-több kapcsolatnál alkalmazottakkal.
@Entity
public class Store {
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public Set<Customer> getCustomers() {
...
}
}
@Entity
public class Customer {
@ManyToMany(mappedBy="customers")
public Set<Store> getStores() {
...
}
}
A Store_Customer a kapcsolótábal, a stores_id a Store táblára hivatkozó, a customer_id pedig a Customer táblára hivatkozó külső kulcs.
Alap típusok és beágyazható objektumok kollekciója
[szerkesztés]Egyszerű esetben nem két entitást kapcsolunk össze, hanem egy entitást egy alap típusú vagy beágyazható objektummal. Ebben az esetben az @ElementCollection használható.
@Entity
public class User {
[...]
public String getLastname() { ...}
@ElementCollection
@CollectionTable(name="Nicknames", joinColumns=@JoinColumn(name="user_id"))
@Column(name="nickname")
public Set<String> getNicknames() { ... }
}
A @CollectionTablevel adhatjuk meg a kollekció táblát, ami a kollekció adatait tárolja. Ha elhagyjuk, alapértelmezésként a neve a tartalmazó entitás neve, _, a kollekció attribútum neve lesz. A példában User_nicknames lenne.
Az alap típust tartalmazó oszlop nevét a @Column annotációval adhatjuk meg. Elhagyása esetén ez a tulajdonság neve lesz. Esetünkben ez a nicknames.
Nem csak alap típusokat használhatunk, hanem beágyazható objektumokat is. A beágyazott objektum oszlopneveinek megváltoztatására a @AttributeOverride használható.
@Entity
public class User {
[...]
public String getLastname() { ...}
@ElementCollection
@CollectionTable(name="Addresses", joinColumns=@JoinColumn(name="user_id"))
@AttributeOverrides({
@AttributeOverride(name="street1", column=@Column(name="fld_street"))
})
public Set<Address> getAddresses() { ... }
}
@Embeddable
public class Address {
public String getStreet1() {...}
[...]
}
Egy beágyazott objektum nem tartalmazhat kollekciót.
Megjegyzés Ha Mapet használunk, a key előtaggal hivatkozhatunk a kulcsként tárolt, value előtaggal az értékként tárolt beágyazott objektumok mezőire az oszlopnevek megadásakor. Például: @Entity public class User { @ElementCollection @AttributeOverrides({ @AttributeOverride(name="key.street1", column=@Column(name="fld_street")), @AttributeOverride(name="value.stars", column=@Column(name="fld_note")) }) public Map<Address,Rating> getFavHomes() { ... }
Megjegyzés Az @ElementCollection annotáció a régebbi @org.hibernate.annotations.CollectionOfElementst váltja fel.
Indexelt kollekciók (List, Map)
[szerkesztés]Listát kétféleképpen képezhetünk le:
- rendezett listaként, ilyenkor a rendezés az adatbázis szintjén nem jön létre,
- indexelt listaként, ilyenkor az adatbázisban is rendezettek az adatok.
A listák memóriában való rendezésére a @javax.persistence.OrderBy használható. Vesszővel elválasztva megadhatóak sorrendben a rendezés alapját szolgáló attribútumok és a rendezés iránya (pl. firstname asc, age desc). Üres string megadása esetén a célentitás elsődleges kulcsa szerint történik a rendezés.
@Entity
public class Customer {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
@OneToMany(mappedBy="customer")
@OrderBy("number")
public List<Order> getOrders() { return orders; }
public void setOrders(List<Order> orders) { this.orders = orders; }
private List<Order> orders;
}
@Entity
public class Order {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
public String getNumber() { return number; }
public void setNumber(String number) { this.number = number; }
private String number;
@ManyToOne
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) { this.customer = customer; }
private Customer customer;
}
-- Table schema
|-------------| |----------|
| Order | | Customer |
|-------------| |----------|
| id | | id |
| number | |----------|
| customer_id |
|-------------|
Az index érték egy külön oszlopban tárolható a @javax.persistence.OrderColumn segítségével. Ezzel megadhatjuk a nevét és a tulajdonságait az indexeket tároló oszlopnak. Ez az oszlop a külső kulcsot tartalmazó táblában fog elhelyezkedni. Ha nem adunk oszlopnevet, az alapértelmezett: hivatkozott tulajdonság, _,ORDER, a következő példában orders_ORDER lenne.
@Entity
public class Customer {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
@OneToMany(mappedBy="customer")
@OrderColumn(name"orders_index")
public List<Order> getOrders() { return orders; }
public void setOrders(List<Order> orders) { this.orders = orders; }
private List<Order> orders;
}
@Entity
public class Order {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
public String getNumber() { return number; }
public void setNumber(String number) { this.number = number; }
private String number;
@ManyToOne
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) { this.customer = customer; }
private Customer number;
}
-- Table schema
|--------------| |----------|
| Order | | Customer |
|--------------| |----------|
| id | | id |
| number | |----------|
| customer_id |
| orders_index |
|--------------|
Megjegyzés Az @org.hibernate.annotations.IndexColumn helyett érdemes az @OrderColumn használata, kivéve ha alap típus esetén meg akarjuk adni az első elem indexét. A szokásos értékek 0 vagy 1. Az alapértelmezett 0, mint Javaban.
Hasonlóan, mapek is használhatják kulcsként a kapcsolódó entitás egyik tulajdonságát, vagy külön oszlopot hozhatnak létre a rendezés céljából. Előbbi esetben a @MapKey(name="myProperty") annotációval definiálhatjuk mi legyen a kulcs (myProperty a kapcsolódó entitás egy tulajdonságának a neve). Ha a @MapKeyt a name definiálása nélkül használjuk, a célentitás elsődleges kulcsa lesz használva. A map kulcsa ugyanazt az oszlopot használja, mint a hivatkozott tulajdonság, vagyis nem jön létre új oszlop az értékek tárolására. Azonban miután a map megkapta a kulcs értékeket, nem marad szinkronban a hivatkozott tulajdonsággal, tehát ha megváltoztatjuk egy célentitás adott tulajdonságának értékét, a mapre ez nem lesz hatással.
@Entity
public class Customer {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
@OneToMany(mappedBy="customer")
@MapKey(name"number")
public Map<String,Order> getOrders() { return orders; }
public void setOrders(Map<String,Order> order) { this.orders = orders; }
private Map<String,Order> orders;
}
@Entity
public class Order {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
public String getNumber() { return number; }
public void setNumber(String number) { this.number = number; }
private String number;
@ManyToOne
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) { this.customer = customer; }
private Customer number;
}
-- Table schema
|-------------| |----------|
| Order | | Customer |
|-------------| |----------|
| id | | id |
| number | |----------|
| customer_id |
|-------------|
Másik lehetőség, hogy a map kulcsát egy külön oszlopban képezzük. Ennek testreszabására a következők használhatóak:
- @MapKeyColumn abban az esetben, ha a map kulcsa alap típusú. Ha nem adunk meg oszlopnevet, akkor az a tulajdonság_KEY alakú lesz. Pl.: orders_KEY.
- @MapKeyEnumerated/@MapKeyTemporal ha a map kulcsa felsorolás típus (enum) vagy Date.
- @MapKeyJoinColumn/@MapKeyJoinColumns ha a map kulcsa egyéb entitás.
- @AttributeOverride/@AttributeOverrides ha a map kulcsa beágyazható objektum. A key. előtaggal tudunk a beágyazott objektum tulajdonságaira hivatkozni.
Továbbá amennyiben nem használunk generikus típusokat, a kulcs típus megadható a @MapKeyClassszal.
@Entity
public class Customer {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
@OneToMany @JoinTable(name="Cust_Order")
@MapKeyColumn(name"orders_number")
public Map<String,Order> getOrders() { return orders; }
public void setOrders(Map<String,Order> orders) { this.orders = orders; }
private Map<String,Order> orders;
}
@Entity
public class Order {
@Id @GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
public String getNumber() { return number; }
public void setNumber(String number) { this.number = number; }
private String number;
@ManyToOne
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) { this.customer = customer; }
private Customer number;
}
-- Table schema
|-------------| |----------| |---------------|
| Order | | Customer | | Cust_Order |
|-------------| |----------| |---------------|
| id | | id | | customer_id |
| number | |----------| | order_id |
| customer_id | | orders_number |
|-------------| |---------------|
Megjegyzés A korábbi @org.hibernate.annotations.MapKey / @org.hibernate.annotation.MapKeyManyToMany annotációk helyett a fenti megközelítést érdemes használni.
A következő táblázatban látható, hogy milyen kollekció szemantikát használhatunk a leképezés típusa alapján.
Szemantika | megfelelője Javaban | annotációk |
---|---|---|
Bag | java.util.List, java.util.Collection | @ElementCollection vagy @OneToMany or @ManyToMany |
Bag elsődleges kulccsal | java.util.List, java.util.Collection | (@ElementCollection vagy @OneToMany vagy @ManyToMany) és @CollectionId |
List | java.util.List | (@ElementCollection vagy @OneToMany vagy @ManyToMany) és (@OrderColumn vagy @org.hibernate.annotations.IndexColumn) |
Set | java.util.Set | @ElementCollection vagy @OneToMany vagy @ManyToMany |
Map | java.util.Map | (@ElementCollection vagy @OneToMany vagy @ManyToMany) és ((semmi vagy @MapKeyJoinColumn/@MapKeyColumn) vagy @javax.persistence.MapKey) |
Speciálisan, java.util.List kollekciók @OrderColumn vagy @IndexColumn nélkül bagként lesznek kezelve.
Tranzitív perzisztenica kaszkádolással
[szerkesztés]A cascade attribútum CascadeType típusú tömböt kaphat paraméterül. Pl.: @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER). A Hibernateben használt kaszkád fogalom nagyon hasonló a JPAban használt fogalomhoz, de a szemantika és a kaszkád típusok kissé eltérnek:
- CascadeType.PERSIST: a persist vagy create utasítás hatására, vagy ha az entitás menedzselve van, a kapcsolódó entitások persist utasítását hívja meg.
- CascadeType.MERGE: a merge utasítás hatására, vagy ha az entitás menedzselve van, a kapcsolódó entitások merge utasítását hívja meg.
- CascadeType.REMOVE: az entitás törlésekor (delete( )) eltávolítja a kapcsolódó entitásokat.
- CascadeType.REFRESH: a refresh( ) hívásakor a kapcsolódó entitásoknál is meghívja azt.
- CascadeType.DETACH: a detach( ) hívásakor a kapcsolódó entitásoknál is meghívja azt.
- CascadeType.ALL: az összes eddigit magába foglalja.
Megjegyzés CascadeType.ALL lefedi a Hibernate speciális utasításait is, pl.: save-updage, lock, ...
Egy másik lehetőség az árva eltávolítási szemantika (orphan removal semantic) használata. Ha egy hivatkozott entitást eltávolítunk egy @OneToMany kollekcióból, vagy a @OneToOne kapcsolatból, az entitás törlésre jelölhető meg, ha az orphanRemoval igazra (true) van állítva. Más szavakkal: a hivatkozott entitás életciklusa a tartalmazóéhoz van kötve, mint a beágyazható objektumok esetén.
@Entity class Customer {
@OneToMany(orphanRemoval=true) public Set<Order> getOrders() { return orders; }
public void setOrders(Set<Order> orders) { this.orders = orders; }
private Set<Order> orders;
[...]
}
@Entity class Order { ... }
Customer customer = em.find(Customer.class, 1l);
Order order = em.find(Order.class, 1l);
customer.getOrders().remove(order); //order törölve lesz az adatbázisból
Asszociáció fetchelés
[szerkesztés]Két mód létezik az entitások fetchelésére: lusta (lazy) és mohó (eager). Ez a FetchType.LAZY és FetchType.EAGER paraméterekkel adható meg. EAGER esetén outer join selectet használ, hogy az összes kapcsolódó entitást letöltse az adatbázisból a memóriába, míg LAZY esetén csak az első hivatkozáskor hajt végre selectet a kapcsolódó entitásra. @OneToMany és @ManyToMany annotációk használatakor az alapértelmezés a LAZY, míg @OneToOne és @ManyToOne esetén az EAGER. Ha nem megfelelően használjuk az EAGERt, feleslegesen sok adatot tölthetünk le az adatbázisból.