Hibernate annotációk - entitás asszociációk/kapcsolatok

A Wikikönyvekből, a szabad elektronikus könyvtárból.

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]