Stručný popis
SQL
Procesor slouží k vykonávaní ANSI SQL
dotazů bez nutnosti psát obslužný kód na úrovni JDBC
nebo ORM. Důsledkem je úspora psaní (zejména
obslužného) kódu a zkrácení vývojové fáze, čímž
vzrůstá produktivita realizace. Současně je genericky
ošetřeno množství konverzních a obslužních procesů,
čímž se snižuje chybovost v kódu a vzrůstá
spolehlivost aplikace.
Tyto možnosti nabízí v
současné době množství ORM technologií. SQL Procesor
se odlišuje v tom, že do centra vrací ANSI
SQL. Dobře napsaný SQL dotaz nebo CRUD
příkaz je lepší a jednodušší, než předimenzovaná ORM
technologie, zejména v případě databází s letitou
historií, které nejsou zcela normalizované nebo
normalizovatelné. Tyto databáze jsou taky většinou
nasazené v rozsáhlejších implementacích a zásadnější
změna jejich struktury v podstatě není možná. Na
druhou stranu optimalizace SQL dotazů je velice
žádoucí. SQL Procesor tohle nabízí, většinou bez
nutnosti měnit Java kód, a to dokonce i v produkci.
V neposlední řadě SQL
Procesor nabízí přidanou hodnotu v podpoře
stránkování, třídění, kontroly času vykonávání SQL
dotazů (tedy zatížení produkční databáze), statistiky
atd.
SQL Procesor může být
provozován nad několika technologiemi:
-
nativně nad JDBC
knihovnou -
nad Hibernate ORM
-
nad Spring DAO
Cílem je co nejvíce
vytěžit z uvedených technologií a přizpůsobit SQL
Procesor cílové platformě, která je v provozu.
Jelikož těžiště SQL Procesor-u je ve vykonávaní SQL
dotazů, jeho vlastní architektura je databázově
nezávislá.
Na vstupu do SQL
Procesoru jsou
-
vyhledávací formuláře (nebo také
dotazovací formuláře nebo vstupní třídy),
což jsou POJO třídy. Typicky vstupní formulář je
vyplněn uživatelem webové aplikace. -
META
SQL dotazy nebo příkazy, což jsou
rozšířené ANSI SQL příkazy. Toto rozšíření je
definováno pomocí ANTLR
gramatiky. Všechny META SQL příkazy jsou umístěné
v externím souboru a nejsou součástí Java kódu. -
výstupní
pravidla, což jsou předpisy pro mapování výsledků SQL dotazů na
atributy Java tříd. Tyto předpisy jsou opět
definovány pomocí ANTLR gramatiky. Můžou být
součástí META SQL příkazů, nebo jsou samostatně
umístěné v externím souboru a nejsou součástí
Java kódu.
Na základě vstupních
parametrů je vygenerován finální
SQL příkaz (může být vždy jiný), který je následně
vykonán. Tento princip je nazván “dotaz řízený daty”
(Data Driven Queries). Dosazení
vstupních parametrů je realizováno pomocí reflexe,
bez nutnosti psát obslužný kód.
Na výstupu je
-
seznam výstupních tříd (nebo také transportních tříd, což jsou opět POJO
třídy). Tyto třídy jsou konstruovány opět pomocí
reflexe, bez nutnosti psát obslužný kód.
V následujícím si
uvedeme velice jednoduchý příklad na přibližení
použití této technologie. Více informací je pak možno
obdržet na
kde jsou k dispozici
návody, referenční příručka, příklady a Javadoc. SQL
Procesor je OSS projekt, který je
hostován na GitHub infrastruktuře.
Jednoduchý příklad
Chceme pořídit seznam dat o osobách, přičemž na
vstupu může a nemusí být podmínka na jméno osoby.
Máme databázovou tabulku PERSON s následující strukturou
(HSQLDB)
CREATE TABLE PERSON
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL
, NAME VARCHAR(100) NOT NULL
);
Objektový model, která odpovídá této tabulce, je
Java třída Person,
která
může sloužit taky jako vyhledávací formulář:
package
org.sqlproc.sample.simple.model;
import
java.util.List;
public class Person {
private Long id;
private String
name;
public Person()
{
}
public
Person(String name) {
this.name = name;
}
public Long getId()
{
return id;
}
public void setId(Long
id) {
this.id = id;
}
public String
getName() {
return name;
}
public void
setName(String name) {
this.name = name;
}
}
Chceme tedy obdržet seznam všech lidí v této
tabulce. Výstupní třída je opět Java třída Person. META SQL dotaz se jménem ALL_PEOPLE a výstupní pravidlo se stejným jménem jsou
uvedené v souboru dotazů queries.properties:
QRY_ALL_PEOPLE= \
select p.ID id, p.NAME name \
from PERSON p \
{= where \
{& id=:id} \
{& UPPER(name)=:+name} \
} \
{#1 order by ID} \
{#2 order by NAME}
OUT_ALL_PEOPLE=id name
Každý META SQL dotaz/příkaz je uveden jako QRY_xxx
,kde
xxx
je
jméno. Každé explicitně definované výstupní pravidlo
je uvedeno jako OUT_xxx
, kde xxx
je jméno. Kromě
META SQL dotazů/příkazů a výstupních pravidel můžou
být v tomto souboru uvedené volitelné konfigurační
parametry, které ovlivňují fukcionalitu SQL
Procesor-u. Kažý takový parametr je uveden jako SET_xxx
.
Alternativou k explicitně uvedeným výstupním
pravidlům jsou implicitní výstupní pravidla, které
jsou vnořené do META SQL dotazu. Pro předchozí
příklad to může být:
QRY_ALL_PEOPLE= \
select p.ID @id, p.NAME @name \
from PERSON p \
{= where \
{& id=:id} \
{& UPPER(name)=:+name} \
} \
{#1 order by ID} \
{#2 order by NAME}
Abychom vytvořili instanci SQL Procesor-u nad JDBC
knihovnou k uvedenému dotazu, můžeme použít:
JdbcEngineFactory factory = new JdbcEngineFactory();
SqlQueryEngine sqlEngine = sqlFactory.getQueryEngine("ALL_PEOPLE");
V procesu vytvoření instance JdbcEngineFactory je načten soubor dotazů
queries.properties. Všechny
artefakty (META SQL dotazy/příkazy a výstupní
pravidla) jsou před-kompilována pomocí odpovídajících
ANTLR gramatik. Třída SqlQueryEngine
představuje výkonnou třídu (stroj) SQL Procesor-u,
jež obsahuje jednu dvojici uvedených artefaktů.
Dále potřebujeme databázovou relaci, která je
realizována pomocí třídy SqlSession:
Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:sqlproc", "sa", "");
SqlSession session = new JdbcSimpleSession(connection);
Obdržení seznamu osob je možno:
List<Person> list = sqlEngine.query(session, Person.class);
První parametr metody query()
je databázová relace. Druhý
parametr je signatura třídy, jež říká SQL Procesor-u,
jak konstruovat instance výstupní třídy. V produkci
je vygenerován a proveden následující SQL dotaz:
select p.ID id, p.NAME name from PERSON p
Třídu Person je možno současně použít
jako vyhledávací formulář. Pro obdržení seznamu osob,
jež mají jméno "Jan"
a
pro setřídění tohoto seznamu sestupně podle jména:
Person person = new Person();
person.setName("Jan");
List<Person> list = sqlEngine.query(session, Person.class, person, SqlOrder.getDescOrder(2));
První parametr metody query()
je databázová relace. Druhý
parametr je signatura třídy, jež říká SQL Procesor-u,
jak konstruovat výstupní třídy. Třetí parametr je
vyhledávací formulář. Čtvrtý parametr je předpis pro
třídění. V produkci je vygenerován a proveden
následující SQL dotaz:
select p.ID id, p.NAME name from PERSON p where UPPER(name)=? order by NAME DESC
Pro obdržení seznamu osob, jež mají ve jménu
textový fragment "an"
a pro setřídění tohoto seznamu sestupně podle jména,
použijeme v souboru dotazů následující META SQL
dotaz:
SET_LIKE_STRING=like
SET_WILDCARD_CHARACTER=%
SET_SURROUND_QUERY_LIKE=true
SET_SURROUND_QUERY_MIN_LEN=2
QRY_LIKE_PEOPLE= \
select p.ID @id, p.NAME @name \
from PERSON p \
{= where \
{& id=:id} \
{& UPPER(name) like :+name} \
} \
{#1 order by ID} \
{#2 order by NAME}
Dále pak pro obdržení tohoto seznamu:
SqlQueryEngine sqlEngine = sqlFactory.getQueryEngine("LIKE_PEOPLE");
Person person = new Person();
person.setName("an");
List<Person> list = sqlEngine.query(session, Person.class, person, SqlOrder.getDescOrder(2));
V produkci je vygenerován a proveden následující
SQL dotaz:
select p.ID id, p.NAME name from PERSON p where UPPER(name) like ? order by NAME ASC
META SQL
META SQL je rozšířením ANSI SQL.
Rozšířující elementy začínají levou složenou závorkou
{
a
končí pravou složenou závorkou }
. Vstupní hodnoty
z vyhledávacích formulářů jsou označeny prefixem
:
nebo $
. Implicitní výstupní pravidla jsou
přiřazeny výstupním databázovým sloupcům nebo
alias-ům a jsou označeny prefixem @
. V předchozím
META SQL dotazu QRY_ALL_PEOPLE
:
-
select p.ID @id, p.NAME
je
@name from PERSON p
standardní SQL fragment dotazu. Z tohoto
fragmentu jsou odstraněny znaky@
. Ty slouží pro korelaci aliasů databázových
sloupců a atributů výstupních tříd. -
{= where
je speciální označení pro WHERE fragment SQL dotazu. Je užit pro
označení této části SQL dotazu. Současně aktivuje
speciální zpracovnání vstupních atributů v tomto
fragmentu. Před tento fragment je přidáno klíčové
slovo WHERE a všechny potenciální
prefixy AND/OR z
následujícih podmínek jsou ostraněny. -
{& id=:id}
je podmíněný fragment dotazu
typu AND. Text mezi{&
a}
se stane součástí vygenerovaného SQL dotazu
pouze v případě, že všechny vstupní hodnoty
uvedené mezi závorkami jsou neprázdné. V tomto
případě je zde vstupní hodnota:id
, takže pokud ve vyhledávacím formuláři
je atribut id neprázdný, vygenerovaný SQL
dotaz bude obsahovat fragmentAND id=:id
a hodnotaid
atributu bude použita v tomto dotazu.
Pokud ve vyhledávacím formuláři je atributid
prázdný, vygenerovaný SQL dotaz nebude
obsahovat tento fragment. -
{&
je
UPPER(name)=:+name}
podmíněný fragment dotazu typu AND.
Text mezi{&
a}
se stane součástí vygenerovaného SQL dotazu
pouze v případě, že všechny vstupní hodnoty
uvedené mezi závorkami jsou neprázdné. V tomto
případě je zde vstupní hodnota:
+name
,
takže pokud ve vyhledávacím formuláři je atribut
id neprázdný,
vygenerovaný SQL dotaz bude obsahovat fragment
AND
a
UPPER(name)=:name
hodnotaid
atributu převedena na velká
písmena bude použita v tomto dotazu. Znak+
v:+name
značí konverzi na velká
písmena. Pokud ve vyhledávacím formuláři je
atributname
prázdný, vygenerovaný SQL dotaz nebude obsahovat
tento fragment. -
{#1 order by ID}
je předpis pro třídění s
identifikátoremid = 1
. V případě, že metodaquery()
má jako
parametrSqlOrder.getAscOrder(1)
,
vygenerovaný SQL dotaz bude obsahovat fragment
order by ID
. V případě, že
ASC
metodaquery()
má jako parametrSqlOrder.getDesOrder(1)
, vygenerovaný SQL dotaz bude obsahovat
fragmentorder by ID DESC
. -
{#1 order by ID
je předpis pro
NAME}
třídění s identifikátoremid =
2
. V
případě, že metodaquery()
má
jako parametrSqlOrder.getAscOrder(
2
)
, vygenerovaný SQL dotaz bude
obsahovat fragmentorder by NAME ASC
. V případě, že metodaquery()
má
jako parametrSqlOrder.getDesOrder(
2
)
, vygenerovaný SQL dotaz bude
obsahovat fragmentorder by NAME DESC
. Předpisy pro třídění je
možno kombinovat, jako např.SqlOrder.getDescOrder(1).addAscOrder(2)
.
Výstupní pravidla
Výstupní pravidla jsou předpisy
pro mapování výsledků SQL dotazů
na atributy Java tříd, tedy předpisy, jak naplnit
instance výstupních tříd. V podstatě to jsou seznamy
databázových sloupců a/nebo aliasů, jež jsou použity
jako výstupní hodnoty. Jmémo databázového sloupce
nebo aliasu nemusí odpovídat názvu příslušného
atributu výstupní třídy. Výstupní pravidlo v tomto
případě koreluje tyto názvy. Dále pak ani typ
databázového sloupce nebo aliasu nemusí odpovídat
typu příslušného atributu výstupní třídy. Ve
výstupním pravidlu může být v tomto případě uvedeno
konverzní pravidlo (nebo je užito standardní
konverzní pravidlo). V předchozím (explicitním)
výstupním pravidlu OUT_ALL_PEOPLE
:
-
id
je název databázového sloupce a
současně je to název atributu ve výstupní třídě -
name
je název databázového sloupce
a současně je to název atributu ve výstupní třídě
V případě implicitního výstupního pravidla zde není
uveden/použit OUT_ALL_PEOPLE
, ale v META SQL dotazu
QRY_ALL_PEOPLE
všechny aliasy s prefixem @
mají
stejnou funkci.
Konfigurační parametry
Volitelné konfigurační parametry
ovlivňují fukcionalitu SQL Procesor-u. Příkladem jsou
parametry, které slouží pro aktivaci speciálního
vyhledávání podle textových fragmentů. Tato
funkcionalita byla prezentována v předchozím META SQL
dotazu QRY_LIKE_PEOPLE
:
-
SET_SURROUND_QUERY_LIKE=true
– tento
parametr aktivuje uvedenou funkcionalitu -
SET_LIKE_STRING=like
–
kažý SQL dotaz s klíčovým slovemlike
může sloužit pro
uvedený typ vyhledávání -
SET_SURROUND_QUERY_MIN_LEN=2
– minimální
delká textového řetězce, který může být použit
pro tento typ vyhledávání -
SET_WILDCARD_CHARACTER=%
– speciální
znak, který může sloužit jako prefix/postfix pro
textové fragmenty
Uvedené příklad je k dispozici v GIT repository https://github.com/hudec/sql-processor/tree/master/sql-samples/simple-jdbc.