Bez dalších řečí se rovnou pustím do popisu. Jak už jsem předeslal, základem dané filtrace nad daty, jsou anotace. Uvedu jejich výčet a popis.

@Criteria (class)
Jedná se o základní anotaci, která říká, že se jedná o „filtrační“ objekt, který je schopen nabídnout své atributy k porovnání. Jediným parametrem je entita, která říká o jakou entitu se jedná.

@Criterion (atribut) Popisuje atribut, který je automaticky brán jako jeden z prvků filtrace. Samotná anotace obsahuje moznosti, které označí, jak se k danému atributu bude přistupovat. Zde je výčet některých z nich:
  • property – název atributu podle dané entity, může se jednat i o vnořenou hodnotu (zakaznik.id)
  • operator – definuje typ porovnávání mezi hodnotou atributu a názvem atributu, typ porovnávání je výčtový typ, který lze rozšířit
  • excludeEmptyString – určuje, zda se bude brát v potaz prázdný String či se bude ignorovat
V budoucnu předpokládám rozšíření těchto vlastností. Ale držím se pravidla: Méně je někdy více. Přeci jen nechci tvořit kód, který bude obsahovat spoustu deprekovaných způsobů.

@Between (atribut)
Složení minimální a maximální hodnoty, která je prováděna na základě definovaného idf a property, což je výčtový typ, který může být buď MIN či MAX. Výhodou tohoto použití je také v tom, že pokud jedna z hodnot neexistuje, automaticky převádí BETWEEN na porovnání jedné hodnoty. Pokud například hodnota pro MAX bude empty a MIN bude existovat, bude generována následující podmínka: WHERE datum >= ‚hodnota‘.

@Conjunction (atribut)
Konjunkce funguje podobně jako v Criteria API. Na základě definovaného idf se spojí ty hodnoty, které k sobě patří. Zde ovšem existuje jedno omezení (stejně jako u disjunkce): Vnořené podmínky. Je to další krok, který budu muset vyřešit.

@Disjunction (atribut)
Opět podobný způsob jako v případě konjunkce. Zde se tedy generuje něco jako: WHERE (property [operator] value OR …). Spojení daných atributu se opět provádí pomocí stejného názvu v idf.

@Alias (class)
Jelikož se často stává, že se potřebuji dotazovat na sloupec, který je v nějaké tabulce, která je v relaci s danou entitou, potřebuji aliasovat danou entitu, pro kterou bude platit nasledující alias.
Zde rovnou uvedu příklad:
Mám zaměstnance, který spadá do daného střediska, z kterého chci znát název, podle kterého filtruji. V OQL by mohl dotaz vypadat následovně: FROM Zamestnanec z WHERE z.stredisko.nazev = ‚Vyroba‘. V Criteria API se k danému sloupci dostanu přes alias, takže by to mohlo vypadat následovně: @Alias(associ­ationPath = „stredisko“, alias = „s“).
Aby bylo možné definovat aliasů více, existuje anotace s nazvem Aliases, která může obsahovat pole daných aliasů.

@OrderBy (class)
Poslední popisovaná anotace slouží k definování řazení dat. Jelikož požadavek na řazení dat je častým jevem, existuje možnost nadefinovat filtračnímu objektu tuto vlastnost. Daná anotace obsahuje pole anotací s názvem @OrderValue, která má dvě vlastností a tím je název atributu pro řazení a typ řazení, který je výčtvový typ ASC nebo DESC.

Samotné použití je postaveno na DetachedCriteria. Ti, co znají Hibernate vědí, že se jedná o criteria, které neobsahují Session. Jinými slovy, takováto criteria si můžete vytvořit ještě v době, kdy o Hibernate Session nemá Váš kod ani ponětí. Jistě je to dobrý způsob jak zajistit určité odělení od zbytku vlastní implementace.

K přístupu slouží třída s názvem: „CriteriaDAOFac­tory“. Jak už název napovídá, jedná se o factory, která vrací interface CriteriaDAO. Daný interface již obsahuje metodu vracející DetachedCriteria. Dané použití může vypadat následovně:
MyFiltr filtr = new MyFiltr();
// naplneni filtru
CriteriaDAO d = CriteriaDAOFac­tory.getCrite­ria(filtr);
DetachedCriteria dc = d.getDetached­Criteria();

Nyní uvedu několik příkladů definování daného filtru a jeho ekvivalent v podobě SQL dotazu. U všech ukázek vynechám settry a gettry, které jsou nezbytnou součástí. Pokud někdo chce tvořit neměnný objekt, tak samozřejme settry může vynechat. Takže jedem:

@Criteria(entity = Zamestnanec.clas­s)
public class ZamestnanecFiltr {

    @Criterion(pro­perty=„cislo“)
    private String id;

