25 kwietnia 2007

Co to jest JDO i co nowego w JDO 2.0

JDO, czyli Java Data Objects to standard rozwijany w ramach JCP (akr. Java Community Process). Pierwotna wersja specyfikacji, JDO 1.0, powstała w wyniku prac nad JSR 12 (akr. Java Specification Request) i oparta była o standard ODMG 3.0 (akr. Object Data Management Group). Jej finalna wersja została zatwierdzona i opublikowana w kwietniu 2002. Aktualną wersję JDO, JDO 2.0, opracowano w ramach JSR 243 i zatwierdzono ostatecznie, po długotrwałych trudnościach natury politycznej, w maju 2006.

JDO jest standardem interfejsu programistycznego (ang. API) dla technologii trwałości danych. Główne idee, jakie stara się realizować to pełna niezależność od rodzaju źródła danych oraz przeźroczystość. Niezależność od rodzaju źródła danych oznacza, że aplikacja używająca JDO może być bez żadnych zmian w kodzie uruchamiana na relacyjnej, obiektowej oraz każdej innej bazie danych, a także na źródłach danych, których zwyczajowo nie nazywa się bazami danych. Przeźroczystość przejawia się w kilku aspektach, najważniejsze z nich to:
  • Trwałość przez osiągalność (ang. persistence by reachability) – utrwalając obiekt, który ma referencje do innych obiektów obiekty osiągalne z obiektu utrwalanego explicite zostaną także utrwalone. Jeśli np. nasza klasa Osoba ma referencję nazwaną adresZamieszkania do obiektu typu Adres to utrwalając obiekt klasy Osoba, powiązany z nim obiekt klasy Adres zostanie także automatycznie utrwalony.
  • Przeźroczysta nawigacja – pobierając pewien obiekt ze źródła danych nie pobieramy wszystkich obiektów, które są osiągalne z danego obiektu poprzez referencje. Wyobraźmy sobie obiekt klasy Sprzedawca, który ma referencje do wszystkich obsługiwanych przez siebie obiektów Klientów, którzy z kolei mają referencje do wszystkich swoich obiektów Zamówień, te mają dalsze referencje do obiektów klasy Towar. Byłoby to tysiące lub dowolnie dużo więcej obiektów do pobrania, podczas gdy my chcemy znać tylko imię i nazwisko danego sprzedawcy. Przeźroczysta nawigacja oznacza, że efektywnie pobierany jest tylko obiekt Sprzedawca, zaś dopiero odwołanie się do jego referencji powoduje rzeczywiste, ale zupełnie nie widoczne dla programisty, pobranie obiektu referowanego. Programujemy więc tak jakby wszystkie obiekty były pobrane od razu, ale rzeczywisty odczyt ze źródła danych następuje dopiero wtedy gdy jest to konieczne. Funkcjonalność ta dostępna jest jednak jedynie wtedy, gdy kod wykonuje się w kontekście zarządzanym przez managera JDO.
JDO jest jednak tylko standardem definiującym interfejs programistyczny. Aby mieć z niego jakikolwiek rzeczywisty pożytek niezbędna jest jeszcze implementacja. JDO realizuje ideę niezależności od rodzaju źródła danych tylko w tym sensie, że nie narzuca mechanizmów charakterystycznych dla np. relacyjnych baz danych, rzeczywista niezależność zależy od faktycznego istnienia odpowiedniej implementacji. Referencyjną implementacją dla JDO jest JPOX, http://www.jpox.org/. JPOX 1.0 implementuje JDO 1.0 dla relacyjnych baz danych, JPOX 1.1 to już JDO 2.0 ale również tylko dla relacyjnych baz danych. JDO 2.0 jest w pełni wstecz kompatybilne tak więc implementacja JDO 2.0 jest również implementacją JDO 1.0. JPOX 1.2 dodaje wsparcie dla obiektowej bazy danych DB4O, choć w chwili pisania tego artykułu nie ma jeszcze wersji stabilnej, najnowszą jest JPOX 1.2.0-beta-2. Spis dostępnych implementacji, choć nie wiem czy kompletny, można znaleźć na stronie http://db.apache.org/jdo/impls.html.

