Hibernate: přistupovat pomocí polí nebo vlastností?

Onehdá jsem s kolegou řešil, jestli v Hibernate (JPA) používat přístup pomocí polí (field) nebo vlastností (property). Uvědomuji si, že nejsem ani první ani poslední kdo něco podobného řeší, nicméně mi došlo, že v tom sám nemám moc jasno tak jsem si dal za domácí úkol to nastudovat.

O co jde? Když mapuji entity, můžu si vybrat, jak bude Hibernate k objektům přistupovat. Mohu například napsat takovouto třídu.

@Entity
@Configurable
public class Client  {
	
	@Id @GeneratedValue(strategy=GenerationType.SEQUENCE)
	private Long id;

	@Column(unique=true,nullable=false)
	private String personalNumber;

	...
}

V takovémto případě bude při načítání z databáze Hibernate data zapisovat přímo do polí. To znamená, že například pro ID nemusíme mít vůbec set metodu, Hibernate nebo jiný JPA poskytovatel to tam magicky zapíše.

Nebo mohu zvolit druhou variantu.

@Entity
public class ClientGroup {
	
	private Long id;
	
	private List clients = new ArrayList();
	
	private String name;

	@Id @GeneratedValue(strategy=GenerationType.SEQUENCE)
	public Long getId() {
		return id;
	}

	private void setId(Long id) {
		this.id = id;
	}

	@OneToMany(cascade=CascadeType.ALL)
	public List getClients() {
		return clients;
	}

	
	private void setClients(List clients) {
		this.clients = clients;
	}
	public void addClient(Client client)
	{
		clients.add(client);
	}
	...
}

V tomto případě jsem dal anotace nad get metody a Hibernatu jsem tím dal najevo, že má pro přístup používat vlastnosti (property). Tzn. při plnění instancí z databáze bude volat set metody, při ukládání do databáze bude používat hodnoty z get metod.

Protože tu tvrdošíjně používám českou terminologii, shrnu to do slovníku.
Field access – přístup pomocí polí – anotace nad poli
Property access – přístup pomocí vlastností – anotace nad get metodami

Dobrá zpráva je, že v jednom programu mohu používat oba přístupy. Jednu třídu mohu mapovat tak, druhou onak. Sice bych to moc nedoporučoval, ale teoreticky to jde. U jednotlivých tříd ale oba přístupy míchat nemůžeme. Nemohu si říci, že ID chci mapovat jako field a seznam klientů jako property. Specifikaci už si nepamatuji, ale v praxi to funguje tak, že se typ přístupu určuje podle toho, nad co dám anotaci @Id.

Takže tu máme dva různé přístupy a nabízí se otázka, který je vhodnější. Oba mají svá pro a proti. Já osobně preferuji přístup přes pole. Prostě se smířím s tím, že mi Hibernate magicky mění vnitřnosti objektu. Podle některých to porušuje zapouzdření. Částečně mají tito lidé pravdu, ale tento přístup je podle mě menším ze dvou zel. Radši se smířím s tím, že mi zapouzdření bude narušovat Hibernate, než abych se smířil s tím, že mi ho bude narušovat programátor.

Vezměme si příklad ID. To je obvykle nastavováno Hibernatem. Rozhodně nechci, aby mi ho nastavoval nebo měnil programátor. V případě přístupu přes pole jednoduše nevytvořím set metodu a mám hotovo. V případě přístupu přes vlastnost musím vytvořit set metodu. Může být klidně i private, ale musí tam být. Takže stejně musím nechat Hibernate, aby mi prolomil zapouzdření. Stejně ho musím nechat volat private metodu. Nebo ji mohu nechat public, ale pak se musím modlit, aby mi někdo ID nezměnil. Mě osobně připadá čistší tam ten setter vůbec nedávat.

Podobný problém je i u kolekcí. Obvykle nechci nechat programátora, aby mi kolekci nastavoval přímo (viz. ClientGroup příklad). Ale u přístupu pomocí vlastností stejně musím mít set metodu, i když opět může být private.

Property přístup je podle mě také o trochu náchylnější na chybu. Představme si, že do objektu přidáme metodu getFullName, která jen zřetězí křestní jméno a příjmení. Když máme anotace nad poli, není tu žádný problém, pokud máme anotace nad metodami, tak si bude JPA myslet, že chceme mít v databázi sloupeček FULL_NAME a ošklivě nám vynadá. Budeme to muset vyřešit dobře mířenou anotací @Transient.

Specifickou kapitolou jsou netriviální get a set metody. V set metodách můžu mít validace vstupních hodnot, v get metodách vracet defenzivní kopie. U přístupu přes pole to není problém. Ale pokud používám přístup pomocí vlastností tak se mohou dít ošklivé věci. Validační kód se bude volat při každém načtení z databáze, což může mít na výkonnost dopad jen malý. Horší je to s defenzivními kopiemi. Ty mohou ovlivnit mechanismus detekce změn. Pokud budu například vracet defenzivní kopii kolekce, Hibernate bude přesvědčen, že se mu změnil obsah kolekce a pokaždé danou kolekci z databáze smaže a vloží ji tam znovu. A to i v situaci, že jsme danou instanci vůbec nezměnili! To může mít na výkonnost dopad dost ošklivý.