    @Criterion(ope­rator=Restric­tionDAO.LIKE)
    private String prijmeni;
 }
SELECT * FROM zamestnanec
 WHERE cislo = ‚value‘ AND prijmeni LIKE ‚%value%‘
@Criteria(entity = Zamestnanec.clas­s)
@OrderBy(valu­es={@OrderValu­e(name = „prijmeni“)})
@Alias(associ­ationPath = „stredisko“, alias = „s“)
public class ZamestnanecFiltr {

    @Criterion(pro­perty=„s.nazev“)
    private String nazev;

    @Criterion(pro­perty=„datumPri­jeti“)
    @Between(idf = „dp“, property = BetweenDAO.MIN)
    private Date datumOd;

    @Criterion(pro­perty=„datumPri­jeti“)
    @Between(idf = „dp“, property = BetweenDAO.MAX)
    private Date datumDo;
 }
SELECT * FROM zamestnanec z
 INNER JOIN stredisko s ON z.stredisko = s.cislo
 WHERE s.nazev = ‚value‘ AND z.datumPrijeti BETWEEN ‚min‘ AND ‚max‘
 ORDER BY z.prijmeni

Tím bych asi ukončil tento popis. Samozřejmě, lze dané věci dobře kombinovat. Je jasné, že se naleznou další potřebné funkce, které nechám spíše vyplynout postupem času. Pokud by někdo měl zájem, mohu se pokusit danou funčnost implementovat.

Poslední věcí, kterou chci zmínit je pár otázek, které bych rád nějak vyřešil. Jelikož vše má svá úskalí… Pokud bude mít někdo zájem, budu vítat jakoukoli pomoc či nakopnutí.
  • řazení dat je definováno staticky, jak implementovat dynamický způsob
  • specifikování získaných dat, jak omezení na určité sloupce, tak vlastní DTO či inicializace LAZY
  • vnořené konjunkce a disjunkce, které závisí na předchozích podmínkách, a zda má vůbec cenu něco takového implementovat
  • dynamické rozhodování zvoleného operátoru, uživatel například může mít k dispozici volbu mezi (LIKE, <, >, =)

Samotný projekt je psán v NetBeans, proto ho také jako NetBeans projekt vystavuji. Předem se omlouvám za absenci JUnit testů. Upřímně, zatím jsem neměl dostatek času vše otestovat a navíc testy mám komponovány přímo na danou specifikaci, ke které je třeba existence daného modelu a zdroje. Samotná implementace je závislá na Hibernate API a na log4j. Knihovna log4j je v adresáři lib, knihovny pro Hibernate neuvádím záměrně. Pro práci s Hibernate existuje mnohem větší závislost :)

Projekt ke stažení: IrminsulCriteria

V minulém příspěvku jsem psal o tom, jakým způsobem nadefinovat základní DAO vrstvu. Zvolil jsem způsob, který se stal zavislý na Hibernate Session. Tento „vendor“ nabízí i jednu z věcí, kterou jsem si pomalu oblíbil, a tím jsou Criteria API.

Psaní základních SQL dotazů jsem nahrazil OQL dotazy. OQL je v podstatě velice podobný SQL s tím rozdílem, že daný dotaz se provádí nad entitami reprezentující data v DB. Díky tomu odpadá nutnost psát ruzné JOIN konstrukce, specifikovat vrácené sloupce a hlavně získávám nezávislost na použitém databázovém systému. Převod z MySQL na Oracle či MSSQL je triviální záležitost spočívající v přepsání pár řádku v persistence.xml či v přepsání JDBC zdroje v aplikačním serveru.

Ale zpět. I když je OQL velice elegantní způsob psaní, stále obsahuje velké omezení, které spočívá v tvorbě dynamických dotazů. Pokud aplikace přistupuje k datům tak, že je získává na základě uživatelského filtru, musím dotaz poskládat z daného Stringu, což je dost otravující záležitost.
K tomuto účelu se velice hodí Criteria API. Nebudu zde popisovat funkci Criteria API, k tomuto účelu slouží referenční příručka. U Criteria API se mi velice zalíbila možnost filtrovat data přes danou entitu. K tomuto účelu zde existuje objekt „Example“. Při použití tohoto přístupu mohu velice snadno získat data filtrovaná podle daných sloupců (atributů) v tabulce (entitě).

Jenže…

Pokud si například řeknu: „Chci vybrat všechny záznamy s podmínkou WHERE datum BETWEEN od AND do“, dostanu se opět do slepé uličky a musím přistoupit zpět k čistému Criteria API nebo dokonce k OQL.

Toto byl první důvod, proč jsem přistoupil k implementaci vlastního způsobu, který je řízen pomocí anotací. Ještě než přejdu k samotnému způsobu, musím zmínit druhou pozitivní věc.