Jeśli mamy już w ręce pewien obiekt pobrany ze źródła danych, to kolejne obiekty pobieramy w sposób przeźroczysty, nie widoczny dla programisty, poprzez odwołanie się do referencji tego obiektu w zwykły, charakterystyczny dla Javy a nie dla JDO sposób. Jednak musimy jakoś pobrać ten pierwszy obiekt. Nie owijając w bawełnę. Praca z danymi wymaga istnienia dobrego mechanizmu wyszukiwania. JDO definiuje swój własny język zapytań, JDOQL. Zanim jednak popadniemy w zniechęcenie... JDOQL jest prosty, intuicyjny i... podobny do Javy. Tak! Do Javy, nie do SQL! Począwszy od JDO 2.0 JDOQL został rozszerzony w ten sposób, że można go używać także w bardziej SQL’owy sposób, ale nie trzeba, a udostępniono tą możliwość dlatego, że niektórzy tak wolą, oraz pewnie po to, żeby upodobnić JDO do JPA, ale to inna historia. Stworzenie prostego zapytania mogłoby wyglądać tak:

Query query = pManager.newQuery(Osoba.class, „wiek >= 50 && wiek < 60");

Jak duża jest różnica między JDO 1.0 a JDO 2.0? Ogromna! A właściwie to nie jest to różnica tylko różnice. Niektóre, istotne rozszerzenia, jakie wprowadza JDO 2.0 to:
  • Standaryzacja mapowania obiektowo-relacyjnego – mimo że JDO nie jest w żaden sposób związane z relacyjnymi bazami danych, to w obliczu przytłaczającej dominacji RDBMS’ów jako źródeł danych zdecydowano się na standaryzację mapowania obiektowo-relacyjnego. Mapowanie to definiujemy w pliku XML, no i oczywiście nie musimy tego robić jeśli nie będziemy uruchamiać aplikacji na relacyjnej bazie danych.
  • Rozszerzenie mechanizmu uruchamiania zapytań i języka zapytań JDOQL – liczne usprawnienia, w szczególności, umożliwienie wykonywania zapytań agregujących, np. zliczających liczbę obiektów lub wyliczających sumy, minima czy maksima. Dodanie mechanizmu projekcji, tak by możliwe było pobieranie tylko wybranych atrybutów obiektów, w tym możliwość projekcji do obiektów dowolnego typu. Dodanie mechanizmu stronicowania wyników zapytań.
  • Mechanizm odłączania i przyłączania do kontekstu zarządzanego JDO – wydaje się to być niezbędne dla sprawnej implementacji nowoczesnych aplikacji wielowarstwowych i trudno mi jest wyobrazić sobie, że mogło tego pierwotnie nie być. Obiekt pozyskany ze źródła danych można teraz wyłączyć z kontekstu zarządzania JDO, zmodyfikować w innej warstwie i ponownie przyłączyć, co spowoduje zapisanie jego zmodyfikowanego stanu do bazy danych.
  • Mechanizm definiowania zachłannego pobierania obiektów – mechanizm, który pozwala na zdefiniowanie, które obiekty powiązane referencjami z pobieranym obiektem zostaną pobrane w sposób zachłanny (ang. eager). Np. jeśli obiekt klasy Osoba posiada referencję do obiektu klasy Adres to możemy określić aby pobierając obiekt klasy Osoba pobrać od razu skojarzony z nim adres.
  • Interfejsy zarządzania pamięcią podręczną drugiego poziomu – ze względów wydajnościowych wiele implementacji JDO 1.0 dostarczało pamięć podręczną (ang. cache) drugiego poziomu, tj. współdzieloną przez wszystkie obiekty managerów JDO. JDO 2.0 standaryzuje interfejs programistyczny do zarządzania tą pamięcią podręczną, choć specyfikacja dopuszcza brak rzeczywistej implementacji, tj. implementację w postaci zaślepki.

16 kwietnia 2007

Implementacja metody toString() z użyciem Reflection API

Zapewne każdy przedefiniowywał kiedyś metodę toString() klas dziedziny aplikacji, aby ułatwić sobie testowanie czy wykrywanie błędów. Jest to zajęcie o tyleż proste, co jeśli wykonywane w najprostszy z możliwych sposobów a więc dla każdej z klas z osobna, nudne i nużące. Tym bardziej, że nie ma uzasadnienia dla wykonywania tej pracy. Z pomocą przychodzi Reflection API. Dla tych, którzy posiadają największą z cnót programisty, lenistwo, załączam gotowy do użycia kod. Wymaga on Javy 5, ale łatwo przerobić go tak by działał także na starszych wersjach.

import java.lang.reflect.Field;

public class SelfDescribingObject {
public String toString() {
StringBuffer objDescBuffer = new StringBuffer(this.getClass().getSimpleName());

Class currentClass = this.getClass();
while (currentClass != null) {
Field fields[] = currentClass.getDeclaredFields();

try {
objDescBuffer.append(fieldsToString(fields));
} catch (Exception exc) {
return exc.toString();
}

currentClass = currentClass.getSuperclass();
}

return objDescBuffer.toString();
}

private StringBuffer fieldsToString(Field[] fields) throws IllegalAccessException {
StringBuffer objDescBuffer = new StringBuffer();

for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);

objDescBuffer.append(" ");
objDescBuffer.append(fields[i].getName());
objDescBuffer.append(": ");
objDescBuffer.append(fields[i].get(this));
}

return objDescBuffer;
}
}

