W poprzednim wpisie przedstawiłem podstawową funkcjonalność db4o w kontekście integracji bazy z frameworkiem webowym Tapestry. Ze statystyk dla tamtego wpisu wynika, że kilka osób zainteresowanych było czymś konkretniejszym, mianowicie wyszukiwaniem. Niniejszy wpis stanowi zatem przegląd poszczególnych sposobów wyszukiwania obiektów w db4o.
Punktem początkowym dla wyszukiwania jest zawsze obiekt typu ObjectContainer. W nim dostępne są różne metody query odpowiadające sposobom wyszukiwania.
Dla przypomnienia podaję dwa schematy, jak uzyskać ObjectContainer:
W trybie “stand-alone”:
ObjectContainer oc = Db4o.openFile("database.db4o");
W trybie klient-serwer:
// server int portNumber = 12345; String user = "db4oUser"; String password = "pw"; String dbFilename = "database.db4o"; ObjectServer os = Db4o.openServer(dbFilename, portNumber); os.grantAccess(user, password); // client String host = "localhost"; int portNumber = 12345; String user = "db4oUser"; String password = "pw"; ObjectContainer oc = Db4o.openClient(host, portNumber, user, password);
Wyszukiwanie całych klas obiektów
Przykład:
class A {} // ... List<A> all = oc.query(A.class);
Działa to również dla całych hierarchii klas:
abstract class A {} class B extends A {} class C extends A {} // ... List<A> list = oc.query(A.class); for (A a : list) { System.out.println(a.getClass().getSimpleName()); // B C B B C ... }
Wyszukiwanie przez przykład
W wyszukiwaniu przez przykład (ang. query by example, QBE) jako parametr dla metody query podajemy obiekt klasy, którą chcemy przeszukiwać. W obiekcie tym ustawiamy te właściwości, które mają być kryteriami naszego wyszukiwania a reszcie pozostawiamy wartości domyślne. db4o znajdzie wszystkie obiekty, które będą mieć takie same wartości atrybutów jak przekazany obiekt:
class Person { private String firstname, lastname; Person(String firstname, String lastname) { this.firstname = firstname; this.lastname = lastname; } @Override public String toString() { return firstname + " " + lastname; } } // ... oc.store(new Person("Jan", "Kowalski")); oc.store(new Person("Zbigniew", "Nowak")); oc.store(new Person("Adam", "Kowalski")); oc.commit(); // ... List<Person> people = oc.queryByExample(new Person(null, "Kowalski")); for (Person p : people) { System.out.println(p); // Jan Kowalski, Adam Kowalski }
Tryb ten posiada pewne ograniczenia:
- Do wszystkich atrybutów przykładowego obiektu db4o uzyskuje dostęp poprzez refleksję.
- Nie można w ten sposób tworzyć bardziej złożonych zapytań (and, or, itd.)
- Nie można szukać obiektów o atrybutach równych 0, null i false, ponieważ db4o potraktuje to jako brak kryterium dla danego atrybutu.
- Musi istnieć możliwość tworzenia obiektów z niezainicjowanymi atrybutami, żeby później móc ustawić te, które mają być naszym kryterium wyszukiwania.
Zapytania natywne
Zapytania natywne są głównym i preferowanym sposobem wyszukiwania w db4o. Zapytania te są zakodowane w sposób pozwalający na sprawdzanie typów w trakcie kompilacji i łatwą refaktoryzację.
Ideą stojącą za zapytaniami natywnymi jest ewaluacja wyrażenia dla każdego obiektu i zwróceniu wartości true dla tych, które spełniają nasze kryteria:
class Person { private String firstname, lastname; Person(String firstname, String lastname) { this.firstname = firstname; this.lastname = lastname; } @Override public String toString() { return firstname + " " + lastname; } public String getFirstname() { return firstname; } public String getLastname() { return lastname; } } // ... oc.store(new Person("Adam", "Nowak")); oc.store(new Person("Adam", "Kowalski")); oc.store(new Person("Adam", "Kowalczyk")); oc.commit(); // ... List<Person> people = oc.query(new Predicate<Person>() { @Override public boolean match(Person candidate) { return candidate.getLastname().startsWith("K"); } }); for (Person p : people) { System.out.println(p); // Adam Kowalski, Adam Kowalczyk }
Jak widzimy w powyższym przykładzie implementujemy anonimową klasę, która rozszerza Predicate. Jest ona parametryzowana typem, którego obiekty chcemy wyszukać. Metoda match w tym przypadku zwraca true dla obiektów Person, których atrybut lastname rozpoczyna się od litery K.
db4o spróbuje zoptymalizować wyrażenie zawarte w metodzie match i wywoła je na wewnętrznych indeksach, żeby utworzyć jak najmniej instancji obiektów. Nie zawsze jednak się to udaje. Prace nad rozwojem optymalizatora trwają i ulepszenia pojawiają się wraz z każdym nowym wydaniem bazy.
Zapytania SODA
SODA jest niskopoziomowym API to tworzenia zapytań w db4o. Za jego pomocą możemy dowolnie modelować zapytanie:
// class Person {...} jak z poprzedniego przykładu Query q = oc.query(); q.constrain(Person.class); // ograniczamy zapytanie od obiektów klasy Person q.descend("lastname"); // przechodzimy do atrybutu "lastname" q.constrain("K").startsWith(true); // i ograniczamy go do wartości rozpoczynających się od "K"
Metoda query tworzy zapytanie, które zwraca wszystkie obiekty w bazie. Kolejne metody dodają kolejne ograniczenia. Już w tym przykładzie widać, że to API nie zapewnia bezpieczeństwa typów; w trakcie kompilacji nie można sprawdzić, czy atrybut lastname w ogóle istnieje oraz czy wartość K jest poprawnym ograniczeniem dla niego. Sposób ten ma jednak dużą zaletę; dzięki niemu można generować zapytania dynamicznie.
Podsumowanie
Każdy sposób wyszukiwania ma swoje wady i zalety oraz zastosowania, w których sprawdza się najlepiej. Może ten krótki przegląd okaże się pomocny przy wyborze właściwej metody oraz tworzeniu zapytań. Po więcej informacji zapraszam do dokumentacji oraz czekam na wszelkie pytania i komentarze.
Comments
Leave a comment Trackback