3. díl - Databáze v Java JDBC - Výpis dat a parametry

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

V minulém dílu našeho seriálu tutoriálů o databázích v Javě 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).

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ě 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 388x (18.28 kB)
Aplikace je včetně zdrojových kódů v jazyce java

 

  Aktivity (1)

Článek pro vás napsal David Čápka
Avatar
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 se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (7 hlasů) :
55555


 



 

 

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

Avatar
Silvinios
Redaktor
Avatar
Silvinios:

Pěkný článek.

Ještě bych dodal, že jméno a heslo se při vytváření spojení nemusí zadávat do do url, pokud se použije metoda DriverManager­.getConnection(Strin­g, String, String):

DriverManager.getConnection("jdbc:mysql://localhost/slovnicek_db", "root", "");
 
Odpovědět  +1 3.1.2014 19:14
Avatar
David Čápka
Tým ITnetwork
Avatar
David Čápka:

Tutoriál byl myšlený hlavně jako příprava pro webovou Javu, kde se spojení opravdu vždy otevírá a zavírá, server si to potom řídí v poolu.

Odpovědět  -1 9.1.2014 18:55
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
firuz.gaffar
Člen
Avatar
firuz.gaffar:

pekny clanek,
ale me to nefunguje, vychazuje vyjimku "Chyba při komunikaci s databází". v cem muze byt problem?

 
Odpovědět 2.5.2014 9:37
Avatar
firuz.gaffar
Člen
Avatar
 
Odpovědět 2.5.2014 9:45
Avatar
vrciak1
Člen
Avatar
vrciak1:

Mam problem pri preklade. Vypisuje sa mi chybova hlaska

Sql Exception: No suitable driver found for jdbc:sqlserver://localhost:1433;databaseName=slovnicek;user=sa;password=123;

Moj kod:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;

public class Skuska2 {

    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        System.out.println("Zadaj slovicko, ktore chces prelozit: ");
        String slovicko = sc.nextLine();

        try ( Connection con = DriverManager.getConnection("jdbc:sqlserver://localhost:1433;"
                    + "databaseName=slovnicek;user=sa;password=123;");
                PreparedStatement dotaz = con.prepareStatement("SELECT cesky "
                        + "FROM slovo WHERE anglicky");){
            dotaz.setString(1, slovicko);
            try (ResultSet vysledok = dotaz.executeQuery()){
                vysledok.next();
                String cesky = vysledok.getString("cesky");
                System.out.println("Preklad: " + slovicko + ": " + cesky);
            }
        } catch (SQLException sqle){
            System.out.println("Sql Exception: " + sqle.getMessage());
        }
    }

}

Kde mam chybu?

Editováno 13.5.2014 14:58
 
Odpovědět 13.5.2014 14:56
Avatar
vrciak1
Člen
Avatar
Odpovídá na vrciak1
vrciak1:

Podotykam, ze som to skusal aj s otaznikom. Mozno je to preto, lebo databazu mam vytvorenu v sql serveri a je tam iny dotaz ako v mysql, ale malo by to byt rovnake.

 
Odpovědět 13.5.2014 15:02
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na vrciak1
David Čápka:

Tak zaprvé si přečti tu chybovou hlášku. Dost jasně ti říká, že nemá driver pro databázi sqlserver. Taková databáze totiž není. Zadruhé si přečti tu část článku, kde se připojuje k databázi, protože máš ten řetězec špatně.

EDIT: Koukám, že sqlserver označuje databázi MS SQL Server, potom si stahni příslušný ovladač ze stránek výrobce.

Editováno 13.5.2014 21:28
Odpovědět 13.5.2014 21:00
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
vrciak1
Člen
Avatar
Odpovídá na David Čápka
vrciak1:

Vdaka. Uz som to rozbehal. Zabudol som nakopirovat jdbc do kniznice.

 
Odpovědět 14.5.2014 13:43
Avatar
Taskkill
Redaktor
Avatar
Taskkill:

Ahoj Davide,
mám otázku na hlubší fungování těhle příkazů, jak přesně se to za sebou chronologicky spouští? K zabezpečené verzi:
zeptá se mne na slovo, nascanuje, uloží do anglicky, v bloku try se pokusí připojit k databázi to teda dělá hned jak je to napsaný, a teď nerozumím jedné věci, ono to už teď volá "dotaz"? protože jestli jo k čemu by to bylo když ho až o řádek níže měníme...vyvozuju že ještě "dotaz" nezavolá...tipl bych, že všechno se to spouští až v druhém bloku try ale nevím přesně jak a hlavně mě nedává smysl jak přinutíš javu aby vlezla zrovna na tu správnou pozici ...myslel jsem že metodu .next() můžeš volat jen v cyklu...kdybys mi to osvětlil byl bych vděčnej.

 
Odpovědět  -1 25.6.2014 8:40
Avatar
Mára Toner
Člen
Avatar
Odpovídá na Taskkill
Mára Toner:

Sice nejsem David, ale pokusím se na vše odpovědět, aby to pochopili i ostatní, protože Tvé dotazy nejsou od věci.

Nejprve vysvětlím, jak přesně funguje ta bezpečnost - jak třída PreparedStatement zaručí, že nedojde k SQL Injection. Zkrátka - nejprve probíhají přípravy. Připravíme si náš dotaz, uvedeme jeho tvar (String) a poté pomocí metody setString a identifikátoru parametru uvedeme, čím se daný parametr nahradí.

A teď - když zavoláme metodu executeQuery, dotaz se odešle do databázového stroje - ale ve tvaru s tím otazníkem, ještě tam vůbec není uživatelský parametr. Databázový stroj ví, co to znamená - o to se postará třída PreparedStatement, že odešle vše, jak má být. Databázový stroj si tedy provede všechny vnitřní záležitosti, dotaz zkompiluje, uloží do mezipaměti a čeká na parametry. Poté, co je obdrží, nahradí "otazník" tím parametrem samotným - avšak už se nic nekompiluje, čili dotaz samotný (ten původní, náš dotaz) je zkompilovaný a je to tedy funkční kód, který se v databázi provede, kdežto parametr je ve tvaru čistých dat, která se do DB vloží - a je s nimi tedy i tak manipulováno.
Bezpečnost je tedy zajištěna na té nejnižší úrovni. Je to podle mě daleko lepší způsob, než escape sekvence, které i tak kompilují potencionálně nebezpečný kód.

K odeslání dotazu na SQL server dojde po zavolání metody executeQuery - vše předtím jsou pouze naše, lokální přípravy. Vytvoříme tedy dotaz, určíme jeho parametry - a AŽ POTÉ dotaz odešleme.
Druhý blok try je také blok "Try with resources", který se postará o správné zakončení po jeho vykonání. V těle bloku již máme výsledná data k dispozici v KOLEKCI ResultSet.
Nad touto kolekcí tedy můžeme zavolat metodu next.Tuto metodu nevoláme v cyklu, protože díky tvaru dotazu a použití operátoru WHERE víme, že nám SQL dotaz vrátil pouze "1 řádek" výsledků. Předpokládáme totiž, že každé slovo bude v databázi unikátní a bude mít uložen pouze jeden překlad. Samozřejmě, v reálné databázi by mohlo být nalezeno více významů, proto by se to muselo vhodně ošetřit (třeba cyklem).

Je to sice taková malá slohovka, ale číst se to doufám dá. Ve skutečnosti se tam nic moc složitého neděje a je to jednoduché k pochopení, ale chápu, že zpočátku se v tom člověk může lehce ztratit...

Hodně štěstí!

Odpovědět  +2 5. července 15:22
If at first you don't succeed; call it version 1.0
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 11. Zobrazit vše