Pokud začnu tvořit DAO vrstvu, začne se mi každý DAO objekt hemžit metodami jako: findByDatumPrijeti, findByXXX… Jistě není nic špatného na implementaci daných metod, až na to, že metody obsahují parametry, které již jasně říkají, jaká data mají být výsledkem (návratovou hodnotou).
Uvedu malý příklad (pro pozdější srovnání):

public List<zakazka> findForExample(String zakaznik, Date datumOd, Date datumDo) {

// implementace Criteria API, OQL ci suroveho SQL

}

Jak je z ukázky patrné, metoda vybíra zakázky, které spadají jistému zákazníkovi a jejich datum je v rozmezi datumOd-datumDo.

K odstranění psaní samotných dotazů použiji následující kroky:
  • vytvořím objekt pro filtraci
  • daný objekt anotuji příslušnými „metadaty“
  • zruším metodu findForExample a v DAO vytvořím obecnou metodu: findByCriteri­a(Object filtr)

Jediné, co mi zůstane je objekt pro filtrování. Sám se svými metadaty je schopen řici, co vlastně chci. Následující ukázka je ekvivalentní předchozímu způsobu:

@Criteria(entity = Zakazky.class)
public class ZakazkyForExam­pleFiltr {

    @Criterion(pro­perty=„zakaznik­.id“)
    private String zakaznik;

    @Criterion(pro­perty = „datumPrijeti“)
    @Between(idf = „dp“, property = BetweenDAO.MIN)
    private Date datumPrijetiOd;


    @Criterion(pro­perty = „datumPrijeti“)
    @Between(idf = „dp“, property = BetweenDAO.MAX)
    private Date datumPrijetiDo;

    // settry, gettry
}

// pouziti
ZakazkyForExam­pleFiltr filtr = new ZakazkyForExam­pleFiltr();
// naplneni filtru
List<zakazky> result = dao.findByCri­teria(filtr);
  

Pro lidi nesnášející anotace, bude tento způsob jistě nepoužitelný, pro lidi snažící se o co největší usnadnění a zpřehlednění své práce, naopak přínosem. Věřím, že každý si již ten svůj zpusob nalezl a nehodlá ho měnit, na druhou stranu při pohledu na takovýto kod si pokládám jednu otázku: „Není přece jen efektivnější popsat kod metadaty a nechat zbytek na daném frameworku?“.

Jelikož se mi článek rozrostl, rozhodl jsem se ho rozdělit do dvou částí. V první části jsem popsal důvody a uvedl malou ukázku. V druhé části uvedu možnosti a nabídnu danou funkčnost ke stažení. Tedy ten základní „motor“, který lze jednoduše „přilepit“ do své DAO vrstvy. Navíc uvedu další plány a položím otázky, na které si zatím nedokážu odpovědět.

Prosinec 13th, 2007DAO pattern + generika pro EJB3

Když jsem se dostal do fáze většího poznávání J2EE, došlo mi, že vrstvení logiky je nedílnou součástí úspěchu při návrhu.
Jistě by nebylo moudré nechat „dolování dat“ rozházené po aplikační logice tak, jak ho zrovna potřebuji. Zde nastupuje DAO (Data Access Object), což je návrhový vzor sloužící ke komunikaci mezi persistentní vrstvou a zbytkem světa.

Asi nejvíce mě zaujala možnost, kde DAO vrstvu definuji pomocí generiky a abstraktní třídy. Více než tisíc slov je lepší přímá ukázka kódu.

První věcí je interface, který obsahuje všechny důležité metody, které je třeba pro DAO jednotlivých entit třeba vystavit:
public interface GenericDAO<t, ID extends Serializable> {
       public T find(ID id);
       public List<t> findAll();
       public T create(T entity);
       public T save(T entity);
       public void remove(T entity);
 }
Jednotlivé metody jsou vcelku jasné. Jedná se o základní operace jako: získej data, vytvoř, ulož, smaž. Co se týče generických typů, jedná se o entitu, které přísluší dané DAO a o typ primárního klíče. Typem primárního klíče může být jak základní datový typ jako String, Integer, tak i složený primární klíč, např: ZakazkaPK (obsahuje číslo zakázky a datum vystavení).Nyní mohu přejít k vlastní implementaci lokálního interface:
@Local
public interface ZamestnanecDAOLocal extends GenericDAO<za­mestnanec, String> {
 }
Samotná EJB beana může vypadat následovně:
@Stateless
public class ZamestnanecDAOBean implements ZamestnanecDAOLocal {
       @Persistence­Context(unitNa­me = „myDB“)
       private EntityManager em;
       //… implementace metod
 }