Teď mi zrovna došlo, v čem je problém. V podstatě obvykle potřebujeme, aby entita měla dvě rozhraní. Jedno pro programátory, druhé pro perzistenci. To je přesně to, co mi zajistí mapování přes pole. Říkám tím, že ORM je taková chytřejší serializace po které chci aby mi uložila stav objektu. U klasické Java serializace si taky nikdo nestěžuje, že narušuje zapozdření tím, že nastavuje pole napřímo. Při přístupu přes vlastnosti na druhou stranu tvrdím, že ORM a programátor jsou si rovni a že by měli používat stejné rozhraní. Někdy to tak být může, ale obvykle tomu tak není.

Předchozí věta by tvořila pěkný závěr článku, naneštěstí ale ještě končit nemůžu. Je jeden konkrétní případ, kdy jsou anotace nad metodami lepší volba. Je to v případě, že načítáme ID u lazy proxy, které ještě není načtené z databáze. Řekněme, že dělám například toto:

clientGroup.getClient(0).getId()

Může se stát, že Hibernate zná jenom ID klientů, kteří patří do dané skupiny, ale ještě nemá načtená jejich data. V tom případe se používá proxy, která zajistí načtení dat, až když jsou potřeba. Když používám přístup pomocí polí, Hibernate musí všechny informace o klientovi načíst, i když mě zajímá jen ID. Neví totiž, že metoda getId() jenom vrací klíč. Neví jestli náhodou v této metodě nečtu třeba jméno. Nemá šanci to poznat. Teoreticky by se mohl podívat do kódu, ale to by už bylo příliš mnoho magie. Na druhou stranu pokud mám nad metodou getId() anotaci @Id, Hibernate ví, že to je ID a může mi ho rovnou vrátit, aniž by musel přistupovat do databáze.

Ale toto je jediný případ, v kterém je přístup přes vlastnosti lepší. Záleží případ od případu, jestli zrovna tuto vlastnost použiji. Dovedu si představit aplikaci, kde budu například ID používat i v UI pro odkazy v tabulce. Dovoluji si ale tvrdit, že i tak by bylo lepší použít nějakou lepší fetch strategii než se prát s anotacemi nad metodami. Jinak to totiž zavání předčasnou optimalizací.

Další čtení:
http://forum.hibernate.org/viewtopic.php?p=2349555&sid=dd846f97d3eb352ed0b8fbe57739801c

http://chstath.blogspot.com/2007/05/field-access-vs-property-access-in-jpa.html

http://shashivelur.com/blog/2009/01/hibernate-direct-field-acess-and-encapsulation/

Tags: , ,

8 Responses to “Hibernate: přistupovat pomocí polí nebo vlastností?”

  1. benzin Says:

    Mno a jak je to s dedicnosti? Private fieldy preci nezdedim a hibernate mi je nenaplni, naproti tomu podedene anotovane public metody mi hibernate naplni.

  2. Mystik Says:

    benzin: V pripade, ze chci by se mi filedy dedily je proste udelam protected ne?

  3. kve Says:

    My na projektu mame takovou peknou ... no proste ke kazdemu fieldu (property) davame prefix m - jako ze to je member (pak k parametrum metod 'a' a ke statickym prvkum 's' ).

    Ale get set metody jsou jiz pochopitelne bez tohoto prefixu, a anotaci mame pak nad get metodami - kvuli tomu aby se to hezky psalo v HQL ...

    Tedy mame
    class A {
    int mNeco;

    @Column(name="neco")
    public int getNeco() ..
    }

    a pak je "select a from A a where a.neco=5"

    Tedy asi dalsi pripad kdy se vyplati to psat nad getMetody ..

  4. benzin Says:

    4Mustik: Nechci aby z potomku byli videt fieldy, popravde sou mi protected fieldy z duse odporne. Chci aby potomek videl get a set metody, ale kdyz ukladam stav objektu, chci aby se mi ulozil cely a ne jenom cast, k cemuz dojde kdyz sou anotace nad fieldama, ktere sou private.

  5. finc Says:

    to benzin: Ja mam a to, ze takto to neni. Zkusil jsem si udelat abstraktni tridu s anotacemi nad fieldy a v potomkovi se normalne nasetuji.

  6. Lukáš Křečan Says:

    K té dědičnosti: Schválně jsem se kouknul do JPA specifikace, mluví o tom kapitola 2.9. Pokud mám v hierarchii jenom Entity nebo MappedSuperclass, tak vše funguje jak má bez ohledu na to jaké mapování použiji. A to i u private polí. Jediný rozdíl by asi byl, kdybych měl za předka normální třídu bez anotace. Pak bych teoreticky u potomka mohl zastínit get a set metody, oanotovat je a volat z nich get a set metody předka. Když to neudělám, tak opět v přístupech není žádný rozdíl.

  7. Yeti Says:

    Písať anotácie nad metódy je výhodnejšie aj v tedy, keď používate PropertyChangeSupport v súvislosti s bindingom.
    Ak by som použil prístup cez polia, pri opakovanom načítaní objektu z databázy, nebudú o týchto zmenách vedieť komponenty, ktoré zobrazujú jeho vlastnosti.

  8. Ondrej Medek Says:

    http://stackoverflow.com/questions/2593722/hibernate-one-to-one-getid-without-fetching-entire-object/3238182#3238182