31 marca 2007

Uruchamiamy aplikację JPetStore dla Apache iBATIS Data Mapper

Parę dni temu, w artykule Wstęp do technologii Apache iBATIS Data Mapper opisałem podstawy iBATIS’a, dziś przyszła kolej, aby uruchomić pierwszą aplikację. Na stronie projektu mamy gotowy przykład, osławiony JPetStore. Zaczynamy od pobrania i rozpakowania pliku JPetStore-5.0.zip, powstaje katalog JPetStore-5.0. W podkatalogu build\wars znajduje się plik jpetstore.war. Aby pobawić się chwilę aplikacją wystarczy wrzucić ten plik na nasz ulubiony serwer aplikacyjny i tyle, działa bez konieczności wykonywania żadnych dodatkowych akcji, przynajmniej na GlassFish’u. Hmmm... nawet bez konfiguracji połączenia do bazy danych? A skąd biorą się dane, które widzimy w aplikacji? Otóż owszem, z bazy danych, ale powoli, zacznijmy od podpięcia kodu do jakiegoś miłego IDE. Z NetBeans’em 5.5 poszło gładko, w końcu udało się też wygrać z Eclipse’m ale nie obyło się tu bez żonglowania katalogami, więc pozostanę przy tym pierwszym. No więc odpalamy NetBeans > New Project > Web Application with Existing Sources > Next. Pojawia się kolejny ekran. Jako lokalizację źródeł projektu wybieramy katalog JPetStore-5.0, reszta pól wypełnia się automatycznie, wygląda to jakoś tak:


Widzimy komunikat o błędzie, który mówi, że w katalogu JPetStore-5.0 znajduje się podkatalog build co nie jest właściwe. Jako że to, co tam jest nie będzie nam już potrzebne podkatalog ten zwyczajnie kasujemy. Aby NetBeans zauważył zmianę klikamy Back, po czym Next i jeszcze raz Next. Upewnijmy się, że wszystko jest OK, tzn. tak jak na poniższej ilustracji. W szczególności, że jako katalog bibliotek NetBeans wykrył katalog lib a nie devlib.


Klikamy Finish i gotowe. Aby upewnić się, że wszystko jest w porządku uruchamiany projekt z poziomu IDE, tj. klikamy prawym guzikiem myszy na nazwie projektu i z menu kontekstowego wybieramy opcję Run Project. No dobrze, ale jak to jest z tą bazą danych? Zerknijmy na listę pakietów w gałęzi Source Packages. Pakiety zaczynające się od przedrostka ddl (ang. data definition language) to skrypty tworzące strukturę bazy danych i wypełniające tą bazę inicjalną porcją informacji. Jako że różne bazy danych posługują się różnymi dialektami SQL mamy różne wersje skryptów. Otwórzmy teraz plik database.properties z pakietu properties. Już widzimy, że aplikacja używa bazy danych HSQLDB, http://hsqldb.org/, i to w trybie Memory-Only Database, czyli tabele przechowywane są wyłącznie w pamięci operacyjnej. Oznacza to, że żadne dane nie są de-facto utrwalone, i że nie ma sensu poszukiwać gdzieś między plikami aplikacji pliku bazy danych. Zatem skrypty tworzące strukturę bazy danych i wypełniające ją danymi o zwierzątkach muszą być uruchamiane każdorazowo przy starcie aplikacji. I rzeczywiście tak jest. Aplikacja, oprócz iBATIS Data Mapper’a używa jeszcze paru innych bibliotek, w szczególności szkieletu (ang. framework) Apache iBATIS DAO, który do niedawna, tj. aż do wersji 2.2.0 był częścią Data Mapper’a, a przynajmniej był razem dystrybuowany. Obecnie są to osobne, zupełnie niezależne produkty z wyraźnym wskazaniem, że iBATIS DAO jest już nie fajny i że w jego miejsce należy stosować Spring’a. Nasza aplikacja do najnowszych nie należy i używa biblioteki iBATIS DAO, ale obejdzie się bez jej szczegółowej znajomości. Zerknijmy do pakietu com.ibatis.jpetstore.persistence. W klasie DaoConfig.java znajdziemy kod który uruchamia skrypty z pakietu ddl.hsql jeśli tylko zmienna url z wspomnianego wcześniej pliku database.properties ma wartość jdbc:hsqldb:mem:jpetstore. Plik dao.xml to konfiguracja dla biblioteki iBATIS DAO, jego zawartość jest w zasadzie samo opisująca się.

