Wstęp
Przydatne klasy:
- SQLFinder - odpowiada za wykonywanie zapytań
- SQLBuilder - pozwala definiować zapytanie
- SQLQueryBuilder - udostępnia statyczne metody do generowania fragmentów zapytań SQL
- FilterOperator - typ wyliczeniowy z dostępnymi operatorami warunkowymi
- LogicOperator - typ wyliczeniowy z dostępnymi operatorami logicznymi
- SimpleSQLFilter - definiuje prosty filtr
- GroupSQLFilter - definiuje grupę filtrów
- Sorter - definiuje sortowanie
- SortDirection - typ wyliczeniowy z dostępnymi kierunkami sortowania
- CountedResult<?> - przechowuje dane, wraz z ogólną liczbą wyników.
SQLFinder finder = FinderFactory.getSQLFinder();//udostępnia metody wyszukujące
Podstawowe możliwości
//proste zapytanie SQLBuilder builder =new SQLBuilder(); builder.setQuery( "select * from testtable" ); builder.addScalar( "boolcol", StandardBasicTypes.BOOLEAN ); builder.addScalar( "textcol", StandardBasicTypes.STRING ); builder.addScalar( "datecol", StandardBasicTypes.DATE ); builder.addScalar( "intcol", StandardBasicTypes.INTEGER ); builder.addScalar( "doublecol", StandardBasicTypes.DOUBLE ); List<Map<String,Object>> data=finder.find( builder );//wykonanie zapytania
W powyższym przykładzie w wyniku otrzymamy wartości tylko kolumn, dla których zdefiniowaliśmy scalar'y nawet jeżeli tabela testtable posiada więcej kolumn. Rekord jest reprezentowany jako mapa, w której kluczem jest nazwa kolumn(lub alias jeżeli został zdefiniowany). Podanie scalar'ów jest opcjonalne, lecz zalecane.
Dlaczego należy używać scalar'ów
Scalar'y definiują do jakiego typu języka Java należy przekonwertować wartości z bazy danych. Jeżeli nie podamy scalar'ów system spróbuje sam wybrać odpowiedni typ. Jednak z uwagi na dużą różnorodność typów bazodanowych(w zależności od producenta SZBD) system nie zawsze jest w stanie dopasować typ bazodanowy do typu Java. W takim wypadku zostaje rzucony wyjątek. Nawet jeżeli system pomyślnie znajdzie odpowiedni typ to nie wiemy jaki konkretnie jest to typ. Np. jeżeli zdefiniujemy zapytanie z użyciem count( * ), lub z kolumną numeryczną i spróbujemy rzutować wartość na Long to możemy dostać wyjątek, ponieważ system dopasuje typ BigInteger, BigDecimal lub jeszcze inny w zależności od silnika bazy danych.
builder.setQuery( "select * from testtable where textcol=:textcol" ); //... definicja scalarów builder.setParameter( "textcol", "text3" );//ustawienie pojedynczego parametru Map<String,Object> parameters=new HashMap<String,Object>();//alternatywnie możemy podać mapę ze wszystkimi parametrami parameters.put("textcol","text3"); builder.setParameters( parameters );//usunie wcześniej podane parametry i wstawi nowe
Przy podawaniu parametrów ważne jest, aby ich typy zgadzały się z typami kolumn. Np. nie możemy podać "true" jako String, gdy kolumna jest typu boolean.
Definiowanie zwracanego typu
Dodawanie do każdego zapytania scalar'ów może być czasem uciążliwe, a odnoszenie się do wartości rekordów poprzez ich nazwę może powodować błędy wynikające np. z literówek. Dlatego innym podejściem jest utworzenie klasy, która będzie przechowywała zwracane z bazy danych informacje. Przejdźmy do przykładu:
builder.setQuery( "select textcol,boolcol as wlasnanazwa from testtable" ); List<TestDto> data = finder.find( builder, TestDto.class );//wykonanie zapytania
Aby, przedstawiony kod zadziałał musimy oczywiście utworzyć klasę TestDto(nazwa może być inna ). Pola klasy powinny odpowiadać aliasom kolumn. W tym przypadku textcol i wlasnanazwa. Wielkość znaków nie ma znaczenia, jednak konieczne jest trzymanie się standardu JavaBean(gettery i settery). Dzięki temu otrzymujemy listę obiektów z polami uzupełnionymi przez wartości z bazy danych. Nie musimy się również przejmować typami kolumn, ponieważ zostaną automatycznie rzutowane na typy odpowiednich pól.
Filtrowanie
API umożliwia również dodatkowe funkcje tj. budowanie klauzuli where, order by, oraz ograniczanie zbioru wynikowego(offset i limit). Zacznijmy od filtrowania. Klasa SQLBuilder pozwala zbudować filtry za pomocą klas:
- SimpleSQLFilter - prosty filtr, opisuje nazwę kolumny, operator porównania i zawiera wartość filtru.
- GroupSQLFilter - pozwala grupować proste filtry, lub inne grupy filtrów. Warunki w grupie filtrów mogą być łączone operatorem logicznym OR, lub AND.
builder.setQuery( "select textcol,boolcol from testtable " ); builder.addFilter(new SimpleSQLFilter( "textcol", "text3" )); builder.addFilter(new SimpleSQLFilter( "boolcol", true )); List<TestDto> data = finder.find( builder, TestDto.class );//wykonanie zapytania
W przykładzie zdefiniowaliśmy dwa proste warunki. Domyślnie operatorem porównania jest znak równości, a operatorem logicznym, który łączy warunki jest AND. Oczywiście możemy zmienić domyślne ustawienia.
builder.setQuery( "select textcol,boolcol from testtable " ); builder.addFilter(new SimpleSQLFilter( "textcol", Arrays.asList("text3","text4"),FilterOperator.IN )); builder.addFilter(new SimpleSQLFilter( "boolcol", true,FilterOperator.NOTEQ ));// różny od builder.addFilter(new SimpleSQLFilter( "doublecol", 5.0,FilterOperator.GT ));// większy od builder.addFilter(new SimpleSQLFilter( "intcol", new Integer[]{1,10},FilterOperator.BETWEEN ));//dla operator BETWEEN należy podać tablicę dwuelementową List<TestDto> data = finder.find( builder, TestDto.class );//wykonanie zapytania
Dostępne operatory są opisane w dokumentacji FilterOperator. Przejdźmy do grupowania filtrów:
builder.setQuery( "select textcol,boolcol from testtable " ); GroupSQLFilter g1 = new GroupSQLFilter(LogicOperator.OR );//główna grupa GroupSQLFilter g2 = new GroupSQLFilter(LogicOperator.AND);//podgrupa 1 GroupSQLFilter g3 = new GroupSQLFilter(LogicOperator.AND);//podgrupa 2 //grupa g1 łączy g2 i g3 operatorem OR g1.addFilter( g2 ); g1.addFilter( g3 ); //warunki pierwszej podgrupy połączone operatorem AND g2.addFilter( new SimpleSQLFilter( "intcol", 5, FilterOperator.GT ) ); g2.addFilter( new SimpleSQLFilter( "intcol", 7, FilterOperator.LT ) ); //warunki drugiej podgrupy połączone operatorem AND g3.addFilter( new SimpleSQLFilter( "textcol", Arrays.asList( "text3", "text4" ), FilterOperator.IN ) ); g3.addFilter( new SimpleSQLFilter( "boolcol", true, FilterOperator.NOTEQ ) ); builder.addFilter( g1 );//dodajemy stworzoną grupę filtów do zapytania List<TestDto> data = finder.find( builder, TestDto.class );// wykonanie zapytania
Powyższy kod wygeneruje następujące zapytanie:
select textcol,boolcol from testtable where ( ( intcol > :intcol_0 and intcol < :intcol_1 ) or ( textcol in (:textcol_2) and boolcol != :boolcol_3 ) )
Jak widzimy, możliwe jest zagnieżdżanie grup w innych grupach co pozwala na tworzenie drzewa warunków. Należy pamiętać, że gdy zdefiniujemy filtry, zapytanie, które podaliśmy poprzez setQuery ulegnie zmianie. Doklejona zostanie klauzula WHERE, wraz z warunkami. Dlatego nie możemy w setQuery podać zapytania z już istniejącą klauzulą WHERE. Jeżeli jednak pojawi się taka potrzeba możemy niezależnie zbudować fragment zapytania dotyczący warunków, w następujący sposób:
GroupSQLFilter g1 = new GroupSQLFilter( LogicOperator.OR ); //...definicja filtrów(patrz 'Grupowanie filtrów') g2.addFilter( new SimpleSQLFilter( "intcol", 5, FilterOperator.GT ) ); g2.addFilter( new SimpleSQLFilter( "intcol", 7, FilterOperator.LT ) ); List<SQLFilter> filters=new ArrayList<SQLFilter>(); filters.add( g1 ); //builder.addFilter( g1 );//tym razem nie ustawiamy filtrów! builder.setParameters( filters );//ale musimy podać parametry użyte w filtrach! String where=SQLQueryBuilder.buildWhere( filters );//fragment zapytania zawierający warunki(bez słowa WHERE) builder.setQuery( "select textcol,boolcol from testtable where "+where ); List<TestDto> data = finder.find( builder, TestDto.class );// wykonanie zapytania
Efekt będzie dokładnie taki sam jak w przykładzie poprzednim. Należy zwrócić uwagę na parametry użyte w filtrach. Spójrzmy ponownie na zapytanie SQL wygenerowwane w przykładzie:
select textcol,boolcol from testtable where ( ( intcol > :intcol_0 and intcol < :intcol_1 ) or ( textcol in (:textcol_2) and boolcol != :boolcol_3 ) )
Parametry są poprzedzone są znakiem dwukropka. Widzimy, że nazwy parametrów różnią się od nazw podanych w definicji filtrów. Dzieje się tak dlatego, że podczas budowania zapytania parametry są numerowane. Celem numerowania jest umożliwienie definiowania wielu warunków dla jednej kolumny. Gdybyśmy nie wprowadzili numerowania zapytanie wyglądało by tak:
select textcol,boolcol from testtable where ( ( intcol > :intcol and intcol < :intcol ) or ( textcol in (:textcol) and boolcol != :boolcol ) )
Problem pojawiłby się przy kolumnie intcol, której dotyczą dwa różne warunki. Bez numeracji parametr :intcol przyjmie wartość 5(podany jako pierwszy) w dwóch miejscach co spowoduje, że nie otrzymamy żadnych wyników. Z tego powody zaleca się korzystanie z metody builder.setParameters(List<SQLFilter> filters) do ustawienia wartości filtrów.
Sortowanie
builder.addSorter( new Sorter( "intcol", SortDirection.ASC ) ); builder.addSorter( new Sorter( "textcol", SortDirection.DESC ) ); builder.setQuery( "select textcol,boolcol,intcol from testtable " ); List<TestDto> data = finder.find( builder, TestDto.class );// wykonanie zapytania //niezależne budowanie klauzuli ORDER BY List<Sorter> sorters=new ArrayList<Sorter>(); sorters.add( new Sorter( "intcol", SortDirection.ASC ) ); sorters.add( new Sorter( "textcol", SortDirection.DESC ) ); String order=SQLQueryBuilder.buildOrder( sorters ); //wygeneruje: intcol ASC, textcol DESC
Paging
CountedResult<Map<String,Object>> mapResult = finder.find( builder, 0, 5 );// CountedResult<TestDto> classResult = finder.find( builder, TestDto.class, 0, 5 ); Long totalMap=mapResult.getTotal(); Long total=classResult.getTotal(); List<Map<String,Object>> mapData=mapResult.getData(); List<TestDto> classData=classResult.getData();
Przykład prezentuje sposób ograniczenia ilości zwracanych wyników. Poza parametrami oznaczającymi odpowiednio przesunięcie w zbiorze wynikowym(offset) i maksymalną ilość wyników(limit), widzimy, że zmienił się typ zwracanej wartości. Klasa CountedResult jest generyczna i służy przechowywaniu danych, wraz z ilością ogólną elementów. W tym przypadku otrzymamy maksymalnie 5 elementów, a wartość total będzie liczbą wszystkich elementów.