Späť na blog
Tipy a triky

Java persistence – JPA, Hibernate, ORM

Skillmea
05.02.2019
12 minút čítania
Java persistence – JPA, Hibernate, ORM
JPA je java persistence api špecifikácia. Na to, aby si mohol používať JPA v skutočnej aplikácii, potrebuješ implementáciu JPA. Buď použiješ servery, ktoré už ponúkajú implementáciu JPA, ako napríklad GlassFish, alebo použiješ implementáciu, ktorú ti poskytuje framework Hibernate alebo EclipseLink.
Ak používame JPA štandardy, tak je v budúcnosti úplne jedno, akú implementáciu JPA budeme používať. Pri programovaní budeme používať JPA anotácie, ktoré pochádzajú z balíka javax.persistence. V budúcnosti môžeš nasadiť aplikáciu na GlassFish, ktorý pozná javax.persistence a vie s tým pracovať alebo na Tomcat s použitím Hibernate, ktorý tiež pozná javax.persistence a vie s tým pracovať. 

Čo je Persistence?

Ak vytvoríš hocijaký jednoduchý objekt, čo sa stane? Napríklad objekt Adresa? Vytvorí sa v halde – v pamäti. Objekt môže mať nasetované nejaké dáta – informácie. Ak sa ale stratí referencia v javovskom kóde na tento objekt v halde – tak sa zmaže. 
Ak si chceme uchovať tieto informácie, tak ich môžeme uložiť do databázy a najlepšie, aby po vytiahnutí z databázy mali tieto dáta tú istú formu – teda formu objektu Adresa. 
Tomuto sa hovorí, že persistujeme (uchováme stálosť) objekt do databázy. Akoby tento objekt existoval aj mimo java programu. Tento objekt sa uchová v úložisku a znovu sa vytvorí, ak bude treba. 

Čo je ORM?

Klasické databázy ako Oracle, MySql a podobne sú relačné databázy, ktoré majú dáta uložené vo forme tabuliek. V jave ukladáme dáta vo forme objektov, v databáze vo forme tabuliek. Ale čo majú podobné? V relačnej databáze máme stĺpce, ktoré majú názvy a v riadkoch máme hodnoty. Niečo ako klúč hodnota – to isté platí aj pre objekty v jave – tam máme názov premennej a hodnotu v nej uloženú. 
Tu prichádza pod ruku ORM – teda object relational mapping. Je to niečo ako objektovo relačné mapovanie. My naše java objekty namapujeme na tabuľky relačnej databázy. Aby sme vedeli, že tento field v javovskom objekte patrí do tohto stĺpca.  
Objekty v jave sú medzi sebou prepojené pomocou uloženia referencie na daný objekt. Napríklad človek má field Adresa, kde je uložená referencia na objekt Adresa. 
Relačné tabuľky majú medzi sebou tiež väzby. Buď máme v tabuľke pre človeka stĺpec adresa, kde bude uložený identifikátor adresy a na základe tohto identifikátora nájdeme danú adresu. Alebo existuje špeciálna tabuľka, kde budú dva stĺpce jeden pre identifikátor adresy a druhý pre identifikátor človeka. My potom vieme nájsť, aké adresy má človek nastavené, alebo pre akého človeka je nastavená daná adresa.

Problém s JDBC prístupom – výhoda ORM

V kurze Java pre pokročilých, ak si tento kurz videl, sme si ukazovali prístup k databáze cez JDBC. Čo sme spravili? Otvorili sme konekciu na databázu, napísali sme sql príkaz, ktorý sme následne poslali do databázy na vykonanie. Databáza nám vrátila výsledok vo forme result setu. 
Predstav si, že máš len 5 až 10 tabuliek. Nad každou tabuľkou máš napríklad 4 rôzne sql príkazy – to máme približne 20 – 40 sql príkazov. Ak sa ti stane, že musíš zmeniť databázu – napríklad zmeníš názov stĺpca v tabuľke? Čo musíš spraviť? Musíš prepísať názov tohto stĺpcu na xy miestach – na 20 až 40 miestach – a to sme len v malej aplikácii – čo ak by to bolo na 100 miestach?. 
Bol by v tom neporiadok a mohli by nastať problémy. 
Ak ale použijeme ORM, tak v jave pracujeme s naším kódom, tak ako bežne. Vytvoríme si objekty typu Clovek, nastavíme mu nejaké hodnoty. Ďalej si vytvoríme kolekciu Adries pre daného človeka. Nakoniec v ORM frameworku povieme len persistni mi tento objekt. ORM sa potom postará o všetko uloženie týchto objektov do databázy na základe mapovania, ktoré mu poskytneme. 
Clovek clovek = new Clovek();
   clovek.setMeno("Jaro");
   clovek.setPriezvisko("Beno");
   
   Adresa adresa1 = new Adresa();
   adresa1.setUlica("Nejaka 5");
   adresa1.setPSC("94404");
   
   Adresa adresa2 = new Adresa();
   adresa2 = new Adresa();
   adresa2.setUlica("Nejaka 5");
   adresa2.setPSC("94404");
   
   List<Adresa> adresaList = Arrays.asList(adresa1, adresa2);
   clovek.setAdresaList(adresaList);
   
   orm.persist(clovek);
   
Ak by sme nepoužili ORM, sami by sme museli napísať metódu, ktorá nám otvorí konekciu na databázu, museli by sme napísať INSERT SQL príkaz pre človeka a potom aj pre jeho adresy a museli by sme zabezpečiť, aby sme nastavili všade dáta tam kde majú byť a musíme sa postarať aj o prepojenia medzi týmito dvoma objektami. 
Ak ale použijeme ORM, tak sa nemusíme starať o tento balast kódu, ale sústrediť sa najmä na to, čo predáva a to je business logika aplikácie. 
Nevýhody JDBC prístupu sú teda, že máme príliš veľa SQL príkazov, veľmi veľa kópie kódu, ručne sa musíme postarať o nastavenie dát do správnych stĺpcov. 
Výhodou ORM je, že nemusíme robiť tieto veci z predchádzajúcej vety. ORM nám umožní používať java objekty na reprezentáciu relačnej databázy. ORM sa nám postará aj o prepojenie závislostí. ORM spojí výhody relačnej databázy a objektového modelu v jave plus schová všetku komplexitu SQL príkazov. 