Przeszkadzajki mamy już za sobą, możemy przystąpić do przeglądania źródeł, a warto, bo można się z nich wiele nauczyć, także z tematów wykraczających poza technologie trwałości danych i Data Mapper’a. Zwracam jeszcze uwagę tych, którzy postanowią poeksperymentować i uruchomić aplikację na innej bazie danych, że może to wymagać drobnych zmian w SQL’ach. Ja spróbowałem na MySQL Server 5.0 który okazał się być bardziej rygorystyczny niż HSQLDB. Próba przeglądania szczegółów zwierzątka kończyła się bardzo wylewnym wyjątkiem, którego mały fragment wygląda następująco:

Caused by: com.ibatis.dao.client.DaoException: Failed to execute queryForObject - id [getItem], parameterObject [EST-20]. Cause: com.ibatis.common.jdbc.exception.NestedSQLException:
--- The error occurred in com/ibatis/jpetstore/persistence/sqlmapdao/sql/Item.xml.
--- The error occurred while applying a parameter map.
--- Check the getItem-InlineParameterMap.
--- Check the statement (query failed).
--- Cause: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Column 'ITEMID' in field list is ambiguous

I jest to bardzo miłe ze strony iBATIS’a, że mówi nam jasno, co jest źle. Niestety nie wszystkie biblioteki są tak łaskawe. Zgłosiłem powyższy błąd jako IBATIS-411.

26 marca 2007

Rozważania nad Diagramem Stanów notacji UML

UML dawno przeszedł już z fazy bycia nowinką i obiektem wymuszonego zachwytu a wkroczył w fazę pragmatyczną. Zrealizował cykl tak ostatnio charakterystyczny dla wielu zdobyczy IT. Początkowo był nacisk by używać go zawsze i wszędzie, bo jest najlepszy i niezastąpiony, a wraz z szumem informacyjnym, propagandą i presją szła w parze niewiedza, brak zrozumienia i doświadczenia. Gdzieś w tym szumie utonęła częściowo rzeczywista wartość UML’a. Akurat przyszło mi ostatnio popracować z nim trochę i w rezultacie odkryłem dla siebie na nowo część z jego dobroci. Chciałem się z wami podzielić pewnym sporządzonym w ramach moich prac diagramem, bardzo dobrze ilustrującym ekspresyjność tkwiącą w Diagramach Stanów (and. State Diagram). Załączony diagram ilustruje stany zamówienia w pewnym systemie obsługi sklepu internetowego. Rysunek jest spory, więc aby coś zobaczyć trzeba go będzie zapewne pobrać, oto i on:

25 marca 2007

Wstęp do technologii Apache iBATIS Data Mapper

A więc zaczynamy. Mój pierwszy blog, mój pierwszy artykuł. Będzie o technologii trwałości danych Apache iBATIS Data Mapper, http://ibatis.apache.org, znanej także pod bardziej trafną nazwą iBATIS SQL Maps. A ściśle rzecz biorąc o jej edycji dla Javy, bo jest też wersja dla .NET i Ruby'ego. No więc co to jest? Mówiąc krótko prosty i miły w użyciu szkielet (ang. framework) dla warstwy trwałości danych (ang. persistence). Nakładka na JDBC, która pozwala zachować zalety starego dobrego SQL'a zwalniając nas jednocześnie z konieczności mozolnego dłubania. W dokumentacji dla programisty (ang. Developer Guide) autorzy piszą że dostarcza 80% możliwości JDBC przy redukcji nakładów na programowanie do 20%. Spotkałem się z określeniem, że jest to lekkie rozwiązanie do mapowania obiektowo-relacyjnego (ang. light object-relational mapping) ale nie jest to szczególnie trafne. Używając iBATIS’a mapujemy bowiem nie na relacje, tabele relacyjnej bazy danych, ale na polecenia SQL. Najłatwiej będzie zrozumieć patrząc na przykład. Sercem aplikacji korzystającej z iBATIS’a jest plik konfiguracyjny XML postaci:

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE sqlMapConfig
PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN”
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

<sqlMapConfig> (...) </sqlMapConfig>

przy czym element <sqlMapConfig> zawiera kolejne pod elementy, będące właściwą konfiguracją. iBATIS jest biblioteką która działa ponad JDBC, najistotniejszym elementem pliku konfiguracyjnego jest więc konfiguracja sterownika JDBC, w tym celu dodajemy pod element:

<transactionManager type="JDBC" >
<dataSource type="SIMPLE">
<property name="JDBC.Driver" value="${driver}"/>
<property name="JDBC.ConnectionURL" value="${url}"/>
<property name="JDBC.Username" value="${uname}"/>
<property name="JDBC.Password" value="${password}"/>
</dataSource>
</transactionManager>

przy czym wartości atrybutów @value odnoszą się do zmiennych wczytanych z pliku konfiguracyjnego .properties. Naturalnie możemy również te wartości podać explicite, wprost. Aby wczytać wspomniany plik .properties należy do elementu głównego dodać pod element:

<properties resource="ibatis/JDBCConfig.properties" />

gdzie plik JDBCConfig.properties to zwykły plik .properties który mógłby wyglądać tak jak ten:

driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:testdb
uname=scott
password=tiger

no i wreszcie pod element odpowiadający za mapowanie naszych obiektów języka Java na polecenia SQL, a ściślej rzecz biorąc, pod element powodujący wczytanie kolejnego pliku XML który to mapowanie definiuje:

<sqlMap resource="examples/domain/Person.xml" />

Plik konfiguracyjny zawiera jeszcze ustawienia ogólne, czyli pod element <settings>. Ogólne czyli jakie? Przykład poniżej:

<settings
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
maxRequests="32"
maxSessions="10"
maxTransactions="5" />

Można by jeszcze sporo napisać o głównym pliku konfiguracyjnym, ale ja celowo nie wdaje się w szczegóły, póki co wystarczy, że poczujemy o co w tym chodzi. Przejdźmy zatem do pliku mapowania, czyli jak mógłby wyglądać wspomniany wyżej plik Person.xml. Jakoś tak:

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN”
"http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="Person"> (...) </sqlMap>

gdzie element <sqlMap> zawiera pod elementy definiujące właściwe mapowania. Są to, jak już pisałem, mapowania na polecenia SQL. Mamy cztery rodzaje poleceń SQL (ang. CRUD) a więc i cztery rodzaje pod elementów XML, np.:

<select id="getPerson" resultClass="examples.domain.Person">
SELECT
ID as id,
FIRST_NAME as firstName,
LAST_NAME as lastName
FROM PERSON
WHERE ID = #value#
</select>

<insert id="insertPerson" parameterClass="examples.domain.Person">
INSERT INTO PERSON (ID, FIRST_NAME, LAST_NAME)
VALUES (#id#, #firstName#, #lastName#)
</insert>

<update id="updatePerson" parameterClass="examples.domain.Person">
(...)
</update>

<delete id="deletePerson" parameterClass="examples.domain.Person">
(...)
</delete>

w ten oto sposób zdefiniowaliśmy cztery, nazwane tak jak wskazuje atrybut @id, polecenia SQL, wszystkie odnoszące się do klasy examples.domain.Person. Klasa ta to zwykłe POJO, JavaBean. To by było na tyle jeśli chodzi o konfiguracje, możemy przejść do kodowania. Zaczynamy od zbudowania obiektu klienta iBATIS’a na podstawie skonstruowanego uprzednio pliku konfiguracyjnego:

Reader reader = Resources.getResourceAsReader(“ibatis/config.xml”);
SqlMapClient sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);

i to by było na tyle, możemy uruchamiać nasze zapytania zdefiniowane w plikach mapowań, w naszym przypadku w pliku examples/domain/Person.xml. Wyglądało by to jakoś tak:

Person newPerson = new Person(1, „Mariusz”, „Lipinski” );
sqlMap.insert(„insertPerson”, newPerson);

Integer personPk = new Integer(1);
Person person = (Person)sqlMap.queryForObject(“getPerson”, personPk);

person.setLastName(„Lipiński”);
sqlMap.update(“updatePerson”, person);

sqlMap.delete(“deletePerson”, person);

Cisną się na usta pytania? Są niejasności? I bardzo dobrze. Jeśli pobudziłem ciekawość to jest to właśnie to, o co chodziło. Niecierpliwych odsyłam do dokumentacji projektu. Jest tego trochę i to całkiem niezłej jakości. Tych zafrapowanych nie dość zapraszam do lektury moich kolejnych artykułów na ten temat, które mam nadzieje powstaną w przyszłości. Dla tych, którzy nie słyszeli poprzednio o iBATIS’ie i mieli zamiar potraktować go jako ciekawą osobliwość, ale li tylko osobliwość napiszę jeszcze tylko, że twórcy Springa potraktowali go całkiem poważnie. Myślę, że mieli powody. Kropka. Czekam na komentarze, no i na te pytania!