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
    @name from PERSON p
    je
    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 fragment AND id=:id a hodnota id atributu bude použita v tomto dotazu.
    Pokud ve vyhledávacím formuláři je atribut id prázdný, vygenerovaný SQL dotaz nebude
    obsahovat tento fragment.

  • {&
    UPPER(name)=:+name}
    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 :+name,
    takže pokud ve vyhledávacím formuláři je atribut
    id neprázdný,
    vygenerovaný SQL dotaz bude obsahovat fragment
    AND
    UPPER(name)=:name
    a
    hodnota id 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
    atribut name
    prázdný, vygenerovaný SQL dotaz nebude obsahovat
    tento fragment.

  • {#1 order by ID} je předpis pro třídění s
    identifikátorem id = 1. V případě, že metoda query() má jako
    parametr SqlOrder.getAscOrder(1),
    vygenerovaný SQL dotaz bude obsahovat fragment
    order by ID
    ASC
    . V případě, že
    metoda query() má jako parametr SqlOrder.getDesOrder(1), vygenerovaný SQL dotaz bude obsahovat
    fragment order by ID DESC.

  • {#1 order by ID
    NAME}
    je předpis pro
    třídění s identifikátorem id = 2. V
    případě, že metoda query()
    jako parametr SqlOrder.getAscOrder(2), vygenerovaný SQL dotaz bude
    obsahovat fragment order by NAME ASC. V případě, že metoda query()
    jako parametr SqlOrder.getDesOrder(2), vygenerovaný SQL dotaz bude
    obsahovat fragment order 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 slovem like 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.