%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% :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}\\