9 kwietnia 2007

Niebanalny przykład mapowania dla Apache iBATIS Data Mapper

Nie trudno zauważyć przeglądając króciutką historię mojego bloga, nad czym ostatnio spędzam wieczory. Tematem przewodnim jest iBATIS Data Mapper, wszystkie wątki skupione są wokół technologii trwałości danych. Napisałem już dwa artykuły wprowadzające w tematykę Data Mapper’a: Wstęp do technologii Apache iBATIS Data Mapper oraz Uruchamiamy aplikację JPetStore dla Apache iBATIS Data Mapper, przyszła kolej na coś nieco bardziej zaawansowanego. Będzie przykład mapowania obiektu, który zawiera referencję do innego obiektu, który z kolei zawiera referencję do kolekcji obiektów. Całą strukturę wczytamy pojedynczym zapytaniem SQL. Żeby pokazać, że iBATIS potrafi.


Zacznę od przedstawienia klas języka Java i tabel bazy danych, w końcu to są aktorzy pierwszej kategorii. Oto klasy, których będziemy używali:

public class User {
private long id;
private RolesSet rolesSet;

// potrzebujemy także kompletu get’terów i set’terów
}

public class RolesSet {
private Set<String> roles = new HashSet<String>();

// potrzebujemy także get’tera i set’tera
}

public class Credentials {
private String login;
private String passwd;

// potrzebujemy także kompletu get’terów i set’terów
}

Naturalnie klasy te mogą, a nawet powinny zawierać dowolne inne metody i właściwości, ale pominąłem je dla zwięzłości wywodu. Tabele bazy danych przedstawię w formie używanej przez narzędzie DdlUtils, http://db.apache.org/ddlutils/, bardzo fajne, aczkolwiek słabo jeszcze rozwinięte:

<table name="credentials">
<column name="isValid" required="true" type="BIT" />
<column name="login" required="true" type="VARCHAR" size="64" />
<column name="passwd" required="true" type="VARCHAR" size="64" />
<column name="userId" required="true" type="INTEGER" />
</table>

<table name="role">
<column name="id" primaryKey="true" required="true" type="INTEGER" />
<column name="name" required="true" type="VARCHAR" size="32" />
</table>

