#11 — korzystamy z bazy danych

By 6 November 2014 October 13th, 2015 Kurs Javy

W dzisiejszej lekcji zajmiemy się po raz pier­wszy baza­mi danych — na początku trochę teorii, a następ­nie uży­je­my bazy danych do prze­chowywa­nia infor­ma­cji o kotach w naszym systemie :)

Tutaj chcę zwró­cić uwagę na pewną rzecz — zmieni­a­jąc naszą klasę DAO z ArrayListy na bazę danych nie będziemy mody­fikować żad­nej innej częś­ci naszej aplikacji! To tzw. abstrakc­ja z ele­men­ta­mi her­me­tyza­cji (enkap­su­lacji) — jed­na z fun­da­men­tal­nych zasad pro­gramowa­nia obiek­towego. Szerzej ten tem­at omówimy sobie w przyszłoś­ci, oczy­wiś­cie przykład którym się posługu­je­my jest bard­zo uproszc­zony, ale co do zasady o to właśnie chodzi w pro­jek­towa­niu opro­gramowa­nia — tworze­niu kom­po­nen­tów, które udostęp­ni­a­ją pewne funkcjon­al­noś­ci w określony sposób, ale szczegóły w jaki sposób to robią są ukryte, dzię­ki czemu w prosty sposób może­my pod­mieni­ać pewne ele­men­ty mając pewność, że nie zmieni to sposobu w jaki dzi­ała aplikacja.