Čo je Hibernate?


Hibernate je ORM – object relational mapping framework, ktorý slúži na mapovanie java objektov na tabuľky relačných databáz. 
Java programátori sú zvyknutí písať kód v objektoch, prečo teda potrebujú ďalší jazyk – sql – na získanie dát z databázy? Hibernate na pozadí sám vytvára sql príkazy nad databázou a preto nemusíme písať sql príkazy my. 
Ak chceme uložiť mapu objektov, napríklad Cloveka, ktorý ma Adresu, alebo aj viac objektov typu Adries, tak nemusíme písať všetky sql príkazy. Stačí, ak zavoláme jednoduchú metódu na uloženie objektu do databázy a hibernate sa postará o zvyšok. 
Hibernate je aj implementácia JPA.

Čo je JPA?

Skratka JPA je Java Persistence API. Čo to znamená? V jednoduchosti povedané – je to štandard. Trošku zložitejšie povedané – je to špecifikácia pre OR mapovanie a je súčasťou Java EE, ale môžeme ju používať aj v Java SE projektoch. 
Niektoré servery poskytujú vlastnú implementáciu JPA a niektoré nie – v tom prípade použijeme napríklad Hibernate implementáciu. 
Predstav si, že celý tvoj kód používa veci z JPA. Teraz je na tebe, kam nasadíš svoju aplikáciu. Ak ju nasadíš na Glassfish nemusíš prerábať svoj kód, ktorý používa JPA – Glassfish ho pozná. Ak svoju aplikáciu nasadíš na Tomcat, tak mu prihodíš Hibernate, ktorý tiež pozná JPA. Potom tvoj kód bude fungovať všade – lebo používa štandardy JPA. 
Je možné aby sme používali len Hibernate – teda by sme nepoužívali nič zo štandardov. Žiadne anotácie z javax.persistence a podobne – to ale neodporúčam. 

Pýtal som sa

Napadlo mi, že by nebolo od veci spýtať sa kolegov developerov, čo si myslia o JPA a Hibernate. Ak by si si chcel prečítať ich názory, nech sa páči – bez cenzúry, citujem: 
Tak toto je náročná téma a navyše zložitá. 
JPA resp. ORM všeobecne (a teda aj Hibernate) sú vždy zložitejšie, než si používatelia (t.j. vývojári) uvedomujú. Výsledkom sú často nenápadné chybičky, lazy load exceptions, ktoré vedú k anti-patternom ako je OSIV (open session in view) alebo k výkonovým problémom (n+1 problem). 
Týchto problémov je typicky o to viacej, o čo zložitejšie je mapovanie – a pritom práve na riešenie zložitého mapovania bolo ORM vymyslené. Aby sme mohli namapovať doménu do DB. Na to, sa často používajú aj “mimojazykové” triky ako reflection na private polia, takže objekty sú implicitne zviazané s ORM riešením, aj keď napr. mapovanie je oddelené do XML namiesto anotácii, čo samo o sebe je tiež nepraktické. 
Okrem toho majú obe hlavne implementácie dosť bugov na to, aby na ne človek narazil, aj keď postupuje v súlade so špecifikáciou – stačí len chcieť trosku viacej a na nejaký bug určite narazíte. Takže potom to je kľučkovanie medzi bugmi a často komplikovaná možnosť vymeniť ORM providera. 
Čo sa mi na ORM páči je lepšie mapovanie typov, možnosť customizovať mapovanie a podobne. Preto používam JPA aj na jednoduché mapovačky namiesto JDBC. 
Navyše s JPA používam rad Querydsl, ktoré je lepšie/intuitívnejšie, než JPA štandardné Criteria API. 
Hibernate používam dlho ale pravdu povediac nikdy som sa veľmi nezamýšľal nad výhodami. Zatiaľ som nemal výraznejší problém, ktorý by som nevedel vyriešiť, prípadne nejako obísť. 
Plusy
- ľahko sa robí mapovanie do DB s anotáciami aj pre začiatočníka bez veľkých znalostí databázy, zároveň ale bez znalosti DB môže byť mapovanie neefektívne 
- je open source, takže ak potrebujem, viem pozrieť zdrojáky ako funguje 
Mínus
- asociácia OneToOne fetch=lazy nefunguje 
Na používaní Hibernate/JPA (celkovo ORM vrstve) sa mi páči
A) Abstrakcia od fyzického dátového modelu. Vývoj nad doménovým/logickým (entitno-relačným dátovým modelom) - bližšie k biznis vrstve. Čiže zjednodušené práca s objektami namiesto tabuliek. 
B) Možnosť využívať rôzne pokročilejšie techniky získavania dát (napr. Spring Data JPA, ale aj zjednodušujúce Hibernate Query by example) 
C) Agnostické od konkrétnej databázovej technológie (Oracle, MySql, ...) 
D) Cachovanie a optimalizácia (napr. lazy loading) 
  
Nevýhody
A) Niekedy náročný (až nemožný) performance tuning. 
B) Pri niektorých technológiách pomalšia krivka učenia. 
C) Aj napriek používaniu JPA/Hibernate, je takmer nevyhnutné, aby developer poznal aj (native) SQL jazyk a jeho použitie. 

Záver