<table name="userRoles">
<column name="isValid" required="true" type="BIT" />
<column name="userId" required="true" type="INTEGER" />
<column name="roleId" required="true" type="INTEGER" />
</table>

Prawda, że powyższe prezentuje się dużo ciekawiej niż tradycyjny skrypt? Tabele znacznie odchudziłem na potrzeby przykładu, nie koniecznie wyglądają więc teraz sensownie, ale spełnią swoją rolę. To, czego chcemy to skonstruować w pełni zainicjalizowany obiekt klasy User na podstawie obiektu klasy Credentials. Chcemy więc uruchomić coś w stylu:

User user = (User) sqlMapClient.queryForObject("getUser", credentials);

i w wyniku otrzymać obiekt user, taki że zbiór user.getRolesSet().getSet() jest w pełni zainicjalizowany. Wpierw wariant podstawowy, tj. taki, w którym w wyniku pierwszego zapytania otrzymujemy userId z tabeli credentials o ile oczywiście login i hasło są prawidłowe a następnie używamy drugiego zapytania, aby dla danego userId otrzymać zbiór nazw ról z połączonych tabel userRoles i role. Oto jak mógłby wyglądać nasz plik mapowania:

<resultMap class="pckg.User" id="getUserMap">
<result property="id" column="userId"/>
<result property="rolesSet.roles" column="userId" select="getRoles"/>
</resultMap>

<resultMap class="string" id="getRolesMap">
<result property="value" column="name"/>
</resultMap>

<select id="getRoles" parameterClass="long" resultMap="getRolesMap">
SELECT r.name
FROM userRoles u, role r
WHERE u.userId = #value# AND u.isValid = 1 AND r.id = u.roleId
</select>

<select id="getUser" parameterClass="pckg.Credentials" resultMap="getUserMap">
SELECT userId
FROM credentials
WHERE login = #login# AND passwd = #passwd# AND isValid = 1
</select>

Jak to działa? Uruchamia się zapytanie nazwane getUser. Do interpretacji wyników używana jest mapa nazwana getUserMap. Zgodnie z tą mapą tworzony jest obiekt klasy pckg.User i wywoływana jest jego metoda setId z wartością pochodzącą z kolumny userId wyniku zapytania. Następnie tworzony jest i analogicznie przypisywany obiekt odpowiadający typowi zmiennej rolesSet, a więc klasy RolesSet. Uwaga! Data Mapper instancjonuje nasze klasy, muszą mieć one zatem bezargumentowy, publiczny konstruktor. Teraz trzeba by coś przypisać do zmiennej rolesSet.roles. To, co zostanie do niej przypisane determinuje zapytanie getRoles uruchamiane z parametrem wejściowym pochodzącym z kolumny userId wyniku poprzedniego zapytania. Do interpretacji wyniku zapytania getRoles służy mapa getRolesMap. Wynikiem są więc obiekty typu java.lang.String, string to predefiniowany alias. Zmiennej rolesSet.roles zostanie przypisany zbiór zawierający te właśnie obiekty. I to koniec, ale wcale nie musimy zatrzymywać się w tym miejscu. Zamiast obiektów typu napisowego moglibyśmy tam mieć dowolne inne i ciągnąć analogiczny łańcuch wywołań dalej. Uwaga! Czegoś takiego nie da się zrobić używając języka zapytań dla Java Persistence API, JPQL’a! Tam konstrukcji JOIN FETCH można użyć tylko raz! Ale to jest inny, całkiem obszerny temat, może na następny artykuł. Tak więc, skonstruowanie naszego obiektu klasy User wymaga dwu zapytań, ale wyobraźmy sobie, że nie chodzi o logowanie do systemu i że pierwsze zapytanie zwraca wielu użytkowników. W ogólności, nie mamy dwu zapytań tylko N + 1, gdzie N to liczba wyników zwracanych przez pierwsze zapytanie. Byłoby słabo, gdyby nie to, że da się to zawsze zrobić pojedynczym zapytaniem, o czym napisałem już we wstępie tego artykułu. Zmieńmy nasz plik mapowania na poniższy:

<resultMap class="pckg.User" id="getUserMap" groupBy="id">
<result property="id" column="userId"/>
<result property="rolesSet.roles" resultMap="namespace.rolesMap"/>
</resultMap>

<resultMap class="string" id="rolesMap">
<result property="value" column="name"/>
</resultMap>

<select id="getUser" parameterClass="pckg.Credentials" resultMap="getUserMap">
SELECT c.userId, r.name
FROM credentials c
LEFT JOIN userRoles u ON c.userId = u.userId AND u.isValid = 1
LEFT JOIN role r ON u.roleId = r.id
WHERE c.login = #login# AND c.passwd = #passwd# AND c.isValid = 1
</select>

Mamy teraz jedno zapytanie, które łączy wszystkie trzy tabele. Wynik tego zapytania to odpowiedni podzbiór iloczynu kartezjańskiego tych tabel. Zauważmy, że będzie tam tyle wierszy ile ról ma przypisanych nasz obiekt User (powracamy do założenia, że jeden login i hasło odpowiada jednemu użytkownikowi). A teraz zwróćmy uwagę na artybut @groupBy mapy getUserMap. Spowoduje on, że w rezultacie otrzymamy tylko tyle obiektów klasy User ile różnych wartości userId zwróci zapytanie getUser (kolumna userId jest mapowana na właściwość id), a więc jeden obiekt. Przyporządkowanie zmiennej rolesSet.roles mapy rolesMap spowoduje odpowiednie zainicjalizowanie zbioru ról. I tu znowu porównam do Java Persistence API. Analogiczna konstrukcja w JPQL, z wykorzystaniem mechanizmu JOIN FETCH ma tą zaskakującą cechę, że w wyniku, zamiast jednego obiektu User otrzymujemy ich tyle ile wierszy zwróciło wygenerowane zapytanie SQL, podobne do powyższego. Ale jak już wspominałem, JOIN FETCH zasługuje na swój osobny artykuł.

3 kwietnia 2007

Integracja DdlUtils i Eclipse przy pomocy Ant'a

To ciekawe, na co można się natknąć eksplorując projekty Apache. Tym razem mowa o DdlUtils, http://db.apache.org/ddlutils/, następcy commons-sql, rzeczy tyleż prostej, co wspaniałej. Jestem zachwycony. Trochę tylko szkoda, że projekt nie doczekał się jeszcze porządnej dokumentacji ani nawet wydania (ang. release) w formie binarnej, gotowej do użycia. Ale gra jest warta świeczki, pobieramy źródła z repozytorium i budujemy, najwygodniej przy użyciu Eclipse’a. Artykuł nie dotyczy samego DdlUtils, więc nie będę się na ten temat rozpisywał, niezbędne informacje można znaleźć na stronie projektu. W rezultacie budowania powinniśmy otrzymać plik dystrybucji, ddl-utils-1.0-rc1.jar, ale może on się nazywać inaczej, w zależności od tego jakiej metody budowania użyliśmy. Będą nam także potrzebne biblioteki dystrybuowane z kodem źródłowym projektu.

Zaczynamy od utworzenia nowego projektu, Eclipse > New > Project, ja swój nazwałem mgr.db. Tworzymy trzy podkatalogi: lib, input i output, za chwile będzie wiadomo, w jakim celu. W katalogu głównym projektu tworzymy skrypt Ant, nazwijmy go build.xml, oraz plik build.properties. Do pliku build.xml wklejamy poniższy tekst:

<?xml version="1.0"?>

<project name="mgr.db" default="fetch-schema" basedir=".">

<property name="output.dir" value="${basedir}/output" />
<property name="input.dir" value="${basedir}/input" />

<property file="${basedir}/build.properties" />

<target name="fetch-schema">
<databaseToDDL>
<database driverclassname="${jdbc.driver}" url="${jdbc.url}"
username="${jdbc.username}" password="${jdbc.password}" />
<writeschematofile outputfile="${output.dir}/schema.xml" />
</databaseToDDL>
</target>

