iBatis SqlMaps - tak trochu opomíjený ORM

Nedá mi to, abych nenapsal něco o frameworku iBatis. Někteří jej možná znáte, někteří jste možná o něm už slyšeli, ale dle trafficu na java.cz konferenci bych řekl, že jej většina z vás přehlíží. Zůstal nepovšimnut i v našem krají protřelém CZ podcastu číslo 8. Myslím, že je to škoda a proto jsem se rozhodl o malou osvětovou, nebo-li, jak by řekl Roumen, evangelizační práci.

Ještě v úvodu bych rád podotknul, že někomu se může zdát že to není ten pravý "entrprase" framework, není kompatibilní s JPA, nemá anotace a vůbec je celý takový jednoduchý. Že je možná až tak jednoduchý, že jeho použití ani nemůže přinést tu pravou zábavu ve formě hledání příčin mystického chování vaší aplikace. iBatis se navíc nehonosí žádným buzzwordem, který by se dal prodat zákazníkovi. Pokud si to myslíte, máte pravdu - šetřte své oči, už nemusíte číst dál, protože tento článek určitě není pro vás.

...

Po drsnějším úvodu mi doufám zůstali ti praví čtenáři ;). Takže přejděme k práci ...

iBatis je ORM framework, jehož historie sahá někam k roku 2002. Je to stabilní a odladěný kus kódu se skvělou dokumentací a průhlednými zdrojovými kódy, jehož cílem je zjednodušit programátorovi práci s JDBC. Nesnaží se odstínit programátora plně od vlastní databáze, jako to činí JPA compliant frameworky. Zůstává někde na půli cesty mezi plnotučným ORM a prostým JDBC.

Fíčury

Základní myšlenky iBatisu by se daly shrnout možná takto:

  • nenutí programátora učit se o moc víc než to co už umí (JDBC) - learning curve je 3 - 8 hodin ... v podstatě už za 3 hodiny můžete programovat, aniž byste mohli udělat nějakou zásadní chybu, která by vás nutila něco předělávat, když budete framework používat intenzivněji a déle
  • nesnaží se zakrýt práci s DB - tím je jednoduchý a transparentní
  • poskytuje maximální podporu programátorovi při rutinních činnostech
    • mapuje vrácené result sety na Java objekty (dao třídy, jsou stejně tenké, jako když použijete Hibernate)
    • nenutí vás fetchovat celé objekty - klidně si vrácený set může převést na mapu: název sloupce / hodnota nebo jen na primitiv
    • není nutné psát mapování property tříd na sloupce - iBatis podporuje autowire by name
    • jednoduše řeší vytahování záznamů po stránkách
    • poskytuje plugovatelnou cachovací logiku - řízení cache je na vás (což možná nemusí být výkonnostně optimální, zato se nikdy neztratíte)
    • jednoduchá práce s transaction / dávkami příkazů je samozřejmostí
    • stejně jako ostatní ORM vás oddělí od práce s connection a vlastními resultsety - už nikdy nezapomenete na close() ;)
  • odpovědnost za psaní vlastních SQL dotazů nechává na programátorovi - SQL máte plně pod kontrolou a jednoduše a bez hluboké znalosti frameworku vymáčknete z iBatisu i takové chuťovky jako uložené procedury (a stále máte podporu tohoto frameworku - nejste nuceni se snížit k JDBC)
  • odděluje SQL dotazy od kódu - při požadavku na přenositelnost mezi DB se velmi jednoduše pouze upraví nekompatibilní SQL dotazy (což je sice oproti např. Hibernate práce navíc, ale když to vezmete kolem a kolem, té práce není zas až tak moc, pokud máte automatické testy)
  • nenutí vás psát sáhodlouhé XML (s SQL příkazy) - obsahuje řadu vychytávek, které vám ušetří psaní redundantního kódu - dynamické SQL, podmínky v SQL, includy, extenze - a přesto je to všechno strašně jednoduché

Jak iBatis funguje?

Jak vidno z níže uvedeného schématku vyjmutého z iBatis dokumentace, framework s skládá z následujících části:

  • SqlMapConfig.xml
    XML soubor, který je pouze rozcestníkem k dalším mapovacím souborům a jenž obsahuje "globální" konfiguraci iBatisu - defakto je to obdoba hibernate.properties souboru
  • SqlMap.xml
    Vlastní mapovací soubory (např. User.xml), který obsahuje SQL dostazy spojené se serializací a deserializací objektů do a z databáze - opět obdoba *.hbm z Hibernate
  • Mapped statementy
    Z výše uvedených xml souborů iBatis při inicializaci vytvoří "mapped statementy" - tzn. objektovou reprezentaci vaší konfigurace; za běhu si potom pro tyto mapped statementy iBatis postupně vytvoří a zacachuje prepared statementy
  • Vstupní data
    tedy parametry jednotlivých dotazů - může se jednat o Java beany, primitivní typy, mapy a nebo XML
  • Výstupní data
    tedy výstupní naplněné Java "objekty" - může se jednat o Java beany, primitivní typy, mapy a nebo XML