Podarilo sa ti nazrieť do problematiky objektovo relačného prístupu k databáze a pochopil si, čo to znamená. Ak sa však chceš dostať ešte o level ďalej, pripravili sme pre teba samostatný kurz Java persistence – JPA a Hibernate.

Ak chceš ešte viac, tak klikaj:

👍 Viac o mne: http://www.jaroslavbeno.sk/  👍 Kurzy (java, git, maven, bootstrap, Asp .Net,): Learn2Code moje kurzy 👍 Free kurzy: YouTube kanál JaroslavBeno
Skillmea
🥇 Sme jednotka v online vzdelávaní na Slovensku.
Na našom webe nájdeš viac ako 300 rôznych videokurzov z oblastí ako programovanie, tvorba hier, testovanie softwaru, grafika, UX dizajn, online marketing, MS Office a pod. 
Vyber si kurz, ktorý ťa posunie vpred ⏩

Mohlo by ťa zaujímať

Manuálne vs. automatizované testovanie
Tipy a triky
25.01.2019
Skillmea

Manuálne vs. automatizované testovanie

Rád si v detstve všetko rozoberal, špekuloval a škodoradosť hrala v divadle tvojich ranných emócií prím? Si tu správne. Pretože presne to sú hlavné črty testera. Len opatrne s tou škodoradosťou 😉 Manuálne testovanie nie je hanba!Neexistuje nič horšie, ako robiť stále to isté dookola. Našťastie, o tom manuálne testovanie nie je. Ale pekne postupne. Nepoznám krajší pocit v práci (okrem piatkového fajrontu) ako keď ja, človek, ktorý študoval dojivosť kráv a hektárový výnos pšenice, nachytá programátora s nejakou chybou. Manuálne testovanie znamená zobrať novú časť aplikácie, usúdiť či zodpovedá tomu, čo si zákazník želal a popri tom nachytať programátorov. Pri testovaní postupuj rovnako ako pri boji s mafiou. Najskôr ideš po veľkých rybách - chybách, ktoré spravia najviac škody, malé si nevšímaš alebo ich preskočíš, pretože nie je čas. Nech narobíš programátorom dosť roboty. Potom ideš po tých menších, dizajnových vychytávkach, pretože majú rovnakú váhu ako predvčerajšia instastory každej druhej makeup artistky . Vymýšľaš, čo si ešte nevymyslel a ideš po chodníčkoch aplikácie, kadiaľ by sa normálny používateľ nikdy nevydal. Ale Jožo Pročko hovoril 20 rokov dozadu, že nikdy nehovor nikdy. Ako tester to nehovor ani ty. Rozum maká zozačiatku viac ako ruky, a to je fajn. Fajn to byť prestane, keď sa karta obráti a nedajboh, aby si musel dvakrát robiť to isté. Alebo 3-krát. Alebo 4-krát. Alebo furt. Si odsúdený na večnosť regresne testovať celú aplikáciu. Pretože ak sa zmení kód, je potrebné to celé preklikať. A v tento moment mozog vypínaš a pracujú len ruky. Spomínaš si, keď ti ako malému povedali, že ak sa nebudeš učiť, budeš kopať kanále? Toto je to isté, akurát sedíš v bizniscentre s ďalšími korporátnymi kopačmi. Našťastie tú robotu môžeš prenechať počítaču, pretože na rad prichádza... Automatizované testovanie je programovanie!Nechaj zase mozog makať a ruky oddychovať. Automatizované testovanie je o tom, že ty naprogramuješ robota, aby chodil po určitej dráhe, cestičke v aplikácii a on to bude robiť vždy, keď mu prikážeš. Nepredstavuj si robota ako terminátora, ktorý za teba sedí v kancli. A ani takto to nevyzerá:[Image] Ty vidíš, že stránka sa otvorí, ale kliká po nej robot na pozadí, ktorého NIEKTO naprogramoval. A tým niekým si TY. Si programátor so všetkou tou pompou a slávou, píšeš kód napr. v JAVA a vyvíjaš si vlastnú aplikáciu, logiku, ktorá testuje softvér namiesto teba. Zo začiatku je to trošku ťažkopádne ako štartovanie V3S-ky, ale keď tú mašinu raz rozbehneš, práca ti neúmerne až zázračne klesá. A o tom je automatizované testovanie. Robotu, ktorá sa tebe nechce nenecháš na kolegu, ktorý sa vrátil z dovolenky. Ani ju nenaučíš masturbovať, aby sa spravila sama. (cit. Vtipnejší vyhráva 09/1994) Ale prenecháš ju počítaču. A on sa nepomýli, nevynadá ti, nezoberie sa o 16:00 domov a neochorie, keď polovica kancelárie zalieva zázvor vriacou vodou. Nevýhodou však je, že počítač vidí len toľko, koľko ho ty naučíš. Nie je inteligentný a nevidí veci v súvislostiach. Nevie si niečo všimnúť. Povieš mu slová František a Lászlo a on sa nezasmeje. Ani ty sa dúfam nesmeješ. A ešte si dávaj pozor, ako píšeš kód, aby si ho nemusel po sebe 30x opravovať, ak sa na stránke niečo zmení. Pretože to je tiež bolesť, neustále dohľadávať chyby v testoch. Poďme si porovnať manuál a automat. Výhody, nevýhody, kedy ktoré použiť.Manuálne testovanie+ hľadanie nových chýb v aplikácii, exploratory testing + objavenie dizajnových prešľapov + rýchla odpoveď na stav softvéru + improvizácia - nákladné - nevhodné na regresné testovanie - časovo náročné - nespoľahlivé (časový stres, prehliadneš chyby) - ako aplikácia rastie, rastú aj náklady na manuálne testovanie Automatizované testovanie+ regresné testovanie (pred vydaním do produkcie, po každej zmene) + rýchle + spoľahlivé + práca ti postupne ubúda - vyššie vstupné náklady (kým spustíš prvý test) - robot nové chyby nenájde - musíš vedieť programovať - údržba ZáverManuálne testovanie nemôže byť nahradené automatizovaným. Ak si manuálny tester, kľudne si vydýchni. A vydýchni si znovu, pretože ťa viem odbremeniť od tortúry, ktorú ti spôsobuje testovanie po každej jednej zmene. Naučím ťa programovať robota. Základy programovania a automatizovaného testovania ťa naučím v kurze s Batmanom: http://bit.ly/batmanKurz Ako písať efektívny kód, vedieť si postaviť Maven projekt, rozbehať jenkins, to ťa naučím v tomto kurze: http://bit.ly/jokerKurz A ako ten kód pekne zabaliť do jagavého pozlátka, aby mu každý rozumel ťa naučím v kurze s uhorkou: http://bit.ly/cucumberKurz Autorom blogu je Martin "Furby" Škarbala. Keď ťa zaujíma oblasť testovania softvéru, určite daj lajk na jeho Facebook stránku.
Čísla a znaky v Jave
Tipy a triky
28.10.2018
Skillmea