<target name="install-schema">
<databaseFromDDL>
<database driverclassname="${jdbc.driver}" url="${jdbc.url}"
username="${jdbc.username}" password="${jdbc.password}" />
<fileset dir="${input.dir}">
<include name="schema.xml" />
</fileset>
<writeschematodatabase alterdatabase="true" failonerror="true" />
</databaseFromDDL>
</target>

<target name="install-data">
<databaseFromDDL>
<database driverclassname="${jdbc.driver}" url="${jdbc.url}"
username="${jdbc.username}" password="${jdbc.password}" />
<fileset dir="${input.dir}">
<include name="schema.xml" />
</fileset>
<writedatatodatabase usebatchmode="true" batchsize="100">
<fileset dir="${input.dir}">
<include name="data.xml" />
</fileset>
</writedatatodatabase>
</databaseFromDDL>
</target>

<target name="generate-sql">
<databaseFromDDL>
<database driverclassname="${jdbc.driver}" url="${jdbc.url}"
username="${jdbc.username}" password="${jdbc.password}" />
<fileset dir="${input.dir}">
<include name="schema.xml" />
</fileset>
<writeschemasqltofile outputfile="${output.dir}/schema.ddl"
alterdatabase="false" failonerror="true" />
</databaseFromDDL>
</target>

</project>

Skrypt wyjaśnia powód istnienia katalogów input i output. Plik build.properties zawiera definicję zmiennych określających połączenie do bazy danych na której będziemy operować, w moim przypadku, dla bazy danych MySQL Server 5.0 wygląda on tak:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mgr
jdbc.username=root
jdbc.password=password

Jest jeszcze tylko mały problem. Ant nie rozumie zadań <databaseToDDL> i <databaseFromDDL>, trzeba go więc odpowiednio poinstruować. W tym celu wchodzimy do konfiguracji Ant’a, Eclipse > Window > Preferences > Ant > Runtime, zakładka Classpath, gałąź Global Entries, widzimy następujący ekran:




Klikamy Add External JARs i dodajemy nasz plik dystrybucji DdlUtils, ddl-utils-1.0-rc1.jar. Przechodzimy do zakładki Tasks i klikamy Add Task. Z listy dostępnych plików .jar wybieramy dodany przed chwilą ddl-utils-1.0-rc1.jar. W drzewie zasobów dostępnych w wybranym pliku .jar wybieramy klasę implementującą zadanie Ant które chcemy dodać, podajemy jego nazwę i klikami OK. Wybierzmy więc klasę org.apache.ddlutils.task.DatabaseToDdlTask i nazwę databaseToDDL a następnie klasę org.apache.ddlutils.task.DdlToDatabaseTask i nazwę databaseFromDDL. Operacje te ilustruje poniższy zrzut ekranu:


To już prawie koniec, wystarczy jeszcze tylko dodać wymagane przez DdlUtils biblioteki do ścieżki klas (ang. classpath) skryptu Ant. W tym celu uruchamiamy konfigurator narzędzi zewnętrznych Eclipse > Run > External Tools > External Tools. Po lewej stronie ekranu wybieramy nasz skrypt build.xml i przechodzimy do zakładki Classpath. Widzimy następujący ekran:


W zależności od tego gdzie znajdują się nasze biblioteki wybieramy Add JARs albo Add External JARs. Ja wymagane biblioteki skopiowałem do katalogu lib w projekcie mgr.db. Rekomenduje zrobić to samo teraz. W takim wypadku klikamy Add JARs i dodajemy wszystkie biblioteki z katalogu lib. Oprócz bibliotek z kodu dystrybucji DdlUtils potrzebny będzie jeszcze sterownik JDBC do wybranej bazy danych. Poniższa ilustracja pokazuje jak wygląda teraz nasz projekt, widać tu także kompletną listę wymaganych bibliotek wraz ze sterownikiem JDBC dla MySQL.