%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% :JDBC
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{JDBC} \label{JDBC}

JDBC (Java Database Connectivity) je technologie která umožňuje snadno přistupovat z~programu v~jazyce Java do relační databáze. S~databází se pracuje na základní úrovni, nutná je znalost jazyka SQL (tvorba dotazů v~SQL je popsána v~\cite{SQL}). V~této kapitole si ukážeme, jak se připojit k~databázi a~zasílat jí SQL příkazy.



\subsection{Ovladač}

Ze všeho nejdříve je nutné mít nainstalovaný JDBC ovladač (JDBC driver) pro databázi se kterou chceme pracovat. Ovladač je vlastně soubor tříd v~jazyce Java který řeší připojení k~té které databázi a~je poskytován tvůrcem databázového serveru. Ovladač obdržíme jako několik souborů typu \emph{jar} a~nainstalujeme jej jednoduše přidáním do CLASSPATH nebo jako knihovnu v~námi používaném IDE. V~následujících příkladech budeme pracovat s~databází Apache Derby. Řešení příkladu pro jiné databáze jako MySQL nebo Oracle je obdobné, stačí pouze zaměnit ovladač. Náš příklad tedy bude začínat následující konstrukcí:

\begin{Verbatim}
try \{
    Class.forName("org.apache.derby.jdbc.ClientDriver");
\}
catch (ClassNotFoundException ex) \{
    System.err.println("Unable to load JDBC driver");
\}
\end{Verbatim}

Všimněte si, že instanci ovladače nikde neukládáme, sama se automaticky zaregistruje u~objektu \texttt{DriverManager} z~balíčku \texttt{java.sql}. Dále si všimněte, že používaný ovladač je zde vyjádřen řetězcovou konstantou. To je ideální přístup pro dynamické načtení názvu ovladače z~konfiguračního souboru a~předání názvu jako proměnné. Tím lze ovladač měnit i~za běhu aplikace.




\subsection{Spojení s~databází}

Jakmile máme ovladač zaregistrován, můžeme se již připojit k~databázovému serveru. Potřebujeme znát URL pro připojení a~pokud je databáze chráněná, pak také přístupové jméno a~heslo. URL pro připojení je uvozena protokolem, který se v~jednotlivých ovladačích liší a~je potřeba si jej přečíst z~dokumentace. Ovladač Derby používá protokol \emph{jdbc:derby}. Předpokládejme, že databáze je přístupná na adrese \emph{db.animalia.com}. Pak celá URL pro přístup bude \emph{jdbc:derby://db.animalia.com}. Dále předpokládejme, že naše uživatelské jméno pro přístup je \emph{admin} a~heslo \emph{adminadmin}. Spojení s~databází navážeme pomocí statické metody \texttt{getConnection(url, username, password)} třídy \texttt{DriverManager}. Volání této metody nám vrátí objekt typu \texttt{Connection} z~balíčku \texttt{java.sql}, jenž si uložíme do proměnné. Pokud se spojení nepodaří získat, bude vyhozena výjimka \texttt{SQLException} z~balíčku \texttt{java.sql}.

\begin{Verbatim}
String url = "jdbc:derby://db.animalia.com";
String username = "admin";
String password = "adminadmin";
	
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}




\subsection{Poslání SQL příkazu}

Pro poslání SQL příkazu na databázový server potřebujeme nejdřív instanci třídy \texttt{Statement} z~balíčku \texttt{java.sql}. Tu získáme zavoláním tovární metody \texttt{createStatement} na používané instanci třídy \texttt{Connection}. Přes získaný objekt typu \texttt{Statement} již můžeme posílat samotné SQL příkazy. Uděláme to pomocí volání metod \texttt{executeQuery} nebo \texttt{executeUpdate}. Kterou z~těchto metod použít pro který SQL příkaz si ukážeme za chvíli.

\begin{Verbatim}
String url = "jdbc:derby://db.animalia.com";
String username = "admin";
String password = "adminadmin";
	
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
    \codeHighlight{Statement statement = connection.createStatement();}
\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}



\subsubsection{CREATE TABLE}

Vytváření tabulek v~databázi se realizuje voláním metody \texttt{executeUpdate} na objektu typu \texttt{Statement}. Základní verze této metody bere pouze jeden parametr a~to SQL příkaz. Existují i~přetížené verze, ale pro náš příklad je nepotřebujeme a~jejich popisování by vše příliš zkomplikovalo. Pokud o~ně i~přesto máte zájem, jejich popis najdete v~dokumentaci na \cite{JDBC API}. Metoda \texttt{executeUpdate} nevrací žádnou hodnotu. V~příkladu vytvoříme v~databázi tabulku Zvířata (Animals) se třemi sloupečky -- identifikačním číslem, rodem (genus) a~druhem (species).

\begin{Verbatim}
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
    Statement statement = connection.createStatement();
    \codeHighlight{statement.executeUpdate("CREATE TABLE Animals(" +}
    \codeHighlight{    "id INTEGER, genus VARCHAR(30), species VARCHAR(30)");}
\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}




\subsubsection{INSERT}

Vkládání do databáze se realizuje voláním metody \texttt{executeUpdate}. Do tabulky zvířat nyní přidáme tygra (\emph{Panthera tigris}).

\begin{Verbatim}
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
    Statement statement = connection.createStatement();
    \codeHighlight{statement.executeUpdate(}
    \codeHighlight{        "INSERT INTO Animals VALUES (1, 'Panthera', 'Tigris')");}
\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}




\subsection{UPDATE}

Upravování záznamů v~databázi se provede voláním metody \texttt{executeUpdate}. Změníme v~tabulce zvířat tygra na lva (\emph{Panthera leo}). Stačí úprava druhu, jelikož oba patří do stejného rodu (tj. \emph{Panthera}).

\begin{Verbatim}
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
    Statement statement = connection.createStatement();
    \codeHighlight{statement.executeUpdate(}
    \codeHighlight{        "UPDATE TABLE Animals SET species = 'Leo' WHERE id = 1");}
\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}




\subsection{DELETE}

Smazání dat z~databáze se zařídí voláním metody \texttt{executeUpdate}. V~tabulce zvířat teď smažeme všechny zvířata z~rodu \emph{Panthera}.

\begin{Verbatim}
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
    Statement statement = connection.createStatement();
    \codeHighlight{statement.executeUpdate(}
    \codeHighlight{        "DELETE FROM Animals WHERE genus = 'Panthera'");}
\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}




\subsection{SELECT}

Získání dat z~databáze docílíme voláním metody \texttt{executeQuery} na objektu typu \texttt{Statement}. Tato metoda bere jeden parametr (SQL příkaz) a~vrací hodnotu objektového typu \texttt{ResultSet} z~balíčku \texttt{java.sql}. Zde je vidět, proč jsou SQL příkazy v~JDBC zavedeny pomocí různých dvou metod -- jedna vrací hodnotu, druhá nikoliv. Předpokládejme nyní, že v~databázi existují nějaká data zvířat. V~příkladu chceme vypsat všechna zvířata z~rodu \emph{Panthera}.

\begin{Verbatim}
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
    Statement statement = connection.createStatement();
    \codeHighlight{ResultSet result = statement.executeQuery(}
    \codeHighlight{    "SELECT id, genus, species FROM Animals WHERE genus = 'Panthera'");}
\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}




\subsection{Iterace přes výsledek SELECTu}

Výsledek příkazu SELECT obsažený v~získané instanci třídy \texttt{ResultSet} je soubor řádků tabulky zvířat které splňují zadanou podmínku. Předpokládejme, že v~databázi byly uloženy pouze dvě zvířata rodu \emph{Panthera} -- tygr a~lev. Výsledkem jsou tedy dva řádky. Jelikož jsme zadali v~příkazu SELECT výčet všech tří sloupcových hodnot, bude celý výsledek ve formě tabulky 2 * 3. Situaci ukazuje následující obrázek.

\begin{figure}[ht]
  \centerline{\includegraphics[height=18mm]{images/result-set-row-0.png}}
  \caption{Výsledek dotazu SELECT}
\end{figure}

Objekt typu \texttt{ResultSet} nemá vestavěn obyčejný iterátor, projití přes všechny řádky je však velice jednoduché. Stačí na něm v~cyklu volat metodu \texttt{next}, která pomyslný iterátor posune vždy o~řádek dál. Po prvním zavolání \texttt{next} bude pomyslný výsledek ve formě tabulky vypadat takto:

\begin{figure}[ht]
  \centerline{\includegraphics[height=18mm]{images/result-set-row-1.png}}
  \caption{Iterace přes řádky výsledku}
\end{figure}

Metoda \texttt{next} navíc vrací \texttt{true} pokud existují další řádky a~\texttt{false} pokud nikoliv. Nabízí se řešení pomocí cyklu \texttt{while}.

\begin{Verbatim}
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
    Statement statement = connection.createStatement();
    ResultSet result = statement.executeQuery(\\
        "SELECT id, genus, species FROM Animals WHERE genus = 'Panthera'");

    \codeHighlight{while (result.next()) \{}
    \codeHighlight{    // zpracování dat\\}
    \codeHighlight{\}}

\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}




\subsection{Získání hodnot z~výsledku SELECTu}

Teď již umíme projít přes všechny řádky obsažené v~instanci třídy \texttt{ResultSet}. To nejdůležitější je ale získat z~nich data. Pro to musíme znát datový typ každého sloupce. Pro datový typ řetězec použijeme metodu \texttt{getString}, pro celé číslo \texttt{getInt} atd. Pro každou z~těchto metod můžeme použít dvě verze. První verze bere jako parametr název sloupce, u~kterého chceme získat hodnotu. Pro sloupec druhu zvířete (\emph{species}) to bude tedy \texttt{getString("species")}. Pokud názvy sloupců neznáme, můžeme použít druhou variantu. Ta přistupuje ke sloupcům pomocí indexu (číslováno od nuly), tedy pro třetí sloupec to bude \texttt{getString(2)}. Následující příklad ukazuje, jak vytisknout získaná data o~zvířatech rodu \emph{Panthera}. U~reálné aplikace by místo toho samozřejmě bylo nějaké hlubší zpracování.

\begin{Verbatim}
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
    Statement statement = connection.createStatement();
    ResultSet result = statement.executeQuery(\\
        "SELECT id, genus, species FROM Animals WHERE genus = 'Panthera'");

    while (result.next()) \{
        \codeHighlight{System.out.println(result.getInt("id"));}
        \codeHighlight{System.out.println(result.getString("genus"));}
        \codeHighlight{System.out.println(result.getString("species"));}
        \codeHighlight{System.out.println("---");}
    \}
\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}





\subsection{Uzavření spojení}

Po ukončení práce s~databází je nutné řádně uzavřít spojení. To uděláme jednoduše pomocí zavoláním metody \texttt{close} na používané instanci třídy \texttt{Connection}. Počet spojení je zpravidla na databázovém serveru omezený a~řešený pomocí návrhového vzoru \emph{Pool} (více v~\cite{Navrhove vzory}). Pokud bychom zapomněli spojení uzavřít, bylo by jedno vlákno spojení na serveru blokováno a~čekalo by se na timeout. Databázi by to sice neohrozilo, mohlo by však dojít k~jejímu dočasnému zpomalení. Proto bychom měli vždy dbát na explicitní ukončení spojení.To samé platí pro uzavření statementu.

\begin{Verbatim}
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
    Statement statement = connection.createStatement();
    statement.executeUpdate(
            "INSERT INTO Animals VALUES (1, 'Panthera', 'Tigris')");
    \codeHighlight{statement.close();}
    \codeHighlight{connection.close();}
\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}





\subsection{Předpřipravený příkaz}

JDBC umožňuje kromě obyčejných příkazů typu SELECT provádět i~příkazy předpřipravené (prepared statements). Jedná se o~šablony příkazu SELECT kde jsou některé části v~podmínce vyjádřeny jako parametry. Reálné hodnoty parametrů se zadají až při konkrétním volání příkazu metodou \texttt{executeQuery} (pro tento účel slouží její přetížená verze bez parametrů). SQL příkaz je zde uveden už při konstrukci objektu typu \texttt{PreparedStatement} (nahrazuje původní \texttt{Statement}). Výhodou celého postupu je, že kvalitní databázové servery implementují předpřipravený SELECT na své straně a~pokud budeme ten samý SELECT provádět vícekrát za sebou pouze s~jinými parametry v~podmínce, výrazně se tím urychlí odezva databázového serveru.

Jak již bylo zmíněno, namísto instance třídy \texttt{Statement} použijeme instanci třídy \texttt{PreparedStatement} ze stejného balíčku \texttt{java.sql}. Její získání je obdobné. Zavoláme metodu \texttt{prepareStatement} na používaném objektu typu \texttt{Connection} která nám požadovaný objekt vyrobí. Jako parametr metody \texttt{prepareStatement} zadáváme SQL příkaz typu SELECT. Části SELECT příkazu, které chceme parametrizovat nahradíme znakem \texttt{?}. Reálné hodnoty do příkazu dosadíme voláním metod \texttt{setString}, \texttt{setInt}, atd. (princip je stejný jako u~získávání hodnot sloupců u~výsledku SELECTu). Metoda \texttt{setString} (a~její alternativy) bere jako první argument index parametru předpřipraveného výrazu, jako druhý argument hodnotu tohoto parametru. Jakmile jsou všechny parametry zadány, zbývá zavolat metodu \texttt{executeQuery} (bez parametrů) a~zpracovat výsledek stejně, jako u~obyčejného SELECTu. 

\begin{Verbatim}
try \{
    Connection connection = DriverManager.getConnection(
            url, username, password);
    \codeHighlight{PreparedStatement statement = connection.prepareStatement(}
    \codeHighlight{    "SELECT id, genus, species FROM Animals WHERE genus = ?");}
    \codeHighlight{statement.setString(0, "Panthera");}
    \codeHighlight{ResultSet result = statement.executeQuery();}

    while (result.next()) \{
        System.out.println(result.getInt("id"));
        System.out.println(result.getString("genus"));
        System.out.println(result.getString("species"));
        System.out.println("---");
    \}
\}
catch (SQLException ex) \{
    ex.printStackTrace();
\}
\end{Verbatim}

Pro zmíněné zrychlení odezvy ze strany databáze je důležité uchovat referenci na instanci \texttt{PreparedStatement} a~každý další SELECT volat skrz ni. Před takovýmto voláním samozřejmě přepíšeme hodnoty všech parametrů.





\subsection{Závěr}

JDBC představuje velmi pohodlnou práci s~relační databází. Velkou výhodou je možnost snadno měnit JDBC spolu s~adresou databázového serveru. To umožňuje snadno přejít na jiný databázový server a~to dokonce bez nutnosti rekompilace zdrojového kódu. Jelikož je JDBC relativně nízkoúrovňové rozhraní, dává programátorovi veškeré možnosti, které databázový server nabízí. Nevýhodou je nutnost velmi dobré znalosti jazyka SQL a~také složité ruční zpracování výsledků příkazu SELECT (zejména u~velkého počtu sloupců).

Použitá literatura:\\
\cite{JDBC Introduction} \emph{Úvod do JDBC}\\
\cite{JDBC Basics} \emph{Základy JDBC}\\
\cite{JDBC API} \emph{Dokumentace tříd JDBC}\\

