Techniki Zaawansowane
8 godz. 35 min · Java · Full-stack i Programowanie
Rafał SolarskiW poczatkowych lekcjach przyjrzymy sie pokladowi, na jakim bedziemy uruchamiali nasze programy. Wykorzystamy VisualVM do podgladania parametrów JVM. Zobaczymy równiez jakie zasoby sa tam dostepne oraz dowiesz sie jak monitorowac to, czy dobrze z nich korzystamy. Opowiemy Ci tez miedzy innymi o tym, jak zrobic heap i thread dump, oraz jak wlaczyc logi GC.
Kolejne lekcje kursu zostaly poswiecone wyrazeniom regularnym. Wyrazenia regularne mozemy wykorzystac nie tylko do walidacji danych wprowadzania przez uzytkownika, ale równiez do dzielenia tekstu oraz wyluskiwania wystapien wzorców w duzym tekscie. W tym rozdziale dowiemy sie miedzy innymi tego, jak mozemy wykorzystac ten potencjal z poziomu Javy.
Dzieki typom generycznym jestesmy w stanie osiagnac bezpieczenstwo typów w trakcie kompilacji przy zachowaniu elastycznosci pisanego kodu. W nastepnym rozdziale kursu przyjrzymy sie temu, jak mozemy je wykorzystac, oraz w jaki sposób czasami nalezy z tego bezpieczenstwa zrezygnowac.
Interfejsy funkcyjne to sposób na przeniesienie odrobiny swiata programowania funkcyjnego do Javy. Gdy polaczymy je z wyrazeniami lambda oraz API strumieni pozwola nam pisac te sama logike w duzo bardziej przejrzysty sposób.
Kolekcje, czyli listy, mapy, zbiory i kolejki to jedne z najczesciej wykorzystywanych klas w codziennej pracy. To jak dobrze poznamy ich mozliwosci ma ogromny wplyw na to, w jaki sposób bedziemy podchodzili do rozwiazywania wyzwan na naszej drodze.
System plików to zasób bez którego ciezko sobie poradzic. Pozwalaja nam na dostarczanie konfiguracji, danych wejsciowych i wyjsciowych, a przez to równiez integrowanie ze soba calych systemów.W tym rozdziale nauczymy sie korzystac z tych dobrodziejstw w bezpieczny sposób wykorzystujac m.in. IO Streams.
Trzymanie daty w String to niekoniecznie najwygodniejszy sposób. Juz od pewnego czasu Java dysponuje bardzo wygodnym Date&Time API - zobaczymy, co mozemy w nim znalezc.
Wielowatkowosc nie jest prosta... i dlugo tak jeszcze pozostanie. Nawet jesli korzystamy z frameworków, które próbuja ja przed nami ukryc, to ciagle musimy byc swiadomi problemów jakie sie z nia wiaza. W tym rozdziale poznamy glówne problemy na jakie mozna natrafic programujac wielowatkowo w Javie. Poznamy równiez klasy, które zdecydowanie pomoga nam zapanowac nad ta zlozonoscia.
JDBC to najbardziej podstawowy sposób laczenia sie z baza SQL z poziomu Javy. W wielu systemach/aplikacjach stosuje sie rozwiazania ORM takie jak JPA/Hibernate. Mimo wszystko warto wiedziec jak to pod spodem dziala oraz umiec poradzic sobie w aplikacjach, gdzie wybrano bardziej "lekkie" podejscie niz Hibernate.
Na koniec kursu wykorzystamy zdobyta wiedze, aby stworzyc prosta aplikacje pozwalajaca na przechowywanie danych o wydatkach. Po stworzeniu coreu aplikacji, mozesz spróbowac uzupelnic go za pomoca GUI.
Ten kurs stworzony zostal przede wszystkim z mysla o osobach, które juz poznaly podstawy jezyka, takie jak zmienne, mechanizmy kontroli wykonania, klasy, typy generyczne.Jezeli chcesz poczuc sie swobodnie nie tylko jesli chodzi o mechanizmy jezyka, ale równiez pod wzgledem znajomosci standardowej biblioteki Javy - to kurs w sam raz dla Ciebie. Materialy beda przydatne dla studentów, którzy znaja juz podstawowa skladnie Javy; programistów Javy chcacych poszerzyc lub uporzadkowac swoja wiedze; programistów innych jezyków, którzy chca poznac inny stack technologiczny.
Okej mamy już znasz interfejs teraz warto
stworzyć sobie do niego przynajmniej samą klasę implementującą więc
wybieramy implement interface i tutaj nazwijmy te implementacje może
bez sufiksu imp ale niech to będzie jdbc outcomes repository
będzie to taka implementacja okej i skoro
mamy już wygenerowaną implementację możemy sobie napisać
test tutaj możemy wykorzystać create test a następnie wybrać
junit 5 zostanie dla nas wygenerowany test
który znajduje się w odpowiednim powiedzmy do tej klasy której
tutaj mieliśmy w odpowiednim package'u klasa z testami to jest
po prostu powiedzmy w przypadku junita po prostu klasa w której tworzymy metody
które są poszczególnymi testami możemy jeden test sobie wygenerować
na przykład wykorzystując generate i tutaj wybieramy test method nasze
testy będziemy nazywali zaczynając od test a później
będziemy mówili co testujemy pierwszym testem będzie test add
and get all tu moglibyśmy próbować testować na przykład używając jednej metody
ale my wykorzystamy sobie test do przetestowania jednej i drugiej też mam trochę
pomoże w takim pojedynczym teście powinniśmy stworzyć nasze repozytorium
dostarczając też mu wszystkich potrzebnych rzeczy czyli na przykład jak w przypadku
sqlite miejsca gdzie się ma znajdować baza danych
następnie wywołać operacje czy kilka operacji które
testujemy i sprawdzić wynik tak czy powiedzmy stan
dookoła czyli powiedzmy na przykład to co zwróci get all zgadza
się z tym co add all wrzucił oczywiście tutaj pamiętając o tym że id tak nie
jest nadawany prawda przy add tylko dopiero w get all dostaniemy
obiekty więc nie będziemy ich testowali używając
equals tak tylko może na przykład sobie sprawdzić czy ilość się zgadza albo wyciągając
poszczególne pola na przykład z tych obiektów czy się zgadzają okej nasz test
może wyglądać w ten sposób tutaj tworzymy sobie tymczasowy plik w którym
ma się znajdować nasza baza danych tworzymy nasze repositorium
oraz przekazujemy ten plik ten path następnie tworzymy
dwa outcome'y takie można powiedzieć w miarę losowe tutaj wrzucamy jakiś
jakiś czas dodajemy te outcome'y do naszego repozytorium przez
wywołanie add następnie wywołujemy get all oraz
sprawdzamy na koniec jest ta część w której sprawdzamy czy powiedzmy
te metody dobrze zadziałały po prostu sprawdzamy czy na
liście results jest odpowiednia liczba wpisów tak w jakimś
takim bardziej produkcyjnym teście byśmy chcieli sprawdzić pewnie co się znajduje
na tej liście nam tutaj na razie to wystarczy możemy teraz sprawdzić czy ten test przechodzi
czy się kompiluje on nie powinien przejść ale powinien się skompilować tak
mały czerwony pasek teraz możemy przystąpić do implementacji
ok zacznijmy od implementacji metody add tutaj
zanim dodamy nasz wpis do bazy danych powinniśmy mieć utworzoną
tabelę prawda już nawet na etapie tego konstruktora możemy sprawdzić
czy ta tabela istnieje i w razie potrzeby ją dodać zaczniemy
od napisania prostu ddl która wstawi nam te tabele okej
tak może wyglądać właśnie ddl który będzie wstawiał
tabela po prostu create table i tutaj utwórzmy sobie jeszcze metodę init table
która będzie ją wywoływała okej implementacja init table może wyglądać
w ten sposób tutaj jeszcze musiałem stworzyć url naszego
sqlite tak na razie dodać go jako filtr naszego repozytorium
i tutaj mogę driver menadżerem wyciągnąć to połączenie używając tego url
a następnie tworzymy statement tak i na tym
statemencie wywołujemy naszego ddl tak tutaj
jeszcze musimy oczywiście obsłużyć błąd w init table lub rzucić już
tutaj przemapować na tym etapie ten exeption jako
runtime'owy okej możemy go obsłużyć w ten sposób dobra teraz przystąpmy
do implementacji metody add wiesz już to że będziemy potrzebowali connection możemy
stworzyć nowe albo możemy na przykład tutaj już przy tworzeniu tego
w pewien sposób cache'ować je czyli zapisać się do pewnego field'u w naszym
repozytorium tak i później w kolejnych wywołaniach na przykład add
add all i tak dalej będziemy wykorzystywać to samo połączenie obsłużymy metodę get
connection która sprawdza czy connection które jest polem naszego
repozytorium jest nullem lub jeżeli to connection jest na przykład zamknięte no
to wtedy otwieram nowe connections do tego pola a w przypadku błędu
rzuca wyjątkiem zauważ że tutaj nie zamykam też tego connection jeżeli na przykład
zostaje wyrzucony wyjątek zakładam że zostanie to zamknięte z tego względu
że nasz jdbc outcomes repository będzie
otwierany wewnątrz try with resources jeżeli chcesz żeby mój
właśnie jdbc outcome repository mógł być wywołany w try with resouces
dodać jeszcze tutaj autocloseable jako interfejs
jeden z zaimplementowanych interfejsów mogę też jeżeli chce żeby
wszystkie implementacje outcomes repository implementowały
też autocloseable to mogę też zastanowić się w tej nie dodać ja
chcę aby tylko ta implementacja implementowała również auto
closeable teraz też powinniśmy dodać tutaj w tym catchu
również wywołanie close on tu się rzuca że powiedzmy
chce wrzucić exeption tak ponieważ nie jest on handle'owany
zauważ że tutaj nie musimy wcale rzucać
tym wyjątkiem tak powiedzmy nasza tutaj niby interfejs
autocloseable zakłada że on może rzucać wyjątek tak exception
ale tutaj w implementacji możemy go usunąć tak to jest
jakaś taka właściwość okej i teraz powinniśmy odpowiednio zaimplementować
metodę close okej nasza implementacja close może wyglądać w ten sposób
trzeba też sprawdzić czy nie jest to nullem tak zanim wywołamy close oraz
rzucamy tutaj exeption tak w tym momencie sql exception warto
dodać wywołanie tego close w tym miejscu tak bo też
podczas wywołania naszego konstruktora też możemy dostać exeption na przykład
właśnie w init table i wtedy warto zamknąć to połączenie tutaj
on rzuca wyjątkiem możemy go przerzucić na zewnątrz też
z tego względu że nasz nasze repozytorium będzie wywoływane w try
with resources to od razu możemy obsłużyć tamten exeption albo na przykład przerzucić jakiś
inny tak możemy też założyć że będziemy rzucali runtime'owymi exeptionami
okej zacznijmy od napisania naszego inserta tak on będzie
wyglądał w ten sposób po prostu podajemy 3 kolumny tak oraz
dajemy tutaj parametry które będziemy podawali nie dorzucamy
tutaj id on zostanie nadany automatycznie ponieważ wykorzystujemy primary
key autoincrement tworzymy prepared statement z naszego connection
wykorzystując getconnection podajemy naszego sql oraz
wywołujemy execute update należy jeszcze podać odpowiednie parametry jako pierwszy parametr
podstawiany komentarz jako drugi parametr podstawiamy amount i
tutaj warto pamiętać o tym aby przesunąć przecinek w prawo
tak ponieważ chcemy z tego big decimala tworzyć teraz longa
który posiada same grosze prawda więc tym celu przesuwamy właśnie
przecinek w prawo dwa miejsca tak oraz wywołujemy long
value exact ta metoda poza tym że możemy jeszcze wywołać
long value to ta metoda rzuci nam wyjątkiem jeżeli
nie da się dokładnie przerzucić na longa
tego big decimala czyli jest strata pewnych powiedzmy wartości jeżeli coś jeszcze jest
po tym przecinku tak czyli powiedzmy są jakieś wartości po przesunięciu przecinka
w prawo tak to jeszcze coś jest po przecinku tak dostaniemy exception okej
w przypadku czasu wystarczy że zrobimy tu string już
dostaniemy format który akurat jest obsługiwany przez sqlite
w tym miejscu okej mamy załatwiony add teraz przejdźmy do
get all zacznijmy od dodania naszego selecta to jest po prostu select
gwiazdka from outcomes wywołujemy nasze query i tutaj to
może być zwykły statement może byś też prepared ale nie mamy żadnych parametrów więc nie ma to większego znaczenia
dostajemy result set który jeszcze musimy przemapować teraz na obiekty w
tym celu stworzymy sobie metodę map to objects która dostanie tego result seta
i po prostu w pętli przeleci po jego wierszach oraz zmapuje je
na obiekty w metodzie map to objects po prostu tworzymy listę oraz
w while lecimy po wierszach wyciągają poszczególne parametry a na
koniec stworzymy z tego outcome oraz dodajemy do listy tutaj warto
pamiętać o tym żeby wyciągnąć dodatkowo id którego nie wrzucaliśmy do
tej pory oraz aby odpowiednio obsłużyć wartości które
wyciągamy z kolumn to znaczy jeżeli chodzi o time
to musimy tutaj zadbać o sparsowanie czyli powiedzmy wykorzystujemy
metody parse na offset date time w przypadku naszego amount musimy
pamiętać o tym żeby przesunąć teraz przecinek w lewo prawda tam
przesuwaliśmy o dwa przecinki w prawo tak przy wrzucaniu tu
teraz musimy odwrócić te operacje oraz wykorzystujemy value of tutaj warto
zawartość tej metody wydzielić sobie do oddzielnej metody na
przykład map to object i dzięki temu będziemy mogli użyć tej metody do zmapowania
jednego obiektu w get one prawda czyli tutaj okej i zaimplementowaliśmy
nasze metody teraz możemy sprawdzić czy zrobiliśmy to prawnie poprzez wywołanie naszego
testu tak tutaj możesz sobie odpalić po prostu run zrobić na tym teście jak widzimy jest
zielono przeszedł ten test teraz mamy gwarancję z dokładnością
do tego na ile te nasze asserty czyli to sprawdzenie co tu jest dokładne mamy
większą pewność że zaimplementowaliśmy to prawidłowo dzięki temu
implementując kolejne metody z naszego interfejsu będziemy mieli pewność
że poprzedni metody nie zostały zepsute w wyniku kolejnych implementacji
okej jak widzisz testowanie takie powiedzmy
małe jest dość proste możemy sobie teraz
napisać testy na poszczególne metody proponuję aby robić to pojedynczo
żeby nie pisać od razu wszystkich testów na raz tylko napisać jeden test zaimplementować
go tak żeby przychodził tak czyli powiedzmy dodać do naszej implementacji jdbc
outcomes repository odpowiednią ilość kodu który sprawi że ten test stanie
się zielony tak w ten sposób małymi kroczkami jesteśmy w stanie zaimplementować
wszystkie elementy tak wszystkie metody z tego z tego interfejsu teraz
proponuję abyś zrobił pauzę w odtwarzaniu materiału oraz
spróbował samodzielnie zaimplementować pozostałe metody w
tym repozytorium możesz to robić pisząc testy i sprawiając
aby przychodziły możesz też pisać implementacje tak jak
ci się wydaje ja ci proponuję abyś spróbował wykorzystać właśnie podejście
z testami ponieważ później będzie ci to pozwalało zaoszczędzić czas
tak na początek wypisanie tych assertów i powiedzmy testów będzie trochę
czasochłonne ale później to procentuje okej teraz postaram
się pokazać w jaki sposób ja to zaimplementowałem po pierwsze wydzieliłem
kilka takich outcome'ów powiedzmy wydatków i też dat które
przydają się przy poszczególnych testach tak że
nie musiał ich tworzyć za każdym razem tych samych prawda okej teraz
przelecę szybko po poszczególnych testach nie są one najważniejsze ale pomogą
ci powiedzmy uchwycić ten sposób w jaki możemy testować te metody tutaj
mam test który testuję dodatkowo jeszcze add all
różni się tylko tym że posiada teraz listę prawda ale poza tym jest
taki sam metoda digital możemy bardzo łatwo przetestować wywołując get
all po wywołanej metodzie delete all tak tylko sprawdzamy
czy zwracana ta lista przez get all jest pusta oraz
czy to deleted zawiera tyle wierszy tak ten
tutaj ta zmienna ile dodaliśmy wcześniej rekordów prawda
metoda delete z id tak
była trochę trzeba było tutaj jeszcze wyciągnąć
id tego elementu który dodaliśmy tak
czyli zrobiłem sobie coś takiego z tego względu że nie dostajemy id w outcome
po dodaniu go tak tylko musimy dopiero jeszcze wywołać
get all aby dostać outcome'y które mają te id no to najpierw wywołałem
add all później get all i wziąłem sobie pierwszy element z tej listy i
go usunąłem tak tu jest id usunąłem go i sprawdziłem czy
później to co jest w remaining po wywołaniu go tutaj zgadza
się prawda czy czy czy tutaj już dostanę tylko jeden element tak a miałem
test do get one był analogiczny dodałem jeden outcome później
wyciągnąłem go wykorzystując get all wyciągając z niego id i później wyciągnąłem
go wykorzystując get all i tutaj jeszcze sprawdziłem czy ten zwrócony
element raz czy ten optional coś w nim jest oraz
czy pozostałe kolumny tak czy te fieldy się zgadzają ze sobą przy
odejdzie musiałem się tej trochę pokombinować po pierwsze dodałem te elementy wyciągnąłem id
wyciągnąłem ten jeden element po tym id
tutaj mógłbym od razu go wyciągnąć prawda sprawdziłem czy on istnieje
tutaj są powiedzmy jakieś takie checki drobne później zapdejtowałem
w nim komentarz kwotę oraz czas zapdejtowałem
go używając metody update na naszym repozytorium oraz
wyciągnąłem jeszcze raz wyciągając get one oraz sprawdziłem czy już
za drugim razem kiedy wywołałem get one to czy te pola się zmieniły prawda
tutaj ja mam porównanie zapdejtowanych pól tak
tych docelowych wartości z tym co faktycznie było przy drugim wywołaniu get one
w tym miejscu przy compute sum wrzuciłem trochę rekordów tutaj
między nimi powiedzmy one są w odpowiednich czasach tutaj
tu jest ten ma time jeden ten ma time dwa ten ma time trzy i teraz liczę
wywołując compute sum czy ta suma to się
powiedzmy jest sumą tych dwóch pierwszych tak ten ma time
jeden a ten ma time dwa prawda okej czyli powinien dostać 151
z tego wynika tak no i tutaj sprawdzam czy tyle wyszło i tutaj jest jedna
ważna rzecz nie wykorzystuje assert equals nie porównuje
dwóch decimali tak powiedzmy tutaj bym wrzucił w expected
tutaj tego a później bym wrzucił to co jest w sum ale
wtedy jest jeszcze ryzyko że te bigdecimale trzeba dowiedzieć
się jak działa metoda assert equals i ona wywołuje equalsa po prostu
na tych obiektach i teraz ten equals w przypadku big decimala sprawdza
jeszcze precyzję tak czyli powiedzmy w którym miejscu jest przecinek i tak dalej
a i to może się nie zgadzać to może być ta sama wartość
ale tak jakby te opakowania to big decimale mogą
być trochę inne tak czyli wtedy lepiej jest użyć
compare two i sprawdzić czy to co dostaliśmy jest zerem i
to nam załatwi sprawę że mamy na przykład 51 i
mamy 51 kropka 00
no to wtedy właśnie lepiej compare i tutaj jeszcze dodałem ostatni
test taki powiedzmy case gdzie podaje
wywołuje compute sum a w
środku powiedzmy nic nie ma tak czyli powiedzmy nie ma nie ma nic
w tabelce i dostaje po prostu 0 chciałem sprawdzić po prostu czy w
tym momencie nie dostanę jakiegoś nulla czy czegoś w tym stylu dobra tak wyglądają
nie musiałeś pisać testów ale sprawdź czy na przykład te testy
są podobne do tych twoich możesz je porównać tak przejdźmy
teraz do tego jak wykonałem implementację zacznijmy od metody add all ona
bardzo prosta tutaj wystarczyło wywołać w for eachu metodę add którą wcześniej
już zaimplementowaliśmy jedna rzecz o którą powinniśmy dodatkowo zadbać to
jest zapewnienie transakcyjności tej metody na przykład jeśli ta metoda
add rzuci wyjątkiem w momencie dodawania trzeciego
outcome'u na liście wtedy musimy po prostu zrobić
rollback jest wyciągnięty do oddzielnej metody z tego względu
żeby przemapować na exception jeszcze na koniec pamiętajmy o włączeniu
autocommit prawda tutaj tak żeby mieć ten sam stan na początku był metoda
update zakładała że ten outcome wejściowy w środku ma już id więc
wystarczyło napisać update prawda na taki jak tutaj po
prostu ustawia te kolumny używając tego id jako
warunek w prawda okej tutaj ustawialiśmy wszystkie pola
też należałoby zadbać o odpowiednie ustawienie też
tego przecinka przy przemapowaniu big decimala na longa to można wyciągnąć
do oddzielnej metody żeby to używać powiedzmy w innych
miejscach też no i poza tym nie było żadnych tutaj powiedzmy kruczków
jeszcze należało tylko sprawdzić czy wartość zrzucona z update
jest różna od zera i metodę delete i delete old były bardzo podobne
do siebie tutaj wystarczyło wykorzystać odpowiednio sql
tak i odpowiednio w przypadku delete podstawić id a w
przypadku delete all wystarczyło wywołać całą sql bez
tego warunku metodę get all zaimplementowaliśmy razem w
przypadku get one wystarczyło reużyć tę metodę map
to object wcześniej wywołując po prostu result next prawda
należało też odpowiednio zadbać o zwrócenie pustego
optionala jeżeli po prostu ten next zwróci false w przypadku
metody compute sum mogło być kilka kruczków między
innymi tutaj należy odpowiednio napisać po pierwsze tego selecta
jako że sqlite nie przetrzymujemy dat prawda
nie ma takiego typu to musieliśmy użyć pisząc tego selecta
a funkcją wbudowaną date time przypadkowo ta funkcja nazywa się tak
samo jak nasza kolumna można było ją nazwać trochę lepiej no i wykorzystując
ten date time możemy raz wyciągnąć tą datę z tego stringa
tak i później wsadzić ją między powiedzmy te
between i tutaj to date time też musimy wykorzystać na tych
parametrach które podajemy do tego query prawda te parametry one też
są w stringu i tutaj po prostu jest serializowane offset date time jeśli
dodatkowo założyłeś tak jak ja że te parametry mogą być nullami to
wtedy trzeba odpowiednio zadbać o przekazanie ich do powiedzmy tego
selecta tutaj może być kilka pomysłów ja
wybrałem takie po prostu podstawiłem tam minimalną i maksymalną datę
która jest obsługiwana przez sqlite tutaj z tą metodą
z tym z tą funkcją date time jest trochę powiedzmy problemów może
być z tego względu że jeżeli postawimy za małą bądź za dużą datę
w tym miejscu to dostaniemy z niej nulla dlatego musiałem
po prostu sprawdzić w dokumentacji tutaj we wskazanym linku jakie
są minimalne i maksymalne obsługiwane wartości i są to po prostu takie
wartości tak taki rok jest maksymalny a ten jest minimalny
tak te dane wziąłem po prostu z dokumentacji postawiłem
je jako pewne stałe w moim repozytorium tak i
w zależności od tego czy miałem nulla tutaj czy nie to postawiłem takie wartości oczywiście
też należy zadbać przesunięcie przecinka z wyciągniętej sumy
która jest longiem musimy ją zamienić na big decimala okej tak wygląda
stworzona przeze mnie implementacja tego interfejsu mam nadzieję że implementacja
którą stworzyłem jest w miarę zrozumiała ciekawe też czy twoje
implementacja jest podobna czy nie okej zaimplementowaliśmy
nasze repozytorium w kolejnej lekcji zajmiemy się implementacją klas
odpowiadającej za import i eksport danych do naszej aplikacji z
pliku i do pliku dzięki wielkie do zobaczenia