Schémátko vyňaté z iBatis dokumentace:
Schéma iBatis

Ukázka práce s iBatis

V následujícíh odstavcích bych krátce ukázal, jak se s iBatis pracuje.

Konfigurace

Začneme konfigurací. Ve verzi 2.x musíme dodat vždy dva typy konfiguračních souborů:

SqlMapConfig.xml
``` xml ```
a vlastní SqlMap.xml - v našem případě třebas Account.xml
``` xml insert into ACCOUNT ( ACC_ID, ACC_FIRST_NAME, ACC_LAST_NAME, ACC_EMAIL values ( #id#, #firstName#, #lastName#, #emailAddress# ) update ACCOUNT set ACC_FIRST_NAME = #firstName#, ACC_LAST_NAME = #lastName#, ACC_EMAIL = #emailAddress# where ACC_ID = #id# delete from ACCOUNT where ACC_ID = #id# ```

A to je vše. Tím máme zkonfigurováno. Již z tohoto příkladu si můžete udělat obrázek, že iBatis neprovádí žádnou náročnou magii jako například Hibernate nebo TopLink. Vše je až neuvěřitelně průhledné, jednoduché a tím pádem minimálně náchylné k chybám.

DAO - Třída

Nuže a nyní si ukážeme příklad DAO třídy. Bude to také krátké a průrazné. Navíc od verze 3.0 by nám měla stačit už pouze deklarace interface ... o vlastní implementaci by se iBatis postaral sám.

AccountDao.java
``` java package com.mydomain.data; import com.ibatis.sqlmap.client.SqlMapClient; import com.ibatis.sqlmap.client.SqlMapClientBuilder; import com.ibatis.common.resources.Resources; import com.mydomain.domain.Account; import java.io.Reader; import java.io.IOException; import java.util.List; import java.sql.SQLException; /** * Vzato z jednoduchécho příkladu přikládaného k iBatis. * Pozor toto neberte jako příklad BEST PRACTISES a radši jukněte * na příklad JPetStore 5.0 na http://www.ibatis.com * * V případě integrace se Spring bude vše ještě jednodušší. * Stačí pouze podědit z SqlMapClientDaoSupport a o inicializaci * máme postaráno. */ public class SimpleExample { /** * Instance SqlMapClient instances jsou thread safe, * takže nám stačí pouze jedna. V našem příkladě použijeme * singleton. */ private static SqlMapClient sqlMapper; /** * Opět toto není ideální způsob inicializace, ale pro účely * jednoduchého příkladu nám to stačí. * * Opět v případě použití Springu, tady nemusíme nic dělat. * V kódu by nám pak stačilo vždy jen zavolat metodu * getSqlMapClientTemplate(). */ static { try { //načtem konfiguraci Reader reader = Resources .getResourceAsReader("com/mydomain/data/SqlMapConfig.xml"); //vytvoříme klienta na kterém budeme volat jednotlivé příkazy sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader); reader.close(); } catch (IOException e) { // Fail fast. throw new RuntimeException( "Something bad happened while building the SqlMapClient" + " instance." + e, e); } } public static List selectAllAccounts () throws SQLException { return sqlMapper.queryForList("selectAllAccounts"); } public static Account selectAccountById (int id) throws SQLException { return (Account) sqlMapper.queryForObject("selectAccountById", id); } public static void insertAccount (Account account) throws SQLException { sqlMapper.insert("insertAccount", account); } public static void updateAccount (Account account) throws SQLException { sqlMapper.update("updateAccount", account); } public static void deleteAccount (int id) throws SQLException { sqlMapper.delete("deleteAccount", id); } } ```

Tím je hotové i DAO a skončili jsme. Další nespornou výhodou je, že o logování se vám postará iBatis, takže když je potřeba, v lozích detailně vidíte, co se vám vlastně na datové vrstvě děje.

Skvělá dokumentace

Pro rychlou learning curve je dobrá dokumentace nezbytným základem. V tohle ohledu je iBatis řekl bych opravdu na špičce. Doporučuji si udělat rychlý přehled o použití frameworku na:

  1. SimpleExample aplikaci (která je součásti bundle iBatis a částečně jsem ji využil i v uvedených příkladech) - na čtyřech třídách si rychle uděláte přehled
  2. pokračovat krátkým tutorialem, který vás rychle nakopne ... a v podstatě už můžete začít programovat
  3. prostudovat komplexnější JPetStore aplikaci, kde jsou ukázané trochu pokročilejší fíčury spolu s Developer dokumentací - tam je přehledně vše důležité
  4. když nakouknete do zdrojových kódů, lehce zjistíte, že nejsou nijak rozsáhlé, jsou také dobře zdokumentované a přehledné

