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.

Források szerkesztés

Kapcsolódó szócikkek szerkesztés