3. diel - Databázy v Java JDBC - Výpis dát a parametre
V minulom dieli nášho seriálu, Návrh MySQL databázy v IntelliJ IDE, sme si pripravili databázu s testovacími dátami.
V dnešnom tutoriále sa k našej MySQL databáze v Jave pripojíme a budeme z nej čítať hodnoty.
Inštalácia MySQL connectora
Pre pripojenie k MySQL databáze v našom Java projekte využijeme
MySQL connector. V IntelliJ IDE si náš projekt otvoríme a do
súboru pom.xml
, dovnútra tagu <dependencies>
,
pridáme nasledujúcu závislosť:
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>9.0.0</version> </dependency>
Po vložení sa nám v pravom hornom rohu objaví ikonka na načítanie zmien, ktoré sme práve vykonali (pridanie závislosti). Na ikonku klikneme:
Tým sme úspešne pridali závislosť potrebnú na pripojenie k MySQL databáze 🙂
Načítanie ovládača
V minulosti bolo treba JDBC ovládač pred použitím načítať. Robilo sa to týmto spôsobom:
try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException ex) { System.out.println("Error while loading the database driver"); }
Java si dnes už ovládač načíta sama a tento kód teda nie je potrebný. Uvádzame ho len preto, že je niekedy ešte vidieť v starších projektoch alebo zastaraných tutoriáloch.
Triedy pre prácu s databázou
S databázou budeme pracovať v metóde main()
pomocou svätej
trojice tried Connection
, PreparedStatement
a
ResultSet
. Všetky sa nachádzajú v balíčku
java.sql
. Do projektu si teda doplníme import:
import java.sql.*;
Trieda Connection
Connection
zaisťuje databázové spojenie. To je potrebné
vytvoriť predtým, než sa databázy na niečo spýtame. Pri jeho vytváraní
uvedieme tzv. connection string. Ide o reťazec obsahujúci
meno databázového ovládača, URL servera,
kde databáza beží, ďalej názov databázy,
používateľské meno a heslo.
Vytvorenie novej inštancie spojenia bude v našom prípade vyzerať takto:
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/dictionary_db?user=root&password=")
Trieda PreparedStatement
PreparedStatement
je databázový dotaz. Pri vytvorení
inštancie zadávame SQL kód, ktorý chceme na databáze spustiť. Java
obsahuje aj triedu Statement
, ktorá sa od
PreparedStatement
líši tým, že nemôže obsahovať
parametre.
V našom prípade si vytvoríme nasledujúcu inštanciu dotazu:
PreparedStatement statement = connection.prepareStatement("SELECT * FROM word")
Vytvorili sme jednoduchý SQL dotaz. Príkazom SELECT
hovoríme,
že chceme z databázovej tabuľky vybrať dáta. Hviezdička označuje, že pri
výsledných riadkoch chceme vybrať hodnoty zo všetkých stĺpcov.
FROM word
označuje, že vyberáme z tabuľky word
.
Dotaz teda vyberie všetky hodnoty všetkých slov.
Všimnime si, že sa dotaz vytvára cez inštanciu spojenia.
Trieda ResultSet
ResultSet
je kolekcia výsledkov, ktoré vrátil nejaký SQL
dotaz. ResultSet
naplnený výsledkami SQL príkazu
SELECT
získame pomocou metódy executeQuery()
na
inštancii dotazu:
ResultSet results = statement.executeQuery();
Uzatváranie spojenia
Ak ste v Jave už pracovali so súbormi, nebude pre vás žiadnym prekvapením, že aj databázové spojenie sa musí uzavrieť. Malým prekvapením však môže byť, že sa musíme postarať o správne uzavretie všetkých troch databázových objektov. Keby sme to neurobili, zostalo by spojenie otvorené a server by sa za nejaký čas zahltil.
Najjednoduchší spôsob je vytvoriť objekty v bloku
try-with-resources
. Akonáhle Java tento blok kódu opustí, sama
sa postará o uzavretie inštancií, ktoré sú v deklarácii bloku
vytvorené:
try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/dictionary_db?user=root&password="); PreparedStatement statement = connection.prepareStatement("SELECT * FROM word"); ResultSet results = statement.executeQuery();) { // Processing the result } catch (SQLException ex) { System.out.println("Error while communicating with the database"); }
Ak sa niečo pokazí, informujeme o tom používateľa chybovou hláškou.
Pri ladení blok catch
zakomentujeme, aby sme mohli
na chyby reagovať a kód opraviť.
Výpis výsledkov
V premennej results
máme už načítané slovíčka z
databázy. Zostáva ich len vypísať. ResultSet
na to poskytuje
metódu next()
. Tá presunie aktuálnu pozíciu v kolekcii na
ďalší prvok alebo vráti false
v prípade, že sme na konci
výsledkov.
Na samotné čítanie hodnoty na aktuálnom riadku výsledkov slúžia
metódy getInt()
, getString()
, getDate()
a ďalšie. Metódam môžeme dať ako parameter buď názov stĺpca alebo jeho
číselný index. Pri číselnom indexe pozor, prvý stĺpec má index
1
:
while (results.next()) { int id = results.getInt(1); String english = results.getString("english"); String spanish = results.getString("spanish"); System.out.println("Id: " + id + ", English: " + english + ", Spanish: " + spanish); }
Kódom vyššie iterujeme nad výsledkami, získavame ich parametre a tie potom vypisujeme do konzoly. Ukážme si ešte kompletný kód aplikácie:
try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/dictionary_db?user=root&password="); PreparedStatement statement = connection.prepareStatement("SELECT * FROM word"); ResultSet results = statement.executeQuery()) { while (results.next()) { int id = results.getInt(1); String english = results.getString("english"); String spanish = results.getString("spanish"); System.out.println("Id: " + id + ", English: " + english + ", Spanish: " + spanish); } } catch (SQLException ex) { System.out.println("Error while communicating with the database"); }
Môžeme si skúsiť, že aplikácia naozaj vypíše všetky slovíčka z databázy:
Konzolová aplikácia
Id: 1, English: computer, Spanish: ordenador
Id: 2, English: ball, Spanish: pelota
Id: 3, English: dog, Spanish: perro
Id: 4, English: I, Spanish: yo
Id: 5, English: love, Spanish: amor
Id: 6, English: ICT, Spanish: ICT
Odovzdávanie parametrov
Doveďme aplikáciu naozaj do podoby slovníčka. Používateľa necháme zadať slovíčko v španielčine a to mu následne preložíme do angličtiny.
SQL injekcia
SQL dotaz by mal teraz vybrať len určitý riadok, je do neho teda potrebné
dodať podmienky. To docielime pomocou klauzuly WHERE
. Naivne by
sme mohli vložiť slovíčko od používateľa priamo do tela SQL dotazu:
// This code is dangerous Scanner scanner = new Scanner(System.in); System.out.println("Enter a Spanish word to be translated:"); String spanish = scanner.nextLine(); try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/dictionary_db?user=root&password="); PreparedStatement statement = connection.prepareStatement("SELECT english FROM word WHERE spanish=\"" + spanish + "\""); ResultSet results = statement.executeQuery();) { results.next(); String english = results.getString("english"); System.out.println("Translating " + spanish + ": " + english); } catch (SQLException ex) { System.out.println("Error while communicating with the database"); }
Výsledok:
Konzolová aplikácia
Enter a Spanish word to be translated:
ordenador
Translating ordenador: computer
Kód sa príliš nezmenil. V SQL dotaze už nevyberáme všetky stĺpce, ale
iba stĺpec english
. Okrem toho nám tu pribudla podmienka
WHERE
. Výsledky už nenačítame v while
cykle,
pretože nás zaujíma iba jeden.
Hoci sa zdá, že aplikácia funguje perfektne, opak je pravdou. Čokoľvek používateľ zadá, sa vloží priamo do SQL dotazu. Čo sa stane, keď zadá napr. nasledujúci reťazec:
"; DROP TABLE word --
Na databáze sa spustí príkaz na vymazanie tabuľky a máme po dátach. To je ešte ten lepší prípad, šikovnejší používateľ by nám cez slovníček mohol napríklad vytiahnuť heslá používateľov z inej tabuľky. A to už by bol problém. Verme alebo nie, ale používatelia takéto vstupy naozaj zadávajú a naše aplikácie sa im musia brániť. Tejto technike útoku sa hovorí SQL injection, pretože sa vkladá cudzí SQL kód do nášho dotazu.
Odovzdávanie parametrov
Celý problém je samozrejme v tom, že vkladáme vstup od
používateľa priamo do SQL dotazu. V minulosti sa premenné
ošetrovali špeciálnou funkciou, ktorá tzv. zescapovala nebezpečné znaky
(najmä úvodzovky). Najbezpečnejšie je však používať
PreparedStatements
. Ide o dotaz, ktorý obsahuje namiesto
parametrov zástupné znaky, najčastejšie otázniky. Samotné
hodnoty sa do dotazu dosadia oddelene a samotná databáza sa
postará o ich bezpečné vloženie do dotazu.
Prepíšme našu aplikáciu tak, aby používala parametrizované dotazy:
Scanner scanner = new Scanner(System.in); System.out.println("Enter a Spanish word to be translated:"); String spanish = scanner.nextLine(); try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/dictionary_db?user=root&password="); PreparedStatement statement = connection.prepareStatement("SELECT english FROM word WHERE spanish=?");) { statement.setString(1, spanish); try (ResultSet results = statement.executeQuery()) { results.next(); String english = results.getString("english"); System.out.println("Translating " + spanish + ": " + english); } } catch (SQLException ex) { System.out.println("Error while communicating with the database"); }
Všimnime si otáznik v dotaze a volaní metódy setString()
,
ktorá nastaví prvý parameter v dotaze na daný reťazec. Samozrejme tu
nájdeme aj metódy pre ďalšie dátové typy.
Výstup bude podobný:
Konzolová aplikácia
Enter a Spanish word to be translated:
perro
Translating perro: dog
Naša aplikácia je však oproti predchádzajúcemu príkladu teraz bezpečná.
V nasledujúcom kvíze, Kvíz - Základy práce s databázou v Java JDBC, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 17x (2.1 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Java