Zas­trzeże­nie! Sposób korzys­ta­nia z bazy danych, który tutaj odpisu­je­my, jest już prak­ty­cznie nieuży­wany w nowych pro­jek­tach z uwa­gi na dostęp­ność lep­szych, wygod­niejszych narzędzi. Niem­niej częs­to w pra­cy zawodowej zdarza się, że pracu­je­my z kodem twor­zonym wcześniej i z tego powodu nie korzys­ta­jącego z najnowszych tech­nologii. W określonych przy­pad­kach opisane pode­jś­cie także jest prefer­owane (np. w sytu­ac­jach gdzie kluc­zowa jest wyda­jność lub wyko­rzys­tu­je­my bard­zo specy­ficzne ele­men­ty naszej bazy danych). To jeden z powodów, dla których lekc­ja ta znalazła się w tym kur­sie. Dru­gi to pokazanie, w jakim kierunku dąży rozwój Javy i pow­iązanych stan­dard­ów (kole­jne dwie lekc­je będą doty­czyły tego samego zagad­nienia, będziemy zmieniali naszą klasę DAO korzys­ta­jąc z różnych narzędzi. Zaczynamy?

Lekcja

Relacyjne bazy danych — teoria

Na wstępie należy zaz­naczyć, że nie będziemy zaj­mować się teorią baz danych od strony matem­aty­cznej (zain­tere­sowanych zachę­cam do zapoz­na­nia się z artykułem na wikipedii lub doku­mentem “A Rela­tion­al Mod­el of Data for Large Shared Data Banks” autorstwa Edgara Cod­da), ale spo­jrzymy na to, co to oznacza dla nas od strony prak­ty­cznej. Nie będziemy się też sku­pi­ać na pro­jek­towa­niu baz danych, posta­ci­ach nor­mal­nych itp — to omówimy szerzej w uzu­peł­ni­a­ją­cym wpisie w przyszłoś­ci. To, czemu ma służyć ta lekc­ja to zapoz­nanie się z pod­stawa­mi baz danych, aby rozu­mieć co robimy w kodzie i być w stanie samodziel­nie szukać infor­ma­cji i posz­erzać wiedzę według potrzeb.

Tabele

Tabele to pod­sta­wowy sposób orga­ni­za­cji danych w rela­cyjnych bazach danych (moż­na też spotkac się z określe­niem RDBMS — rela­tion­al data­base man­age­ment sys­tem). Tabele wyglą­da­ją podob­nie jak w rzeczy­wis­toś­ci — mają kolum­ny oraz wier­sze, gdzie kolum­ny są określone przez struk­turę tabeli, a poszczególne wier­sze zaw­ier­a­ją dane (dzi­ała to trochę jak np. w Excelu, z tą różnicą, że kolum­ny mają też nazwy). Kolum­ny określa­ją też, jakie dane moż­na w nich prze­chowywać (np. licz­ba, dane tek­stowe o określonej dłu­goś­ci itp).

Wier­sze tabeli są też określane krotka­mi lub enc­ja­mi (nie jest to do koń­ca praw­da, ponieważ określe­nia te są bardziej pojemne i w bazach danych obe­j­mu­ją także pow­iązane reko­rdy i infor­ma­c­je; nie moż­na więc powiedzieć, że wier­sz w tabeli to krot­ka czy enc­ja, ale nie są to poję­cia odd­alone — intu­icyjnie, słysząc ‘enc­ja’ czy ‘krot­ka’ moż­na więc mieć na myśli właśnie wier­sz tabeli).

Tabele mają określoną struk­turę, tzn kolum­ny są z góry określone (choć oczy­wiś­cie moż­na zmienić to w trak­cie dzi­ała­nia aplikacji, ale jest to zmi­ana struk­tu­ry tabeli i wyni­ka ona jedynie z oper­acji na tabeli, poprzez manip­u­lację dany­mi nie da się zmienić struk­tu­ry tabeli / deklaracji kol­umn) i defini­u­ją one, jaki typ danych może­my prze­chowywać w danej kolum­nie oraz jaki jest rozmi­ar tej kolum­ny (tzn. jaka jest pre­cyz­ja prze­chowywanych liczb, ich maksymalne/minimalne wartoś­ci lub maksy­mal­na dłu­gość ciągu znaków).

Tabele są właśnie tą relacją, od której nazwę wzięły rela­cyjne bazy danych. Jest to nieco niein­tu­icyjne w jezyku pol­skim, ponieważ o relac­jach mówimy także w kon­tekś­cie zależnoś­ci między tabela­mi (omówimy je za chwilę). Się­ga­jąc jed­nak do języ­ka ang­iel­skiego jest jed­nak różni­ca (Matem­aty­czne relac­je — ‘rela­tion’ — są tym, o co chodzi w nazwie ‘rela­tion­al data­base’; zależnoś­ci pomiędzy tabela­mi określane sa ter­minem rela­tion­ship; dyskusję na tem­at rózni­cy w znacze­niu tych pojęć moż­na znaleźć np. pod adresem http://english.stackexchange.com/a/15211) — warto te różnice zapamię­tać i mieć je na uwadze czy­ta­jąc doku­men­tac­je związaną z baza­mi danych lub komu­niku­jąc się z inny­mi. Relacją (matem­aty­czną) nazy­wamy więc tabelę, rozu­mi­aną jako jej schemat (nazwę, kolum­ny) oraz dane razem.

Relacje pomiędzy tabelami (klucze obce)

Ponown­ie, w tym przy­pad­ku powiemy sobie tylko czym są relac­je pomiędzy tabela­mi ale na ten moment nie będziemy zgłębi­ać ich szczegółów. Złączenia tabel w języku SQL to obsz­erny tem­at sam w sobie, który moż­na samodziel­nie zgłębi­ać mając pod­sta­wową zna­jo­mość SQL, a w codzi­en­nej pra­cy współczes­nego pro­gramisty nie jest on wykony­wany zbyt często.

Bard­zo częs­to wys­tepu­je sytu­ac­ja, że jeden obiekt jest pow­iązany z innym za pomocą jakichś zależnoś­ci (np. w aplikacji która służyła­by do zarządza­nia pro­jek­ta­mi w fir­mie, ist­nieje zależność typu jeden-do-wielu (o typach zależnosci powiemy sobie za chwilę): jed­na fir­ma ma wiele pro­jek­tów, ale jeden pro­jekt jest przyp­isany tylko do jed­nej firmy (oczy­wiś­cie to pewne uproszcze­nie rzeczy­wis­toś­ci, ale załóżmy, że tak jest). W języku pol­skim takie pow­iązanie między tabela­mi także określamy mianem relacji.

W tym miejs­cu należy jeszcze krótko wspom­nieć o pro­ce­sie nor­mal­iza­cji i posta­ci­ach nor­mal­nych (nor­mal form, NF). Nor­mal­iza­c­ja to for­mal­ny pro­ces, który ma zapewnić, że w bazie danych nie ma niepotrzeb­nych powtórzeń, odpowied­nie tabele są praw­idłowo połac­zone i mod­el jest spójny i inte­gral­ny. O samym pro­ce­sie nor­mal­iza­cji i jak go wykon­ać moż­na poczy­tać w artykule na Wikipedii, w uproszcze­niu pole­ga on na doprowadze­niu mod­elu do założonej postaci nor­mal­nej (najczęś­ciej czwartej) poprzez iter­a­cyjne doprowadze­nie najpierw do pier­wszej postaci nor­mal­nej, drugiej itp. Każ­da postać nor­mal­na ma pewne założe­nia, które mod­el musi speł­ni­ać, żeby moż­na było o nim powiedzieć, że jest w danej postaci nor­mal­nej. Przykład­owe założe­nia to np, że każ­da kolum­na zaw­iera tylko jed­ną infor­ma­cję (więc imię i nazwisko trzy­mamy w osob­nych kolum­nach) czy też, że infor­ma­c­ja nie jest dup­likowana (w przy­pad­ku np. firmy i pra­cown­ików, dane firmy trzy­mamy w osob­nej tabeli i odnosimy się do nich jedynie za pomocą klucza). Z pro­ce­sem nor­mal­iza­cji wiąże się także denor­mal­iza­c­ja — czyli świadome ode­jś­cie od zasad nor­mal­iza­cji. Cza­sem robi się tak, ponieważ albo moż­na pewne rzeczy założyć (np. że samochód ma jed­nego właś­ci­ciela, albo że właś­ci­ciele samo­chodów mieszka­ją zawsze pod różny­mi adresa­mi) albo jest to ważne z punk­tu widzenia wyda­jnoś­ci zapy­tań. Nato­mi­ast kluc­zowa jest świado­mość, co się robi oraz jak to wpłynie na sys­tem, decyz­ja o denor­mal­iza­cji powin­na być pode­j­mowana tylko przez doświad­c­zonych programistów/projektantów i tylko w wyjątkowych sytuacjach.

Ist­nieją trzy log­iczne typy relacji:
— jeden-do-wielu
— jeden-do-jed­nego — ten typ relacji jest najm­niej intu­icyjny, jego prak­ty­czne zas­tosowanie sprowadza się najczęś­ciej do opty­mal­iza­cji (cza­su zapy­tań lub rozmi­aru danych). Oczy­wiś­cie ma on swo­je uza­sad­nie­nie pod­czas pro­ce­dury nor­mal­iza­cji. Przykła­dem mogą być szczegóły użytkown­i­ka, np. jego pref­er­enc­je czy kom­plet danych (np. w pro­filu por­talu społecznoś­ciowego). Mamy więc użytkown­i­ka oraz szczegóły użytkown­i­ka, jeden reko­rd jest zawsze związany z jed­nym reko­r­dem drugiego typu, ale podzi­ał ma sens (np. z uży­ciem tabeli użytkown­ików dokonu­je­my uwierzytel­nienia, pobieranie za każdym razem wszys­t­kich danych pro­filu mija się z celem i było­by niewyda­jne). Dru­ga sytu­ac­ja kiedy częs­to jest to stosowane to wybiór­cze dane (np. przy dziedz­icze­niu i hier­ar­chi­ach) — np. w szkole, mamy uczniów i nauczy­cieli, obie te role to po pros­tu oso­by. Oso­ba ma imię i nazwisko, nauczy­ciel dodatkowo przed­miot, doświad­cze­nie, stopień i klasę, której jest wychowaw­cą, uczeń z kolei ma rok nau­ki i klasę w której jest. Moż­na trzy­mać wszys­tkie te dane w jed­nej tabeli, moż­na całkiem osob­no w 2, ale najwygod­niej jest trzy­mać część wspól­ną (imię, nazwisko) w jed­nej, szczegóły uczniów w drugiej i szczegóły nauczy­cieli w trze­ciej. Reko­rd oso­by jest więc pow­iązany z zero lub jed­nym reko­r­dem szczegółów ucz­nia (i ana­log­icznie nauczy­ciela), a szczegóły ucz­nia są pow­iązane zawsze z jed­nym reko­r­dem oso­by. To też dobry przykład do zobra­zowa­nia różni­cy pomiędzy encją (krotką) a wier­szem w tabeli (reko­r­dem) — reko­rd obe­j­mu­je jed­ną tabelę. Enc­ja to wszys­tkie te infor­ma­c­je, czyli np. wszys­tkie infor­ma­c­je o uczniu ‘pobrane’ z 2 tabel
— wiele-do-wielu — w prak­tyce wyma­ga to utworzenia dodatkowej tabeli, która jest w relacji jeden-do-wielu z obiema tabela­mi, które są log­icznie połąc­zone relacją wiele-do-wielu. Przykła­dem zas­tosowa­nia może być sys­tem do zarządza­nia plana­mi lekcji, w których mamy zaję­cia (lekc­je) oraz uczniów. Każdy uczeń może uczęszczać na wiele zajęć, ale też każde zaję­cia są uczęszczane przez wielu uczniów.

Dygresja — NoSQL, czym jest i zastosowania

Ostat­nio dość pop­u­larne są tzw. bazy NoSQL, na pewno pracu­jąc w IT w niedługim cza­sie spotkasz się z tym określe­niem lub nawet będziesz mogła uży­wać takiego opro­gramowa­nia w praktyce.

NoSQL to ogól­na nazwa dla wszys­t­kich baz danych, które odchodzą od mod­elu rela­cyjnego — rodza­jów takich baz jest wiele (key-val­ue store, col­umn fam­i­ly itp), dlat­ego ter­min ten nie określa jakiego rodza­ju jest dana baza danych, a raczej jakiego rodza­ju nie jest. W tym kur­sie nie będziemy zaj­mować się baza­mi NoSQL, warto jed­nak wiedzieć, że częs­to ofer­u­ją lep­szą wyda­jność od baz rela­cyjnych oraz bardziej elasty­czny mod­el. Potenc­jal­ny­mi wada­mi tych rozwiązań są nie­s­tandar­d­owe sposo­by odpy­ty­wa­nia ich, elasty­czny mod­el (tak, to zarówno wada jak i zale­ta) oraz mniejsze możli­woś­ci w zakre­sie pow­iązań pomiędzy obiek­ta­mi. Branża cały czas ‘uczy się’ z nich korzys­tać, ale świa­towi gigan­ci nie mogli­by osiągnąć obec­nego rozmi­ary bez ich uży­cia — np. Google uży­wa włas­nego rozwiąza­nia o nazwie Big­Da­ta, pod­czas gdy Ama­zon także korzys­ta (i udostęp­nia w ramach usług AWS) z włas­nego sys­te­mu o nazwie DynamoDB. Kon­cep­cyjnie sys­te­my te bard­zo się różnią, zain­tere­sowanych odsyłam do prac naukowych oraz blue­print­ów na ten tem­at (pier­wsze lin­ki po wpisa­niu tych fraz w google).

Podstawy SQL

Przede wszys­tkim — ten dzi­ał trak­tuj bardziej jako sug­estię, co dalej czy­tać niż jako pełno­prawny kurs. Poniższe infor­ma­c­je to tylko wierz­chołek góry lodowej jeśli chodzi o tem­atykę baz danych i SQL.

W tej częś­ci nie będziemy poruszać kwestii wiąza­nia danych w tabelach. Opiera się ono o te same zapy­ta­nia z dodatkowy­mi słowa­mi kluc­zowy­mi, ale sama teo­ria i kon­sek­wenc­je są dość obsz­ernym tem­atem, który być może poruszymy w przyszłoś­ci w ramach dodatkowych kursów.

Pobieranie danych — SELECT

Pod­sta­wowym rodza­jem zapy­tań są zapy­ta­nia typu SELECT, które pozwala­ją nam pobier­ać dane z tabeli (lub wielu tabel jed­nocześnie). Składa­nia wyglą­da następująco:

SELECT [pola] FROM [tabela] WHERE [warunki]

Przy czym WHERE [warun­ki] nie jest obow­iązkowym ele­mentem. Po kolei:

  • [pola] — to lista kol­umn i wyrażeń, które chce­my pobrać; kolum­ny opisu­je­my po pros­tu wpisu­jąc ich nazwę, może­my też użyć sym­bolu *, który znaczy ‘pobierz wszys­tkie dostęp­ne kolum­ny’; może­my też użyć innych wyrażeń, np. funkcji lub wyrażeń matem­aty­cznych (np. cena_netto+podatek, zakłada­jąc, że cena_netto i podatek to praw­idłowe nazwy kol­umn, lub NOW() , która zwraca aktu­al­ny czas )
  • [tabela] to po pros­tu nazwa tabeli, z której chce­my pobrać dane
  • [warun­ki] to ciąg log­iczny, w którym określamy warun­ki, jakie musi speł­ni­ać każdy wier­sz który chce­my pobrać, np. cena_netto>10 lub cena_netto>10 AND cena_netto<30 — do łączenia wielu warunk­ów uży­wamy słów kluc­zowych AND oraz OR

Wstawianie danych — INSERT

Zapy­tanie INSERT służy do wstaw­ia­nia do tabel nowych reko­rdów. Skład­nia wyglą­da następująco:

INSERT INTO [tabela] ([kolumny]) VALUES ([wartosci kolumn])

gdzie:

  • [tabela] to oczy­wiś­cie określe­nie tabeli, do której chce­my wstaw­ić nowy reko­rd; podob­nie jak w zapy­ta­niu select, wpisu­je­my po pros­tu nazwę tabeli
  • [kolum­ny] — to lista kol­umn, do których chce­my wpisać dane wymieniona po przecinku; jeśli nie wymien­imy tutaj jakiejś kolum­ny, otrzy­ma ona wartość NULL (jeśli baza danych na to pozwala, jeśli nie, zostanie zwró­cony błąd)
  • [wartoś­ci kol­umn] — to wartoś­ci do umieszczenia w poszczegól­nych kolum­nach; ilosć i kole­jnosć musi się zgadzać z tym, co wpisal­iśmy w [kolum­ny], w prze­ci­wnym razie otrzy­mamy błąd; także for­mat danych musi być zgod­ny z for­matem kolum­ny (szczegółowe infor­ma­c­je moż­na znaleźć w doku­men­tacji uży­wanej przez nas bazy danych — np. daty najczęś­ciej wpisu­je­my jako ciąg znaków — w apos­tro­fach — w for­ma­cie RRRR-MM-DD)

Aktualizacja danych — UPDATE

Ten rodzaj zapy­tań jest uży­wany do mody­fikacji ist­nieją­cych już wier­szy. Jego skład­nia przed­staw­ia się następująco:

UPDATE [tabela] SET [nowe wartości] WHERE [warunki]

Tak jak w przy­pad­ku zapy­ta­nia SELECT, WHERE [warun­ki] jest opcjon­alne, ale prak­ty­cznie zawsze stosowane (ponieważ chce­my zmienić wybrany wiersz/wiersze, a nie wszystkie).

Ele­men­ty [tabela] oraz [warun­ki] mają taką samą postać jak w przy­pad­ku zapy­ta­nia SELECT. Z kolei [nowe wartoś­ci] to lista par kolum­na-wyraże­nie (np. kolumna1=wartosc1) odd­zielona od siebie przecinka­mi (jeśli zmieni­amy wiele kol­umn jed­nocześnie). Co ważne, wyraże­nie może odwoły­wać się do innych kolumn.

Przykład: chcąc zak­tu­al­i­zować stawkę podatku dla pro­duk­tów które do tej pory miały podatek 22%, może­my wywołać następu­jące zapytanie:

UPDATE produkty SET vat=0.23, cena_brutto=1.23*cena_netto WHERE vat=0.22

Usuwanie danych — DELETE

To zapy­tanie ma najprost­szą skład­nie, wyglą­da ona następująco:

DELETE FROM [tabela] WHERE [warunki]

Podob­nie jak w powyższych, część WHERE [warun­ki] jest opcjon­al­na. Warun­ki i tabelę określa się iden­ty­cznie jak w pozostałych zapy­ta­ni­ach. To zapy­tanie usunie z tabeli wszys­tkie wier­sze, które speł­ni­a­ją warun­ki (lub wszys­tkie wier­sze, jeśli nie określimy wspom­ni­anych warunków).

CRUD

Powyższe zapy­ta­nia stanow­ią skład­owe CRUD — akro­n­imu od słów Cre­ate, Read, Update, Delete . Te cztery oper­ac­je to pod­sta­wowe oper­ac­je na danych i wszys­tkie odwoła­nia z poziomu aplikacji do bazy danych sprowadza­ją się właśnie do tych czterech elementów.

Wartość NULL w baziach danych

Mówiąc o bazie danych należy też wspom­nieć o wartoś­ci NULL. W pro­gramowa­niu null oznacza ‘nic’ — np. w Javie porów­ni­an­ie tego rodzaju:

Object o1 = null;
Object o2 = null;
if (o1 == o2) {...}

będzie prawdzi­we (tłu­macząc na ‘nasz’ — nic, ma dokład­nie tą samą wartość co nic).
W bazach danych jest inaczej, ponieważ null oznacza nie ‘nic’, a ‘nie wiem’. Najczęś­ciej nie będzie to prob­le­mem ale warto o tym wiedzieć bo może­my się kiedyś spotkać z prob­le­mem tego rodza­ju np. pisząc zapy­tanie do bazy danych.

Kon­sek­wencją takiego rozu­mienia jest sposób dzi­ała­nia, w języku SQL zapytanie:

NULL == NULL zwró­ci NULL. Tak samo zapy­tanie NULL != NULL także zwró­ci NULL. Każde porów­nanie lub uogól­ni­a­jąc oper­ac­ja (tech­nicznie, porów­nanie to też oper­ac­ja, podob­nie jak dodawanie, mnoże­nie itp), w którym jeden z operan­tow to NULL zwró­ci NULL. Dlat­ego niein­tu­icyjny będzie wynik zapytania:

... WHERE tabela.pole = NULL;

które nie zwró­ci żad­nych wartoś­ci (nawet, jeśli są reko­rdy w tabeli tabela, dla których kolum­na o nazwie pole ma wartość NULL). Dzieje się tak, ponieważ wynikiem porów­na­nia ‘czy nie wiem co to to samo co nie wiem co’ jest zawsze ‘nie wiem’, a więc warunek nie jest spełniony. Praw­idłowo zapy­tanie to powin­no wyglądać:

... WHERE ISNULL(tabela.pole);

które moż­na przetłu­maczyć jako ‘czy nie wiesz, co jest w kolum­nie pole’ .

Kwes­t­ia przyzwycza­je­nia, ale ponown­ie, jest to coś, co warto mieć na uwadze pracu­jąc jako programista.

Materiały dodatkowe / dokumentacja

Zadanie

W ramach zada­nia na tą lekcję, przy­go­tuj zapy­ta­nia SQL do tabeli koty, które poz­wolą nam wykony­wać oper­ac­je które mamy w klasie DAO. Zami­ast para­metrów, użyj póki co stałych wartości.

zip Pobierz rozwiązanie tego zadania

#3 konwersja typów, obsługa wyjątków-2

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przy­dat­ną, mamy małą prośbę: pol­ub nasz fan­page. Dzię­ki temu będziesz zawsze na bieżą­co z nowy­mi treś­ci­a­mi na blogu ( i oczy­wiś­cie, z nowy­mi częś­ci­a­mi kur­su Javy). Dzięki!