Čísla a znaky v Jave

V tomto článku sa spolu pozrieme na základy práce s číslami a znakmi v programovacom jazyku Java.  Čísla[Image] Prečo používať Numbers a nie primitívne dátové typy? Ak nejaká metóda príjma ako parameter Object, tak jej neviem podsunúť primitívny dátový typ. Môžeš použiť konštanty, ako napríklad MIN_VALUE alebo MAX_VALUE. Môžeme používať metódy na konverziu do a z primitívnych dátových typov ako aj zo String.  Byte b = 127; Byte b2 = 128; //error Byte len do 127Pre všetky typy máme metódy, ktoré z textu vedia vyloviť daný typ. Tu si treba dať pozor, lebo ak chcem zo Stringu dostať Integer – ale zadám tam text, tak to bude chyba. String decimal = "2.5"; double d1 = Double.parseDouble(decimal); decimal = "2.5a"; double d; d = Double.parseDouble(decimal); //chyba Keď hovoríme o číslach, tak nemôžme nespomenúť modulo. Plus, mínus poznáme, ale modulo by nám mohlo robiť problém. private static void modulo() { for(int i = 0; i < 32; i++){ rozdajHracoviKartu(i%4,i); } } private static void rozdajHracoviKartu(int hrac, int karta) { System.out.println("rozdavam hracovi "+hrac+", kartu cislo "+karta); }V tomto príklade výsledok modula nebude nikdy viac ako 3 a menej ako 0. Teda sa karty rozdajú medzi všetkých hráčov v hre. Skús si to pomeniť sám.  Matematické operácie Pre veľa matematických operácií máme triedu Math, ktorá obsahuje množstvo statických metód. Názvy sú samo vysvetľujúce, alebo si pozri nižšie komentáre: System.out.println("a "+a+" abs "+ Math.abs(a)); //absolútna hodnota System.out.println("b "+b+" ceil "+Math.ceil(b)); //zaokrúhli nahor System.out.println("b "+b+" floor "+Math.floor(b)); //zaokrúhli nadol System.out.println("b "+b+" rint "+Math.rint(b)); //klasicke zaokruhovanie zmen b ... vracia double hodnotu intu System.out.println("b "+b+" round "+Math.round(b)); //klasicke zaokruhovanie zmen b ... vracia int alebo long ... int round(float f) System.out.println("c "+c+" a d "+d+" max "+Math.max(c, d)); System.out.println("c "+c+" a d "+d+" min "+Math.min(c, d)); Náhodné číslo V Math triede máme metódu random. Vracia hodnotu od 0.0 po 1.0 . Krátením vieš zvätsit a musíš pretypovať na int ak chceš celé čísla. private static void randomNumbers() { int number = (int)(Math.random() * 100); System.out.println(number); }ZnakyPrimitívny dátový typ char sa používa na uchovanie jedného znaku. Pri char máme tak isto možnosť použiť jeho alternatívu objektovú a to Character V jave existujú escape sekvencie. To sú znaky, teda char, pred ktorým je spätné lomítko. Tieto sekvencie majú pre kompilátor zvláštny zmysel. Neberú sa ako nejaký jednoduchý text.  \t    -  vloží tab  \b   -   vloží backspace  \n   -   vloží nový riadok  \r    -   vloží carriage return  \f    -   vloží formfeed  \'    -    vloží jednu uvodzovku  \"    -   vloží dvojitú uvodzovku  \\    -   vloží spätné lomítko  Máme množstvo pomocných metód: Character ch3 = 'a'; System.out.println("char "+ch); System.out.println("isLetter "+Character.isLetter(ch)); System.out.println("isDigit "+Character.isDigit(ch)); System.out.println("isWhitespace "+Character.isWhitespace(ch)); System.out.println("isUpperCase "+Character.isUpperCase(ch)); System.out.println("isLowerCase "+Character.isLowerCase(ch)); System.out.println("toUpperCase "+Character.toUpperCase(ch)); System.out.println("toLowerCase "+Character.toLowerCase(ch)); System.out.println("toString "+Character.toString(ch));Takýmto spôsobom vieš vložiť aj špeciálne znaky z hora:System.out.println("Some \t nice text. tab"); System.out.println("Some \t\b nice text. backspace"); System.out.println("Some \n nice text. new line"); // je to niečo ako na starom písacom stroji kedy si sa presunul na začiatok riadku //ak nedáš ale nový riadok, tak ti prepíše to čo tam už máš napísané System.out.println("Some \r nice text. carriage return"); System.out.println("Some \r\n nice text. carriage return a new line"); System.out.println("Some \' nice text. "); Ak chceš zadať špeciálnu hodnotu, tak musíš zadať pred daný špeciálny znak lomítko. char uvodzovka = '''; //error char uvodzovka = '\''; System.out.println("Some " nice text."); //error System.out.println("Some \" nice text."); System.out.println("Some \ nice text."); //error System.out.println("Some \\ nice text.");ZáverAk ťa zaujíma Java, tak si tu na https://skillmea.sk pohľadaj kurzy, ktoré sa venujú programovaniu v Jave a nauč sa viac. Ja som Jaro a dúfam sa vidíme pri ďalšom článku alebo videu.
Websockety - message board
Tipy a triky
04.10.2018
Miroslav Beka