iBatis vs. JDBC4

V diskusích se párkrát objevil názor, zda má iBatis po zavedení JDBC stále smysl. Existují různé názory, ale pro iBatis mluví stále pár argumentů:

  • JDBC 4.0 je Java 1.6+ / iBatis je 1.4+ (do verze 2.20 1.3+)
  • iBatis je tu a umí pracovat i s drivery verze 3.0 a 2.0 (dvojkové JDBC bude ale od určité verze deprecated ... možná už od 2.20, podobně jako JDK 1.3) - většina velkých vendorů ještě JDBC 4.0 drivery ještě ani nemají, nebo mohou podporovat jen nové verze databází (např. Oracle od 11g, která je teprve beta ...)
  • iBatis je odladěný a vychytaný, s novými JDBC 4.0 drivery nám každý vendor přinese svou snůšku bugů, které jim postupně společně teprve vyladíme
  • iBatis má možnosti dynamických SQL dotazů, extenzí a includů, které JDBC 4.0 nepodporuje
  • JDBC 4.0 je zafixované a nebude se rozhodně dynamicky rozvíjet tak jako může iBatis, který s sebou netáhne takovou "mrtvou váhu"
  • JDBC 4.0 je možné konfigurovat s pomocí anotací (což je poměrně sympatická vlastnost) - iBatis prozatím nikoliv, ale podpora anotací je již v roadmap pro verzi 3.0 spolu s dalšími velmi sympatickými fíčurkami, jako např:
    • Interface binding - pro napsání DAO nám bude stačit deklarace interface, o implementaci se postará iBatis sám
    • Anotace
    • Konfigurace konvencí - tak tohle bude asi největší masáž a částečné odchýlení od principů, které jsem uvedl na začátku; v podstatě se jedná o to, že by iBatis sám dokázal vygenerovat jednoduché SQL dotazy pro základní sadu jednoduchých SQL příkazů (jako jsou např. insert / update / delete + jednoduchý select) pouze na základě deklarace metody v interface; okolo tohoto bodu se ale ještě vedou další debaty
    • Více úrovňová konfigurace - vzestupně podle priority konvence, anotace (může předefinovat konvenci), XML (může předefinovat konvenci a anotace) a java kód (může předefinovat cokoliv)

Odkazy na primární zdroje (kdybych náhodou něco interpretoval špatně ;)):

iBatis DAO

To co jsem doposud popisoval byly iBatis SqlMaps. Z rodiny iBatis pochází ještě jedna "utilitka" nazvaná iBatis DAO, jejímž cílem je stadnardní cestou programátorovi zpřístupnit práci s transakcemi. iBatis SqlMaps a DAO jsou dvě nezávislé knihovny, které lze použít i samostatně. Já například používám jen SqlMaps knihovnu, jelikož o transakce a práci s connection se mi stará Spring.

Pro ty, kteří Spring nepoužívají se možná iBatis DAO bude hodit. Umožňuje vám zkonfigurovat přípojení k databázi (simple JDBC, DBCP, JNDI), transakčního manažera a na úrovni kódu vám poskytuje zástupné třídy pro práci s transakcemi. Transakční manažeři jsou pluggovatelní a v současné době si můžete vybrat mezi JDBC, JTA, SqlMap, Hibernate a external transakčním manažerem.

Pro vlastní dao třídy existují Template třídy frameworku, které vám zpřístupňují takové ty metody jako getConnection() (JDBC, JNDI), getSession() (Hibernate) nebo getSqlMapExecutor() (SqlMaps) - podobně jako v případě Springu.

Myslím, si že z tohoto krátkého odstavečku jste asi nemohli pochopit, co přesně iBatis DAO dělá, a proto bych vás rád odkázal na kraťoučkou dokumentaci, která veškerou práci s knihovnou přehledně popisuje.

Závěrem

Vřele doporučuji na iBatis kouknout i v případě, že používáte Hibernate, TopLink nebo jiné ORM. Opakuji to tu alespoň po páté, ale budete překvapeni jednoduchostí, průhledností a bezproblémovostí iBatisu. Programátoři s velkou zkušeností s Hibernate pravděpodobně nepřejdou - Hibernate je opravdu mocnější nástroj, ale má řadu úskalí, o které si člověk může ošklivě nabít. Pro programátory začátečníky doporučuji místo JPA / Hibernate / Toplink, začít nejdříve na iBatisu. Ušetří vám opravdu velké množství práce, nebudete muset do něj pronikat nijak dlouho, nenachytá vás do žádné pasti a odmění se vám bezproblémovou službou.