Velkou nevýhodou je ovšem fakt, že pokaždé musím implementovat metody, které jsou potřeba ke splnění požadavku z GenericDAO.

Zde nastupuje abstraktní třída, která za nás vyřeší onu potřebnou implementaci.
public abstract class AbstractDAO<t, ID extends Serializable> implements GenericDAO<t, ID> {
       private Class<t> entityBeanType;
       private EntityManager em;
       public AbstractDAO() {
              // vytvari, ziskava typ entity pro dane DAO
              this.entityBe­anType = (Class<t>) ((Parameterized­Type)
                            getClass().get­GenericSuperclas­s())
                            .getActualType­Arguments()[0];
       }

       public Class<t> getEntityBean­Type() {
              return entityBeanType;
       }

       @Persistence­Context(unitNa­me = „default“)
       public void setEntityMana­ger(EntityMana­ger em) {
              this.em = em;
       }

       protected EntityManager getEntityMana­ger() {
              if (em == null) {
                     throw new IllegalStateEx­ception(„…“);
              }
        return em;
       }

       public T find(ID id) {
              T entity;
              entity = getEntityMana­ger().find(ge­tEntityBeanTy­pe(), id);
              return entity;
       }

       // … implementace zbylych metod
 }
Nyní stačí danou abstraktní třídu nechat dědit samotnou implementací EJB beany:
@Stateless
public class ZamestnanecDAOBean extends AbstractDAO<za­mestnanec, String> implements ZamestnanecDAOLocal {
 }

Takto definovaná beana je schopna podědit abstraktní třídu a například anotovaný setter pro entitymanager převzít na svá bedra. Podle specifikace je totiž možné EJB beany nechat dědit mezi sebou, s vyjímkou, že dependency injections a další služby jsou poté managovány potomkem, v našem případě „ZamestnanecDA­OBean“.

Aby samotné DAO nebylo tak chudé, přidal jsem si několik dalších možností. Některé jsou již specifikovány na danou implementace ORM frameworku. Osobně jsem díky tomu přešel na Hibernate Session a Criteria API. Tady jsou některé další metody:

    /
     * <p>Vraci vsechny zaznamy z dane tabulky, ktera prislusi entite.
     * Navic umoznuje definovat razeni podle <i>Hibernate Criteria API: Order</i></p>
     * <p>Zpusob pouziti: {@code findAll(Order­.asc(„jmeno“), Order.desc(„prij­meni“))}</p>
     
     
@param orderBy definice razeni zaznamu
     * @return vsechny zaznamy, pokud zadne neexistuji, vraci null
     /

    public List<t> findAll(Order… orderBy);

    /

     
<p>Vraci vsechny zaznamy z dane tabulky, ktera prislusi entite.
     * Navic umoznuje definovat omezeny vyber dat, pouzivane napr. pro strankovani.</p>
     * <p>Zpusob pouziti: {@code findAll(0, 20); } Prvni radek je roven 0</p>
     
     
@param first prvni radek, od ktereho se vybiraji zaznamy
     * @param offset maximalni pocet vybiranych zaznamu
     * @return vsechny zaznamy dle definovanych omezenich
     /

    public List<t> findAll(int first, int offset);

    /
     
<p>Vraci vsechny zaznamy z dane tabulky, ktera prislusi entite.
     * Navic umoznuje definovat omezeny vyber dat, pouzivane napr. pro strankovani
     * a definovat razeni podle <i>Hibernate Criteria API: Order</i></p>
     * <p>Zpusob pouziti: {@code findAll(0, 20, Order.asc(„jme­no“)); } Prvni radek je roven 0</p>
     
     
@param first prvni radek, od ktereho se vybiraji zaznamy
     * @param offset maximalni pocet vybiranych zaznamu
     * @param orderBy definice razeni zaznamu
     
     
@return vsechny zaznamy dle definovanych omezenich
     /

    public List<t> findAll(int first, int offset, Order… orderBy);

    /

     
<p>Dohledava zaznamy na zaklade Hibernate Example.</p>
     * <p>Spousti: {@code findByExample(e­xampleInstance, false, false, false, excludeProper­ty)}</p>
     * <p> Zpusob pouziti: <a href=„http://­www.hibernate­.org/hib_docs/re­ference/en/html/q­uerycriteria.html“>15­.6. Example queries</a>
     
     
@param exampleInstance s naplnenymi vlastnostmi pro vyber
     * @param excludeProperty pradavajici vlastnosti pro vyber pres example
     * @return dohledane zaznamy
     /

    public List<t> findByExample(T exampleInstance, StringexcludeProper­ty);

    /**
     
<p>Ziskava pocet vsech radku z tabulky prislusici dane entite</p>
     
     
@return pocet vsech radku
     */

    public int countAll();

© 2007 finc weblog | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress