Office week Slevový týden - Květen
Pouze tento týden sleva až 80 % na e-learning týkající se MS Office
30 % bodů zdarma na online výuku díky naší Slevové akci!

Lekce 3 - Databáze v Java JDBC - Výpis dat a parametry

V minulém dílu našeho seriálu, Návrh MySQL databáze v NetBeans IDE, jsme si připravili databázi s testovacími daty.

V dnešním dílu se k ní v Javě připojíme a budeme z ní číst hodnoty.

Vytvoření projektu

Vytvoříme si novou Java application s názvem Slovnicek. V oknu Projects na projekt klikneme pravým a zvolíme Properties.

V kategoriích vlevo vybereme Libraries a klikneme na Add Library.

Přidání MySQL ovladače do Java projektu

V dialogu vybereme MySQL JDBC Driver a potvrdíme. Když budete chtít pracovat s jakoukoli databází pomocí JDBC, je vždy třeba přidat tímto způsobem příslušný Connector. Ten se dá stáhnout jako soubor .jar na webu výrobce databáze a přidat stejným způsobem pomocí tlačítka Add JAR.

Přidání MySQL ovladače do Java projektu

Načtení ovladače

V minulosti bylo třeba JDBC ovladač před použitím načíst. Dělalo se to tímto způsobem:

try {
    Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException ex) {
    System.out.println("Chyba při načtení databázového ovladače");
}

Java si dnes již ovladač načte sama a tento kód tedy není třeba. Uvádím ho jen z toho důvodu, kdybyste ho viděli ve starších projektech nebo zastaralých tutoriálech.

Connection, PreparedStatement a ResultSet

S databází budeme pracovat pomocí svaté trojice tříd Connection, PreparedStatement a ResultSet. Všechny se nacházejí v balíčku java.sql.

Connection

Connection je databázové spojení. To je nutné vytvořit předtím, než se databáze na něco zeptáme. Při jeho vytváření uvedeme tzv. connection string. To je řetězec obsahující jméno databázového ovladače, URL serveru, kde databáze běží, dále název databáze, uživatelské jméno a heslo.

Vytvoření nové instance spojení bude v našem případě vypadat takto:

Connection spojeni = DriverManager.getConnection("jdbc:mysql://localhost/slovnicek_db?user=root&password=")

PreparedStatement

PreparedStatement je databázový dotaz. Při vytvoření instance zadáváme SQL kód, který chceme na databázi spustit. Java obsahuje také třídu Statement, která se od PreparedStatement liší tím, že nemůže obsahovat parametry (o tom dále).

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

V našem případě si vytvoříme následující instanci dotazu:

PreparedStatement dotaz = spojeni.prepareStatement("SELECT * FROM slovo")

Všimněte si, že se dotaz vytváří přes instanci spojení.

Slušelo by se vysvětlit i samotný SQL dotaz. Příkazem SELECT říkáme, že chceme z databázové tabulky vybrat data. Hvězdička označuje, že u výsledných řádků chceme vybrat hodnoty ze všech sloupců. FROM slovo označuje, že vybíráme z tabulky slovo. Dotaz tedy vybere všechny hodnoty všech slov.

ResultSet

ResultSet je kolekce výsledků, které vrátil nějaký SQL dotaz. ResultSet naplněný výsledky SQL příkazu SELECT získáme pomocí metody executeQuery() na instanci dotazu.

ResultSet vysledky = dotaz.executeQuery();

Uzavírání spojení

Pokud jste v Javě již pracovali se soubory, nebude pro vás žádným překvapením, že i databázové spojení se musí uzavřít. Malým překvapením však může být, že se musíme postarat o správné uzavření všech 3 databázových objektů. Kdybychom to neudělali, zůstalo by spojení otevřené a server by se za nějakou dobu zahltil.

Nejjednodušší způsob je vytvořit objekty v bloku try-with-resources (TWR). Jakmile Java tento blok kódu opustí, sama se postará o uzavření instancí, které jsou v deklaraci bloku vytvořené.

try (Connection spojeni = DriverManager.getConnection("jdbc:mysql://localhost/slovnicek_db?user=root&password=");
    PreparedStatement dotaz = spojeni.prepareStatement("SELECT * FROM slovo");
    ResultSet vysledky = dotaz.executeQuery();) {

} catch (SQLException ex) {
    System.out.println("Chyba při komunikaci s databází");
}

Pokud se něco pokazí, informujeme o tom uživatele chybovou hláškou. Při ladění blok catch zakomentujte, abyste mohli na chyby reagovat a kód opravit.

Výpis výsledků

V proměnné vysledky máme již načtená slovíčka z databáze. Zbývá je jen vypsat. ResultSet na sobě obsahuje metodu next(). Ta přesune aktuální pozici v kolekci na další prvek nebo vrátí false v případě, že jsme na konci výsledků. Next je nutné zavolat minimálně jednou, pokud chceme z výsledků něco číst.

K samotnému čtení hodnoty na aktuálním řádku výsledků slouží metody začínající get. Nalezneme zde getInt(), getString(), getDate() a další. Metodám můžeme dát jako parametr buď název sloupce nebo jeho číselný index. U číselného indexu pozor, první sloupec má index 1.

while (vysledky.next()) {
    int id = vysledky.getInt(1);
    String cesky = vysledky.getString("cesky");
    String anglicky = vysledky.getString("anglicky");
    System.out.println("Id: " + id + ", česky: " + cesky + ", anglicky: " + anglicky);
}

Kódem výše iterujeme nad výsledky, získáváme jejich parametry a ty poté vypisujeme do konzole. Ukažme si ještě kompletní kód aplikace:

try (Connection spojeni = DriverManager.getConnection("jdbc:mysql://localhost/slovnicek_db?user=root&password=a");
    PreparedStatement dotaz = spojeni.prepareStatement("SELECT * FROM slovo");
    ResultSet vysledky = dotaz.executeQuery();) {

    while (vysledky.next()) {
        int id = vysledky.getInt(1);
        String cesky = vysledky.getString("cesky");
        String anglicky = vysledky.getString("anglicky");
        System.out.println("Id: " + id + ", česky: " + cesky + ", anglicky: " + anglicky);
    }

} catch (SQLException ex) {
    System.out.println("Chyba při komunikaci s databází");
}

Můžete si zkusit, že aplikace opravdu vypíše všechna slovíčka z databáze:

Čtení z MySQL databáze pomocí JDBC v Javě

Předávání parametrů

Doveďme aplikaci opravdu do podoby slovníčku. Uživatele necháme zadat slovíčko v angličtině a to mu následně přeložíme do češtiny.

SQL injekce

SQL dotaz by měl nyní vybrat jen určitý řádek, je do něj tedy třeba dodat podmínky. Toho docílíme pomocí klauzule WHERE. Naivně bychom mohli vložit slovíčko od uživatele přímo do těla SQL dotazu:

// Tento kód je nebezpečný
Scanner scanner = new Scanner(System.in, "Windows-1250");
System.out.println("Zadej anglické slovíčko k překladu:");
String anglicky = scanner.nextLine();
try (Connection spojeni = DriverManager.getConnection("jdbc:mysql://localhost/slovnicek_db?user=root&password=");
    PreparedStatement dotaz = spojeni.prepareStatement("SELECT cesky FROM slovo WHERE anglicky=\"" + anglicky + "\"");
    ResultSet vysledky = dotaz.executeQuery();) {

    vysledky.next();
    String cesky = vysledky.getString("cesky");
    System.out.println("Překlad " + anglicky + ": " + cesky);
} catch (SQLException ex) {
    System.out.println("Chyba při komunikaci s databází");
}

Výsledek:

MySQL slovníček v Javě

Kód se příliš nezměnil. V SQL dotazu již nevybíráme všechny sloupce, ale pouze sloupec cesky. Kromě toho nám zde přibyla podmínka WHERE. Výsledky již nenačítáme ve while cyklu, jelikož nás zajímá pouze jeden.

Ačkoli se zdá, že aplikace funguje perfektně, opak je pravdou. Cokoli uživatel zadá se vloží přímo do SQL dotazu. Co se stane, když zadá např. následující řetězec?

"; DROP TABLE slovo --

Na databázi se spustí příkaz k vymazání tabulky a máme po datech. To je ještě ten lepší případ, šikovnější uživatel by nám přes slovníček mohl třeba vytahat hesla uživatelů z jiné tabulky. A to už by byl problém. Věřte nebo ne, ale uživatelé takové vstupy opravdu zadávají a vaše aplikace se jim musí bránit. Této technice útoku se říká SQL injection, protože se vkládá cizí SQL kód do našeho dotazu.

Předávání parametrů

Celý problém je samozřejmě v tom, že vkládáme vstup od uživatele přímo do SQL dotazu. Jelikož nikdy nemůžeme vědět, jestli se v nějaké proměnné může vyskytnout něco, co uživatel zadal, upravme náš problém na: vložení kterékoli proměnné do SQL dotazu je velké bezpečnostní riziko. V minulosti se proměnné ošetřovaly speciální funkcí, která tzv. zescapovala nebezpečné znaky (zejména uvozovky). Nejbezpečnější je ovšem používat tzv. prepared statements.

Prepared statement je dotaz, který obsahuje místo parametrů zástupné znaky, nejčastěji otazníky. Samotné hodnoty se do dotazu dosadí odděleně a sama databáze se postará o jejich bezpečné vložení do dotazu.

Přepišme naší aplikaci tak, aby používala parametrizované dotazy:

Scanner scanner = new Scanner(System.in, "Windows-1250");
System.out.println("Zadej anglické slovíčko k překladu:");
String anglicky = scanner.nextLine();
try (Connection spojeni = DriverManager.getConnection("jdbc:mysql://localhost/slovnicek_db?user=root&password=");
    PreparedStatement dotaz = spojeni.prepareStatement("SELECT cesky FROM slovo WHERE anglicky=?");) {
    dotaz.setString(1, anglicky);
    try (ResultSet vysledky = dotaz.executeQuery()) {
        vysledky.next();
        String cesky = vysledky.getString("cesky");
        System.out.println("Překlad " + anglicky + ": " + cesky);
    }
} catch (SQLException ex) {
    System.out.println("Chyba při komunikaci s databází");
}

Všimněte si otazníku v dotazu a volání metody setString(), která nastaví první parametr v dotazu na daný řetězec. Samozřejmě zde nalezneme i metody pro další datové typy. Naše aplikace je nyní bezpečná.

Příště, Databáze v Java JDBC - INSERT, UPDATE, DELETE a COUNT, si ukážeme jak záznamy v databázi editovat. Dnešní projekt je se zdrojovým kódem jako vždy ke stažení níže.


 

Stáhnout

Staženo 544x (18.28 kB)
Aplikace je včetně zdrojových kódů v jazyce java

 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
13 hlasů
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor sítě se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Předchozí článek
Návrh MySQL databáze v NetBeans IDE
Všechny články v sekci
Databáze v Javě - JDBC
Miniatura
Následující článek
Databáze v Java JDBC - INSERT, UPDATE, DELETE a COUNT
Aktivity (2)

 

 

Komentáře
Zobrazit starší komentáře (12)

Avatar
Petr Štechmüller
Překladatel
Avatar
Petr Štechmüller:8.10.2019 18:41

Ahoj, dej si do té výjimky výpis chyby

ex.printStackTrace();

To to to řekne, kde přesně je chyba ;-)

Odpovědět
8.10.2019 18:41
Pokud spolu kód a komentář nekorespondují, budou patrně oba chybné
Avatar
Robert Michalovič:9.10.2019 6:25

Důležitou roli hraje taky i kompatibilita mezi verze connectoru(stažený *.jar) a verzí databáze a verzí Javy. Podle mě je problém v JDK13. Dost pochybuji, že je podporována. Používej 8čku a stáhni si správný connector JDBC.
viz. tabulka

 
Odpovědět
9.10.2019 6:25
Avatar
Odpovídá na Robert Michalovič
Filip Něnička:9.10.2019 19:05

Ahoj Roberte.
Díky za radu, pomohlo to :)

Filip

 
Odpovědět
9.10.2019 19:05
Avatar
Filip Něnička:13.10.2019 16:43

Ahoj,
měl bych ještě jeden dotaz. Snažím se změnit název tabulky co mám v databázi. Nemohu příjít na to, proč mi to nefunguje přes setName. Nevíte někdo kde tam je chyba?

Díky

Filip

String nazevVozidla = "fdadfa";
try (Connection spojeni = DriverManager­.getConnection("jdbc:mys­ql://localhos­t/drivvo?user=ro­ot&password=");
PreparedStatement dotaz = spojeni.prepa­reStatement("RE­NAME TABLE tabulka to ?");) {
dotaz.setString(1, nazevVozidla);
int radku = dotaz.execute­Update();
System.out.prin­tln(radku);
} catch (SQLException ex) {
ex.printStackTra­ce();
System.out.prin­tln("Chyba při komunikaci s databází");

 
Odpovědět
13.10.2019 16:43
Avatar
Petr Štechmüller
Překladatel
Avatar
Odpovídá na Filip Něnička
Petr Štechmüller:13.10.2019 17:01

Ahoj, pro vložení kódu tu máme tlačítko.

Můžu se zeptat, odkud jsi získal ten dotaz na přejmenování tabulky? :-) Já jsem našel něco jiného:

ALTER TABLE table_name
  RENAME TO new_table_name
Odpovědět
13.10.2019 17:01
Pokud spolu kód a komentář nekorespondují, budou patrně oba chybné
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Odpovídá na Petr Štechmüller
Filip Něnička:13.10.2019 18:05

Ahoj,
našel jsem to myslím na jednom blogu. Pokud zde můžu vkládat odkazy tak bych ho sem vložil. Ono to funguje pokud tam místo otazníku zadám jméno. Jde o ten otázník a setString. To nefunguje. To je ten problém :-/

 
Odpovědět
13.10.2019 18:05
Avatar
Petr Štechmüller
Překladatel
Avatar
Odpovídá na Filip Něnička
Petr Štechmüller:13.10.2019 19:35

Co přesně znamená nefunguje? Je to dost obecný popis problému, se kterým ti nemůžu poradit. Koukal jsem, že tam máš při výjimce výpis ze stack trace, tak ho sem hoď

Odkaz sem samozřejmě hodit můžeš ;-)

Odpovědět
13.10.2019 19:35
Pokud spolu kód a komentář nekorespondují, budou patrně oba chybné
Avatar
Filip Něnička:14.10.2019 18:52

Ahoj.
Díky za trpělivost :-)

Háže mi to tuto chybu"
java.sql.SQLSyn­taxErrorExcep­tion: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near fdadfa at line 1

A tady je ten blog :-)

"":https://blog.marceloaltmann.com/…as-no-mysql/

 
Odpovědět
14.10.2019 18:52
Avatar
Petr Štechmüller
Překladatel
Avatar
Odpovídá na Filip Něnička
Petr Štechmüller:14.10.2019 19:19

Tak mám pro tebe novniku: to co chceš, nelze aplikovat. Metody setString() + další takové slouží pouze pro nastavování hodnot do sloupečků.

Zde je o tom delší povídání.

You can't. You need to contruct the sql with string concatenation/pla­ceholder with String.format. prepared statement is for the column values not for table name.

Odpovědět
14.10.2019 19:19
Pokud spolu kód a komentář nekorespondují, budou patrně oba chybné
Avatar
Ondrej Mejzlik:3.11.2019 18:51

Ahoj, skoro celý den jsem se s trápil, prvně aby NetBeans viděl a mohl zasahovat do databáze MySQL a potom, aby se k databázi připojila třída. Pořád mi to házelo chyby, jako že není definovaný "server time zone" nebo naopak, že jsou víc jak jeden... Nakonec jsem to nějak rozchodil, tak to třeba někomu pomůže a nebo mi naopak napíšete, že to jde i jinak :-)

Databázi se mi povedlo připojit, až když jsem v sekci Services / Databases / Drivers / MySQL(Connector/j driver) do JDBC ULR doplnil:

jdbc:mysql://lo­calhost:3306/mys­ql?zeroDateTi­meBehavior=con­vertToNull

V rámci samotné třídy mi to začalo načítat hodnoty z databáze, až když jsem modifikoval řetězen na:

Connection spojeni = DriverManager­.getConnection("jdbc:mys­ql://localhos­t/slovnicek_db?u­seJDBCComplian­tTimezoneShif­t=true&useLega­cyDatetimeCode=fal­se&serverTime­zone=UTC&user=ro­ot&password=HES­LO");

Tak třeba to někomu pomůže :-)

Editováno 3.11.2019 18:52
 
Odpovědět
3.11.2019 18:51
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 22. Zobrazit vše