Websockety - message board

Ahoj, naposledy sme hovorili o websocketoch vo flasku. Používali sme knižnicu flask-socketio a prešli sme si základnú funkcionalitu. Táto knižnica používa koncept miestností alebo rooms, ktorý slúži na to, aby sme vedeli adresovať klientov v nejakých skupinách. Tento koncept sa používa v chatových aplikáciách, kde používatelia vidia správy len v miestnosti, v ktorej sa nachádzajú. Nedostanú správy zo žiadnej inej. Pozrieme sa teda na tento koncept a aby sme spravili aj nejaký reálny príklad, spravíme vlastnú chatovaciu appku. Používatelia sa budú môcť pridať do existujúcej miestnosti, chatovať s ostatnými, vytvárať nové miestnosti a podobne. Bude to veľmi jednoduchý message board.[Image] Základ projektuZačne tým, že si vytvoríme virtualenv! Bez toho sa ani nepohneme. $ mkdir websockets_message_board $ cd websockets_message_board $ virtualenv venv $ . venv/bin/activateInštalujeme závislosti. Budeme používať to isté, čo v predchádzajúcom článku (venv)$ pip install flask, flask-socketioIdeme na boilerplate pre našu appku. Štruktúra vyzerá asi takto: ▾ websockets_message_board/ ▾ static/ ▾ css/ main.css ▾ js/ main.js ▾ templates/ board.jinja ▸ venv/ server.pySúbory main.css a main.js sú zatiaľ prázdne, slúžia len ako placeholder. Pokračujeme teda so súborom server.py a ideme ho naplniť kódom. from flask import Flask from flask import render_template from flask import redirect from flask import url_for from flask_socketio import SocketIO app = Flask(__name__) app.config['SECRET_KEY'] = '\xfe\x060|\xfb\xf3\xe9F\x0c\x93\x95\xc4\xbfJ\x12gu\xf1\x0cP\xd8\n\xd5' socketio = SocketIO(app) ### WEB CONTROLLER @app.route("/") def index(): return redirect(url_for("view_board")) @app.route("/board/") def view_board(): return render_template("board.jinja") if __name__ == '__main__': socketio.run(app, debug=True)Rozdiel oproti minimálnej flask appke je ten, že ju inak spúšťame. Nepoužijeme if __name__ == '__main__': app.run()ale budeme ju spúšťať cez socketIO. if __name__ == '__main__': socketio.run(app, debug=True)To preto, aby aplikácia vedela spustiť viacero vlákien pre každého používateľa. Tak isto je dobré vedieť, že deployment na produkčný server takejto aplikácie je trošku komplikovanejší ako keď máme klasickú flask appku. Obsah základného templejtu board.jinja (aj jediného, ktorý budeme používať) je nasledovný: <!DOCTYPE HTML> <html> <head> <title>Short Term Memory Message Board</title> <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script> <script type="text/javascript" src="{{ url_for("static", filename="js/main.js")}}"></script> <link rel="stylesheet" type="text/css" href={{url_for("static", filename="css/main.css")}}> </head> <body> Hello </body> </html>máme tam zopár dôležitých importov ako socket.io, jquery a tak isto aj css a js súbory našej appky. Takýto jednoduchý základ môžeme spustiť a uvidíme, či všetko šlape ako má $(venv) python server.py WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance. * Serving Flask app "server" (lazy loading) * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance. * Debugger is active! * Debugger PIN: 112-998-522FaceliftTento krok nie je vôbec potrebný, ale keďže všetci majú radi pekné veci, nainštalujeme si css framework zvaný semantic-ui. Je to fajn framework, mám s ním dobré skúsenosti. Dokumentácia je možno trošku tažšia na pochopenie, ale okrem toho to funguje a hlavne vyzerá veľmi pekne. [Image] Stačí stiahnuť toto zipko a integrovať do svojho projektu. Je to veľmi jednoduché. Zip rozbalíme a prekopírujeme nasledovné súbory • themes -> websockets_message_board/static/css/ • semantic.min.css -> websockets_message_board/static/css/ • semantic.min.js -> websockets_message_board/static/js/ Súbory semantic.min.js a semantic.min.css musím includnuť na svoju stránku, takže bežím do board.jinja a prihodím do hlavičky ďalšie riadky: <head> <title>Short Term Memory Message Board</title> <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script> <script type="text/javascript" src="{{ url_for("static", filename="js/semantic.min.js")}}"></script> <script type="text/javascript" src="{{ url_for("static", filename="js/main.js")}}"></script> <link rel="stylesheet" type="text/css" href={{url_for("static", filename="css/semantic.min.css")}}> <link rel="stylesheet" type="text/css" href={{url_for("static", filename="css/main.css")}}> </head>Je dôležité dať si pozor, aby sme najprv pridali jquery a až potom semantic.min.js, inak sa mi semantic-ui bude sťažovať, že nevie nájsť jquery knižnicu. V priečinku themes sú hlavne ikony a nejaké obrázky, ktoré semantic-uiposkytuje. Po inštalácií css frameworku môžem hneď vidieť zmenu v podobe iného fontu na mojej smutnej stránke. Nič iné tam ešte nieje. UISpravíme teraz približný náčrt UI, aby som vedel, ako appka asi bude vyzerať a aké funkcie jej vlastne spravíme. Nebude to nič svetoborné. Budeme mať jednu stránku ktorú rozdelím na 3 sekcie. Hlavná bude obsahovať správy, takže to bude môj message board. Bočný panel bude obsahovať zoznam miestností, do ktorých sa budem vedieť prepínať. No a na spodnej lište bude input pre moju správu.[Image] Zhmotním túto svoju predstavu do kódu. Otvorím board.jinja a nahádžem tam nejaké <div> elementy. Keďže používame semnatic-ui ako náš css framework, budem rovno používať triedy v html. Použijeme grid systém, ktorý nám zjednoduší prácu pri ukladaní ui elementov. <body class="ui container"> <div class="ui grid"> <div class="ten wide column"> message board </div> {# end ten wide column #} <div class="six wide column"> rooms </div> {# end six wide column #} </div> {# end grid #} <footer> text input </footer> </body>Môžem skúsiť naplniť tieto časti aj nejakým obsahom. Len tak zo zvedavosti, ako to bude vyzerať. Všetko bude zatiaľ len tak naoko (prototypovanie). Začneme tým najhlavnejším: message boardom <div class="ten wide column"> <h1 id="room_heading" class="ui header">Johny @ Music room</h1> <div id="msg_board"> <div class="ui mini icon message"> <i class="comment icon"></i> <div class="content"> <div class="header">Johny</div> <p>Hello there</p> </div> </div> <div class="ui mini icon message"> <i class="comment icon"></i> <div class="content"> <div class="header">Tommy</div> <p>Hi!</p> </div> </div> <div class="ui mini icon message"> <i class="comment icon"></i> <div class="content"> <div class="header">Tommy</div> <p>What's up?</p> </div> </div> </div> {# end msg board #} </div> {# end ten wide column #}Všetky správy som obalil do div s id msg_board aby som potom jednoducho vedel pridávať nové správy do tohto elementu.[Image] Spravíme to isté pre zoznam miestností. Rozhodol som sa, že do tohto bočného panelu strčíme aj formulár na zmenu mena používateľa. Ten by mal mať možnosť zmeniť svoje meno. Bude to vyzerať asi takto: <div class="six wide column"> <h4 class="ui dividing header">Change username</h4> <form id="choose_username" class="ui form" method="post"> <div class="field"> <div class="ui action input"> <input type="text" id="user_name" placeholder="username..."> <button class="ui button">Change</button> </div> </div> </form> <h4 class="ui dividing header">Rooms</h4> <form id="choose_room" class="ui form" method="post"> <div class="grouped fields"> <label for="fruit">Select available room:</label> <div id="room_list"> <div class="field"> <div class="ui radio checkbox"> <input type="radio" name="room" class="hidden" value="Lobby"> <label>Lobby</label> </div> </div> <div class="field"> <div class="ui radio checkbox"> <input type="radio" name="room" class="hidden" value="Music"> <label>Music</label> </div> </div> <div class="field"> <div class="ui radio checkbox"> <input type="radio" name="room" class="hidden" value="Movies"> <label>Movies</label> </div> </div> </div> <div class="field"> <input type="text" id="new_room" placeholder="create new room"> </div> <button class="ui button"> Change Room</button> </div> </form> </div> {# end six wide column #}[Image] Tak isto som pridal aj <input /> na vytváranie nových miestností. Myslím, že takúto možnosť by používateľ mohol mať. Poslednou skladačkou bude input pre naše správy. <footer> <form id="send_msg_to_room" class="ui form" method="post"> <div class="field"> <div class="ui fluid action input"> <input type="text" id="msg_input" placeholder="message..."/> <button class="ui button" value="send">send</button> </div> </div> </form> </footer>[Image] Momentálne mi nebudú fungovať radio buttony, pretože semantic-ui potrebuje tieto inicializovať v javascripte. Pome teda na to. Otvoríme main.js a píšeme $(document).ready(function(){ // UI HANDLERS $('.ui.radio.checkbox').checkbox(); });Tak isto môžeme rovno vybaviť iniciálne spojenie cez websockety medzi klientom a serverom. $(document).ready(function(){ var url = location.protocol + "//" + document.domain + ":" + location.port; socket = io.connect(url); // UI HANDLERS $('.ui.radio.checkbox').checkbox(); });Posielanie správ môžem rovno aj vyskúšať v konzole prehliadača. Stačí otvoriť developer tools, prejsť na záložku console a tam už môžeme písať socket.emit("test", "hello there")[Image] Avšak, nič sa nedeje, pretože môj backend ešte nie je vôbec pripravený. Vrhneme sa teda na server side a implementujeme miestnosti - room. RoomsPresunieme sa do súboru server.py a pridáme handler pre základné eventy ktoré budeme používať: join, leave, msg_board, username_change ... from flask_socketio import send, emit from flask_socketio import join_room, leave_room ... ### WEB CONTROLLER @app.route("/") def index(): return redirect(url_for("view_board")) @app.route("/board/") def view_board(): return render_template("board.jinja") ## SOCKET CONTROLLER @socketio.on("join") def on_join(data): username = data["user_name"] room = data["room_name"] join_room(room) send("{} has entered the room: {}".format(username, room), room=room) @socketio.on("leave") def on_leave(data): username = data["user_name"] room = data["room_name"] leave_room(room) send("{} has left the room: {}".format(username, room), room=room) @socketio.on("msg_board") def handle_messages(msg_data): emit("msg_board", msg_data, room=msg_data["room_name"]) @socketio.on("username_change") def username_change(data): msg = "user \"{}\" changed name to \"{}\"".format( data["old_name"], data["new_name"]) send(msg, broadcast=True) ...Eventy join, leave a username_change fungujú veľmi jednoducho. Zakaždým sa pozriem na dáta, ktoré mi prišli (premenná data) a vytvorím jednoduchú správu, ktorú potom broadcastujem na všetkých používateľov v tej danej miestnosti. Ak si už poriadne nepamätáš, čo robil ten broadcast, pospomínaj z minulého blogu. Dôležité je použitie funkcií join_room a leave_room. Tieto pochádzajú z knižnice flask-socketio, ktorú sme inštalovali na začiatku. Slúžia na to aby sme priradili danú session do nejakej miestnosti. Potom, keď pošlem správu do miestnosti, dostanú ju všetci v tej miestnosti. Je to fajn mechanizmus ako kontaktovať iných klientov a usporiadať si ich do nejakých kategórií. rooms nemusím nevyhnutne používať len na chatovú funkcionalitu. Môžem to použiť na to, aby som si zoradil používateľov do nejakej spoločnej skupiny, ktorej posielam barsjaké dáta. Dajme tomu, že by som mal appku o počasí, a nejaká skupina používateľov by mala záujem o notifikácie, či bude pršať. Tak týchto by som hodil do spoločnej skupiny - miestnosti - a notifikácie by som posielal len im. Využitie je teda všakovaké. JavaScriptBackend bol v tomto prípade celkom jednoduchý a nepotrebovali sme toho veľa implementovať. Správy sa od nášho backendu len odrážajú ako od relátka, ktorý ich ďalej rozposiela klientom. Na strane klienta toho bude trošku viacej. Pokračujeme v súbore main.js. Teraz sa pokúsime implementovať posielanie správy a zobrazenie prichádzajúcej správy na messageboard. $(document).ready(function() { ... // generate random user name if needed setRandomNameAndRoom(); // join default room joinRoom(socket); // UI HANDLERS $('.ui.radio.checkbox').checkbox(); // send message $("form#send_msg_to_room").submit(function(event) { userName = sessionStorage.getItem("userName"); roomName = sessionStorage.getItem("roomName"); msg = $("#msg_input").val(); sendMessage(socket, userName, roomName, msg); this.reset(); return false; }); // handle new message socket.on("msg_board", function(data){ msg = '<div class="ui mini icon message">'; msg += '<i class="comment icon"></i>'; msg += '<div class="content">'; msg += '<div class="header">'+data["user_name"]+'</div>'; msg += '<p>' + data["msg"] + '</p>'; msg += '</div>'; msg += '</div>'; $("#msg_board").append(msg); }); }); // HELPERS function setRandomNameAndRoom(){ if (sessionStorage.getItem("userName") == null){ randomName = "user" + Math.floor((Math.random() * 100) + 1); sessionStorage.setItem("userName", randomName); sessionStorage.setItem("roomName", "Lobby"); }; }; function joinRoom(socket){ data = { "room_name" : sessionStorage.getItem("roomName"), "user_name" : sessionStorage.getItem("userName") }; socket.emit("join", data); }; function sendMessage(socket, userName, roomName, message){ data = { "user_name" : userName, "room_name" : roomName, "msg" : msg }; socket.emit("msg_board", data); }; Na začiatok vytvoríme nejaké random meno používateľa a zvolíme default miestnosť "Lobby". To aby sme s týmto nemali starosti zatiaľ. Používame na to pomocné funkcie, ktoré si implementujeme bokom, aby nám nezavadzali. Meno používateľa a názov aktuálnej miestnosti si udržiavam v sessionStorage, čo je fajn dočasné úložisko v prehliadači. Prežije aj reload stránky a navyše sa mi tento spôsob viacej páči ako udržiavať informáciu v cookies. Keď máme potrebné dáta, môžeme sa hneď na začiatku buchnúť do nejakej miestnosti. V javascripte používame knižnicu socket.io, ktorá ale žiadny koncept miestností nepozná. Ak sa pozrieš do dokumentácie (pozor! otvor si client api), zistíš, že nič také ako rooms sa tam nespomína. Takže to je vecička knižnice flask-socketio. Použijeme teda klasický emit na handler join, ktorý existuje na servery. Tento riadok $("form#send_msg_to_room").submit( sa pomocou jquery napichne na formulár a zachytí odoslanie formuláru. Potom môžem robiť čo sa mi zachce a nakoniec vrátim false, takže formulár sa reálne ani neodošle. Odoslanie správy je priamočiare. Zistím UserName, zistím RoomName, vytiahnem si text správy a všetko pošlem do funkcie sendMessage. Táto už zabezpečí zabalanie informácií do jsonu a posielam pomocou funkcie emit. Posielam na handler msg_board, ktorý som si spravil pred chvíľkou. Ostáva mi vyriešiť prijatie správy. To robím pomocou funkcie socket.on, kde dám kód, ktorý sa vykoná pri prijatí správy. Tu si jednoducho (ale zato strašne škaredo) pozliepam kus HTML, ktoré potom strčím na koniec elementu s id msg_board. Predtým, ako to budeš skúšať, je fajn si ešte vymazať tie fejkové správy, ktoré sme tam dali natvrdo do HTML. Takže mažeme tieto riadky <div class="ten wide column"> <h1 id="room_heading" class="ui header">Johny @ Music room</h1> <div id="msg_board"> ---> <div class="ui mini icon message"> ---> <i class="comment icon"></i> ---> <div class="content"> ---> <div class="header">Johny</div> ---> <p>Hello there</p> ---> </div> ---> </div> ---> <div class="ui mini icon message"> ---> <i class="comment icon"></i> ---> <div class="content"> ---> <div class="header">Tommy</div> ---> <p>Hi!</p> ---> </div> ---> </div> ---> <div class="ui mini icon message"> ---> <i class="comment icon"></i> ---> <div class="content"> ---> <div class="header">Tommy</div> ---> <p>What's up?</p> ---> </div> ---> </div> </div> {# end msg board #} </div> {# end ten wide column #}Pome teda ako ďalšiu vec vybaviť zmenu používateľského mena. $(document).ready(function(){ ... // set heading updateHeading(); // set user name handler $("form#choose_username").submit(function(event){ // get old and new name var oldName = sessionStorage.getItem("userName"); var newName = $("#user_name").val(); //save username to local storage sessionStorage.setItem("userName", newName); // change ui updateHeading(); // notify others notifyNameChange(socket, oldName, newName); //clear form this.reset(); return false }); }); function updateHeading(){ roomName = sessionStorage.getItem("roomName"); userName = sessionStorage.getItem("userName"); $("#room_heading").text(userName + " @ " + roomName); }; function notifyNameChange(socket, oldName, newName){ data = { "old_name" : oldName, "new_name" : newName } socket.emit("username_change", data); };Tak ako pri posielaní správy, napichnem sa na HTML formulár a spracujem ho ešte pred odoslaním. Zmeny uložím do sessionStorage. Pridal som ešte 2 vychytávky. • funkcia updateHeading nastaví aktuálny názov miestnosti a používateľa ako hlavičku stránky, • notifyNameChange dá všetkým používateľom vedieť, že si niekto zmenil meno. Meno si už môžem meniť, ale notifikáciu o zmene som nedostal. Na to ešte musíme doplniť jeden event handler na message $(document).ready(function(){ ... // system message socket.on("message", function(data){ msg = '<div class="ui mini icon info message">'; msg += '<i class="bell icon"></i>'; msg += '<div class="content">'; msg += '<p>' + data + '</p>'; msg += '</div>'; msg += '</div>'; $("#msg_board").append(msg); }); }); ...Teraz sa nám začnú zobrazovať aj systémové notifikácie o tom, čo sa deje. Kto vošiel do miestnosti, kto ju opustil alebo kto si zmenil meno. Poslednou vecou, ktorú musíme spraviť, je selekcia miestností. Toto bude vyžadovať trošku viacej práce. Zoznam existujúcich miestností si musíme udržiavať na backende. Ani na klientskej časti ani na backende z knižnice flask-socketio neviem získať zoznam všetkých miestností. Musím si ho teda udržiavať sám. from flask import g ... DEFAULT_ROOMS = ["Lobby"] ... @app.route("/board/") def view_board(): all_rooms = getattr(g, "rooms", DEFAULT_ROOMS) return render_template("board.jinja", rooms=all_rooms) ... ### SOCKET CONTROLLER @socketio.on("join") def on_join(data): username = data["user_name"] room = data["room_name"] all_rooms = getattr(g, "rooms", DEFAULT_ROOMS) if room not in all_rooms: all_rooms.append(room) emit("handle_new_room", {"room_name" : room}, broadcast=True) join_room(room) send("{} has entered the room: {}".format(username, room), room=room)Do templejtu board.jinja som si začal posielať nejaké dáta. Vyhodím teda tie fejkové, ktoré sú tam natvrdo, a spravíme loop, v ktorom pridám všetky existujúce miestnosti. <div id="room_list"> {% for room in rooms %} <div class="field"> <div class="ui radio checkbox"> <input type="radio" name="room" class="hidden" value="{{room}}"> <label>{{room}}</label> </div> </div> {% endfor %} </div>Pokračujem v súbore main.js, kde si vytvorím funkcie, ktoré sa postarajú o zmenu miestnosti + ak bola vytvorená nová, tak ju pridám do zoznamu. $(document).ready(function(){ ... // set room name heading selectCurrentRoom(); updateHeading(); ... // set room handler $("form#choose_room").submit(function(event){ newRoom = getRoomName(); // first leave current room leaveRoom(socket); // set new room sessionStorage.setItem("roomName", newRoom); updateHeading(); // join new room joinRoom(socket); //clear input newRoom = $("#new_room").val(""); //clear message board $("#msg_board").text(""); return false; }); socket.on("handle_new_room", function(data){ item = '<div class="field">'; item += '<div class="ui radio checkbox">'; item += '<input type="radio" name="room" class="hidden" value="'+ data["room_name"] + '">'; item += '<label>' + data["room_name"] + '</label>'; item += '</div>' item += '</div>' $("div#room_list").append(item); selectCurrentRoom(); }); }); ... function leaveRoom(socket){ data = { "room_name" : sessionStorage.getItem("roomName"), "user_name" : sessionStorage.getItem("userName") }; socket.emit("leave", data); }; function selectCurrentRoom(){ currentRoom = sessionStorage.getItem("roomName") $(".ui.radio.checkbox").checkbox().each(function(){ var value = $(this).find("input").val(); if (value == currentRoom){ $(this).checkbox("set checked"); }; }); }; function getRoomName(){ roomName = $("#new_room").val(); if (roomName == ""){ roomName = $("input[type='radio'][name='room']:checked").val(); }; return roomName; };Je tu viacero pomocných funkcií, ktoré mi pomáhajú pri výbere miestnosti alebo pri vytváraní novej. Problematické časti nastávajú práve v tedy, keď chcem miestnosť aj vytvárať. V podstate ale nejde o žiadne komplikované veci. Funkcia selectCurrentRoom mi pomôže prehodiť radio button pri zmene miestnosti. Tým, že používame semantic-ui tak sa nám to tiež trošku skomplikovalo, ale výsledok stojí za to.[Image] ZáverPostavili sme takzvaný proof of concept, spravili sme chatovaciu appku len pomocou websocketov. Nie je to dokonalé a určite je tam veľa múch, to nám však nebránilo pochopiť ako fungujú websockety. Všetky správy žijú len v prehliadači používateľa a nie sú uložené na žiadnom serveri. Niekto to môže považovať za chybu, niekto za fičúru. To už nechám na vás. Celý projekt sa dá stiahnuť tu. Onedlho sa opäť vrhneme na nejakú zaujímavú tému ;)

Nezmeškaj info o nových kurzoch a špeciálnych ponukách