Zpět na blog
Tipy a triky

Java persistence – JPA, Hibernate, ORM

Skillmea
05.02.2019
12 minut čtení
Java persistence – JPA, Hibernate, ORM
JPA je java persistence api specifikace. K tomu, abys mohl používat JPA ve skutečné aplikaci, potřebuješ implementaci JPA. Buď použiješ servery, které již nabízejí implementaci JPA, jako například GlassFish, nebo použiješ implementaci, kterou ti poskytuje framework Hibernate nebo EclipseLink.

Pokud používáme JPA standardy, tak je v budoucnu úplně jedno, jakou implementaci JPA budeme používat. Při programování budeme používat JPA anotace, které pocházejí z balíku javax.persistence. V budoucnu můžeš nasadit aplikaci na GlassFish, který zná javax.persistence a umí s tím pracovat nebo na Tomcat s použitím Hibernate, který také zná javax.persistence a umí s tím pracovat.

Co je Persistence?

Pokud vytvoříš ledajaký jednoduchý objekt, co se stane? Například objekt Adresa? Vytvoří se v haldě – v paměti. Objekt může mít nasetována nějaká data – informace. Pokud se ale ztratí reference v javovském kódu na tento objekt v haldě - tak se smaže.

Pokud si chceme uchovat tyto informace, tak je můžeme uložit do databáze a nejlépe, aby po vytažení z databáze měla tato data stejnou formu – tedy formu objektu Adresa.

Tomu se říká, že persistujeme (uchováme stálost) objekt do databáze. Jako by tento objekt existoval i mimo java programu. Tento objekt se uchová v úložišti a znovu se vytvoří, bude-li třeba.

Co je ORM?

Klasické databáze jako Oracle, MySql a podobně jsou relační databáze, které mají data uložena ve formě tabulek. V jevu ukládáme data ve formě objektů, v databázi ve formě tabulek. Ale co mají podobné? V relační databázi máme sloupce, které mají názvy a v řádcích máme hodnoty. Něco jako klíč hodnota – totéž platí i pro objekty v jevu – tam máme název proměnné a hodnotu v ní uloženou.

Tady přichází pod ruku ORM – tedy object relational mapping. Je to něco jako objektově relační mapování. My naše java objekty namapujeme na tabulky relační databáze. Abychom věděli, že toto pole v javovském objektu patří do tohoto sloupce. 

Objekty v jevu jsou mezi sebou propojeny pomocí uložení reference na daný objekt. Například člověk má field Adresa, kde je uložena reference na objekt Adresa.

Relační tabulky mají mezi sebou také vazby. Buď máme v tabulce pro člověka sloupec adresa, kde bude uložen identifikátor adresy a na základě tohoto identifikátoru najdeme danou adresu. Nebo existuje speciální tabulka, kde budou dva sloupce jeden pro identifikátor adresy a druhý pro identifikátor člověka. My pak umíme najít, jaké adresy má člověk nastaveno, nebo pro jakého člověka je nastavena daná adresa.

Problém s JDBC přístupem – výhoda ORM

V kurzu Java pro pokročilé , pokud jsi tento kurz viděl, jsme si ukazovali přístup k databázi přes JDBC. Co jsme udělali? Otevřeli jsme konekci na databázi, napsali jsme sql příkaz, který jsme následně poslali do databáze k provedení. Databáze nám vrátila výsledek ve formě result setu.

Představ si, že máš jen 5 až 10 tabulek. Nad každou tabulkou máš například 4 různé sql příkazy - to máme přibližně 20 - 40 sql příkazů. Pokud se ti stane, že musíš změnit databázi – například změníš název sloupce v tabulce? Co musíš udělat? Musíš přepsat název tohoto sloupce na xy místech – na 20 až 40 místech – a to jsme jen v malé aplikaci – co kdyby to bylo na 100 místech?.

Byl by v tom nepořádek a mohly by nastat problémy.

Pokud ale použijeme ORM, tak v jevu pracujeme s naším kódem, tak jako běžně. Vytvoříme si objekty typu Clovek, nastavíme mu nějaké hodnoty. Dále si vytvoříme kolekci adres pro daného člověka. Nakonec v ORM frameworku řekneme jen persistní mi tento objekt. ORM se pak postará o veškeré uložení těchto objektů do databáze na základě mapování, které 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);
   

Pokud bychom nepoužili ORM, sami bychom museli napsat metodu, která nám otevře konekci na databázi, museli bychom napsat INSERT SQL příkaz pro člověka a poté i pro jeho adresy a museli bychom zajistit, abychom nastavili všude data tam, kde mají být a musíme se postarat io propojení mezi těmito dvěma objekty.

Pokud ale použijeme ORM, tak se nemusíme starat o tento balast kódu, ale soustředit se zejména na to, co prodává a to je business logika aplikace.

Nevýhody JDBC přístupu jsou tedy, že máme příliš mnoho SQL příkazů, velmi mnoho kopie kódu, ručně se musíme postarat o nastavení dat do správných sloupců.

Výhodou ORM je, že nemusíme dělat tyto věci z předchozí věty. ORM nám umožní používat java objekty k reprezentaci relační databáze. ORM se nám postará io propojení závislostí. ORM spojí výhody relační databáze a objektového modelu v jevu plus schová veškerou komplexitu SQL příkazů.

Co je Hibernate?

Hibernate je ORM – object relational mapping framework, který slouží k mapování java objektů na tabulky relačních databází.

Java programátoři jsou zvyklí psát kód v objektech, proč tedy potřebují další jazyk – sql – k získání dat z databáze? Hibernate na pozadí sám vytváří sql příkazy nad databází a proto nemusíme psát sql příkazy my.

Pokud chceme uložit mapu objektů, například Cloveka, který má Adresu, nebo i více objektů typu Adres, tak nemusíme psát všechny sql příkazy. Stačí, když zavoláme jednoduchou metodu pro uložení objektu do databáze a hibernate se postará o zbytek.

Hibernate je také implementace JPA.
 

Co je JPA?

Zkratka JPA je Java Persistence API. Co to znamená? V jednoduchosti řečeno - je to standard. Poněkud složitěji řečeno – je to specifikace pro OR mapování a je součástí Java EE, ale můžeme ji používat i v Java SE projektech.

Některé servery poskytují vlastní implementaci JPA a některé ne – v tom případě použijeme například Hibernate implementaci.

Představ si, že celý tvůj kód používá věci z JPA. Nyní je na tobě, kam nasadíš svoji aplikaci. Pokud ji nasadíš na Glassfish nemusíš předělávat svůj kód, který používá JPA – Glassfish ho zná. Pokud svou aplikaci nasadíš na Tomcat, tak mu přihodíš Hibernate, který také zná JPA. Potom tvůj kód bude fungovat všude – neboť používá standardy JPA.

Je možné, abychom používali jen Hibernate – tedy bychom nepoužívali nic ze standardů. Žádné anotace z javax.persistence a podobně – to ale nedoporučuji.

Ptal jsem se

Napadlo mě, že by nebylo od věci zeptat se kolegů developerů, co si myslí o JPA a Hibernatě. Pokud by sis chtěl přečíst jejich názory, ať se líbí – bez cenzury, cituji:

Tak toto je náročné téma a navíc složité.

JPA resp. ORM obecně (a tedy i Hibernate) jsou vždy složitější, než si uživatelé (tj. vývojáři) uvědomují. Výsledkem jsou často nenápadné chybičky, lazy load exceptions, které vedou k anti-patternům jako je OSIV (open session in view) nebo k výkonovým problémům (n+1 problem).

Těchto problémů je typicky o to více, o co složitější je mapování – a přitom právě na řešení složitého mapování bylo ORM vymyšleno. Abychom mohli namapovat doménu do DB. K tomu se často používají i „mimojazykové“ triky jako reflection na private pole, takže objekty jsou implicitně svázány s ORM řešením, i když například. mapování je odděleno do XML namísto anotací, což samo o sobě je také nepraktické.

Kromě toho mají obě hlavně implementace dost bugů na to, aby na ně člověk narazil, i když postupuje v souladu se specifikací – stačí jen chtít trosku víc a na nějaký bug určitě narazíte. Takže pak to je kličkování mezi bugy a často komplikovaná možnost vyměnit ORM providera.

Co se mi na ORM líbí je lepší mapování typů, možnost customizovat mapování a podobně. Proto používám JPA i na jednoduché mapovačky namísto JDBC.

Navíc s JPA používám řadu Querydsl, které je lepší/intuitivnější, než JPA standardní Criteria API.

Hibernate používám dlouho ale po pravdě řečeno nikdy jsem se moc nezamýšlel nad výhodami. Zatím jsem neměl výraznější problém, který bych neuměl vyřešit, případně nějak obejít.

Plusy
:
- snadno se provádí mapování do DB s anotacemi i pro začátečníka bez velkých znalostí databáze, zároveň ale bez znalosti DB může být mapování neefektivní
- je open source, takže pokud potřebuji, umím podívat zdrojáky jak funguje

Minusy
:
- asociace OneToOne fetch=lazy nefunguje

Na používání Hibernate/JPA (celkově ORM vrstvě) se mi líbí :

A) Abstrakce od fyzického datového modelu. Vývoj nad doménovým/logickým (entitně-relačním datovým modelem) - blíže k byznys vrstvě. Čili zjednodušené práce s objekty namísto tabulek.
B) Možnost využívat různé pokročilejší techniky získávání dat (např. Spring Data JPA, ale také zjednodušující Hibernate Query by example)
C) Agnostické od konkrétní databázové technologie (Oracle, MySql, ...)
D) Cachování a optimalizace (např. lazy loading)

Nevýhody :

A) Někdy náročný (až nemožný) performance tuning.
B) U některých technologií pomalejší křivka učení.
C) I přes používání JPA/Hibernate, je téměř nezbytné, aby developer znal i (native) SQL jazyk a jeho použití.

Závěr

Podařilo se ti nahlédnout do problematiky objektově relačního přístupu k databázi a pochopil jsi, co to znamená. Pokud se však chceš dostat ještě o level dál, připravili jsme pro tebe samostatný kurz Java persistence - JPA a Hibernate .

Pokud chceš ještě víc, tak klikej:

👍 Více o mně:  http://www.jaroslavbeno.cz/   👍 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 tě zajímat

Manuální vs. automatizované testování
Tipy a triky
25.01.2019
Skillmea

Manuální vs. automatizované testování

Rád si v dětství všechno rozebíral, spekuloval a škodolibost hrála v divadle tvých ranních emocí prim? Jsi tady správně. Protože přesně to jsou hlavní rysy testeru. Jen opatrně s tou škodolibostí 😉 Manuální testování není ostuda!Neexistuje nic horšího, než dělat stále to samé dokola. Naštěstí o tom manuální testování není. Ale pěkně postupně. Neznám hezčí pocit v práci (kromě pátečního fajrontu) než když já, člověk, který studoval dojivost krav a hektarový výnos pšenice, nachytá programátora s nějakou chybou. Manuální testování znamená vzít novou část aplikace, usoudit či odpovídá tomu, co si zákazník přál a přitom nachytat programátory. Při testování postupuj stejně jako při boji s mafií. Nejprve jdeš po velkých rybách - chybách, které udělají nejvíce škody, malé si nevšímáš nebo je přeskočíš, protože není čas. Ať naděláš programátorům dost roboty. Pak jdeš po těch menších, designových vychytávkách, protože mají stejnou váhu jako předvčerejší instastory každé druhé makeup artistky. Vymýšlíš, co jsi ještě nevymyslel a jedeš po chodníčcích aplikace, kudy by se normální uživatel nikdy nevydal. Ale Jožo Pročko říkal 20 let dozadu, že nikdy neříkej nikdy. Jako tester to neříkej ani ty. Rozum maká zpočátku víc než ruce, a to je fajn. Fajn to být přestane, když se karta obrátí a nedejbůh, abys musel dvakrát dělat totéž. Nebo 3krát. Nebo 4krát. Nebo furt. Jsi odsouzen k věčnosti regresně testovat celou aplikaci. Protože pokud se změní kód, je třeba to celé proklikat. A v tento moment mozek vypínáš a pracují jen ruce. Vzpomínáš si, když ti jako malému řekli, že pokud se nebudeš učit, budeš kopat kanály? Toto je totéž, akorát sedíš v byznyscentru s dalšími korporátními kopači. Naštěstí tu robotu můžeš přenechat počítači, protože na řadu přichází... Automatizované testování je programování!Nech zase mozek makat a ruce odpočívat. Automatizované testování je o tom, že ty naprogramuješ robota, aby chodil po určité dráze, cestičce v aplikaci a on to bude dělat vždy, když mu přikážeš. Nepředstavuj si robota jako terminátora, který za tebe sedí v kanclu. A ani takhle to nevypadá:[Image] Ty vidíš, že stránka se otevře, ale kliká po ní robot na pozadí, kterého NĚKDO naprogramoval. A tím někým jsi TY. Jsi programátor se vší tou pompou a slávou, píšeš kód např. v JAVĚ a vyvíjíš si vlastní aplikaci, logiku, která testuje software namísto tebe. Ze začátku je to trošku těžkopádné jako startování V3S-ky, ale když tu mašinu jednou rozjedeš, práce ti neúměrně až zázračně klesá. A o tom je automatizované testování. Robotu, která se tobě nechce nenecháš na kolegu, který se vrátil z dovolené. Ani ji nenaučíš masturbovat, aby se udělala sama. (cit. Vtipnější vyhrává 09/1994) Ale přenecháš ji počítači. A on se nesplete, nevynadá ti, nesebere se v 16:00 domů a neonemocní, když polovina kanceláře zalévá zázvor vařící vodou. Nevýhodou však je, že počítač vidí jen tolik, kolik ho ty naučíš. Není inteligentní a nevidí věci v souvislostech. Neumí si něčeho všimnout. Řekneš mu slova František a Lászlo a on se nezasměje. Ani ty se doufám nesměješ. A ještě si dávej pozor, jak píšeš kód, abys ho nemusel po sobě 30x opravovat, pokud se na stránce něco změní. Protože to je také bolest, neustále dohledávat chyby v testech. Pojďme si porovnat manuál a automat. Výhody, nevýhody, kdy které použít.Manuální testování+ hledání nových chyb v aplikaci, exploratory testing + objevení designových přešlapů + rychlá odpověď na stav softwaru + improvizace - nákladné - nevhodné pro regresní testování - časově náročné - nespolehlivé (časový stres, přehlédneš chyby) - jak aplikace roste, rostou i náklady na manuální testování Automatizované testování+ regresní testování (před vydáním do produkce, po každé změně) + rychlé + spolehlivé + práce ti postupně ubývá - vyšší vstupní náklady (dokud spustíš první test) - robot nové chyby nenajde - musíš umět programovat - údržba ZávěrManuální testování nemůže být nahrazeno automatizovaným. Pokud jsi manuální tester, klidně si vydechni. A vydechni si znovu, protože tě umím ulehčit od tortury, kterou ti způsobuje testování po každé jedné změně. Naučím tě programovat robota. Základy programování a automatizovaného testování tě naučím v kurzu s Batmanem: http://bit.ly/batmanKurz Jak psát efektivní kód, umět si postavit Maven projekt, rozběhat jenkins, to tě naučím v tomto kurzu: http://bit.ly/jokerKurz A jak ten kód pěkně zabalit do třpytivého pozlátka, aby mu každý rozuměl tě naučím v kurzu s okurkou: http://bit.ly/cucumberKurz Autorem blogu je Martin "Furby" Škarbala. Když tě zajímá oblast testování softwaru, určitě dej lajk na jeho Facebook stránku.
Čísla a znaky v Javě
Tipy a triky
28.10.2018
Skillmea

Čísla a znaky v Javě

V tomto článku se spolu podíváme na základy práce s čísly a znaky v programovacím jazyce Java.  Čísla[Image]Proč používat Numbers a ne primitivní datové typy? Pokud nějaká metoda přijímá jako parametr Object, tak jí neumím podsunout primitivní datový typ. Můžeš použít konstanty, jako například MIN_VALUE nebo MAX_VALUE. Můžeme používat metody pro konverzi do a z primitivních datových typů i ze String. Byte b = 127; Byte b2 = 128; //error Byte len do 127Pro všechny typy máme metody, které z textu umí vylovit daný typ. Zde je třeba si dát pozor, protože pokud chci ze Stringu dostat 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 Když mluvíme o číslech, tak nemůžeme nezmínit modulo. Plus, minus známe, ale modulo by nám mohlo dělat 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 příkladu výsledek modulu nebude nikdy více než 3 a méně než 0. Tedy se karty rozdají mezi všechny hráče ve hře. Zkus si to poměnit sám. Matematické operácePro mnoho matematických operací máme třídu Math, která obsahuje řadu statických metod. Názvy jsou samo vysvětlující, nebo si viz níže komentáře: 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í čísloV Math třídě máme metodu random. Vrací hodnotu od 0.0 do 1.0 . Krácením umíš zvětšit a musíš přetypovat na int pokud chceš celá čísla. private static void randomNumbers() { int number = (int)(Math.random() * 100); System.out.println(number); }ZnakyPrimitivní datový typ char se používá k uchování jednoho znaku. U char máme také možnost použít jeho alternativu objektovou a to Character V jevu existují escape sekvence. To jsou znaky, tedy char, před kterým je zpětné lomítko. Tyto sekvence mají pro kompilátor zvláštní smysl. Neberou se jako nějaký jednoduchý text. \t - vloží tab \b - vloží backspace \n - vloží nový řádek \r - vloží carriage return \f - vloží formfeed \' - vloží jednu uvozovku \" - vloží dvojitou uvozovku \\ - vloží zpětné lomítko Máme řadu pomocných metod: 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));Tímto způsobem umíš vložit také speciální 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. ");Pokud chceš zadat speciální hodnotu, tak musíš zadat před daný speciální 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ávěrPokud tě zajímá Java, tak jsi tady na https://skillmea.\cz pohledej kurzy, které se věnují programování v Javě a nauč se víc. Já jsem Jaro a doufám se vidíme při dalším článku nebo videu.
Websockety - message board
Tipy a triky
04.10.2018
Miroslav Beka

Websockety - message board

 Ahoj, naposledy jsme mluvili o websocketech ve flasku. Používali jsme knihovnu flask-socketio a prošli jsme základní funkcionalitu. Tato knihovna používá koncept místností nebo rooms, který slouží k tomu, abychom uměli adresovat klienty v nějakých skupinách. Tento koncept se používá v chatových aplikacích, kde uživatelé vidí zprávy jen v místnosti, ve které se nacházejí. Nedostanou zprávy z žádné jiné. Podíváme se tedy na tento koncept a abychom udělali i nějaký reálný příklad, uděláme vlastní chatovací appku. Uživatelé se budou moci přidat do stávající místnosti, chatovat s ostatními, vytvářet nové místnosti a podobně. Bude to velice jednoduchý message board. 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/activateInstalujeme závislosti. Budeme používat totéž, co v předchozím článku. (venv)$ pip install flask, flask-socketioJedeme na boilerplatě pro naši appku. Struktura vypadá asi takto: ▾ websockets_message_board/ ▾ static/ ▾ css/ main.css ▾ js/ main.js ▾ templates/ board.jinja ▸ venv/ server.pySoubory main.css a main.js jsou zatím prázdné, slouží pouze jako placeholder. Pokračujeme tedy se souborem server.py a lze jej naplnit kódem. 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)Rozdíl oproti minimální flask appke je ten, že ji jinak spouštíme. Nepoužijeme if __name__ == '__main__': app.run()ale budeme ji spouštět přes socketIO. if __name__ == '__main__': socketio.run(app, debug=True)To proto, aby aplikace uměla spustit více vláken pro každého uživatele. Stejně tak je dobré vědět, že deployment na produkční server takové aplikace je trošku komplikovanější než když máme klasickou flask appku. Obsah základního templejtu board.jinja (i jediného, který budeme používat) je následující: <!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 pár důležitých importů jako socket.io, jquery a také css a js soubory naší appky. Takový jednoduchý základ můžeme spustit a uvidíme, jestli všechno šlape jak 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 není vůbec potřebný, ale jelikož všichni mají rádi hezké věci, nainstalujeme si css framework zvaný semantic-ui. Je to fajn framework, mám s ním dobré zkušenosti. Dokumentace je možná trošku těžší na pochopení, ale kromě toho to funguje a hlavně vypadá moc hezky. [Image] Stačí stáhnout toto zipko a integrovat do svého projektu. Je to velmi jednoduché. Zip rozbalíme a překopírujeme následující soubory • themes -> websockets_message_board/static/css/ • semantic.min.css -> websockets_message_board/static/css/ • semantic.min.js -> websockets_message_board/static/js/ Soubory semantic.min.js a semantic.min.css musím includnout na svou stránku, takže běžím do board.jinja a přihodím do hlavičky další řádky: <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é dát si pozor, abychom nejprve přidali jquery a až pak semantic.min.js, jinak se mi semantic-ui bude stěžovat, že neví najít jquery knihovnu. Ve složce themes jsou hlavně ikony a nějaké obrázky, které semantic-ui poskytuje. Po instalaci css frameworku můžu hned vidět změnu v podobě jiného fontu na mé smutné stránce. Nic jiného tam ještě není. UIUděláme nyní přibližný náčrt UI, abych věděl, jak appka asi bude vypadat a jaké funkce jí vlastně uděláme. Nebude to nic světoborného. Budeme mít jednu stránku kterou rozdělím na 3 sekce. Hlavní bude obsahovat zprávy, takže to bude můj message board. Boční panel bude obsahovat seznam místností, do kterých se budu umět přepínat. No a na spodní liště bude input pro moji zprávu.[Image] Zhmotním tuto svou představu do kódu. Otevřu board.jinja a naházím tam nějaké <div> elementy. Jelikož používáme semnatic-ui jako náš css framework, budu rovnou používat třídy v html. Použijeme grid systém, který nám usnadní práci při ukládání ui elementů. <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ůžu zkusit naplnit tyto části i nějakým obsahem. Jen tak ze zvědavosti, jak to bude vypadat. Všechno bude zatím jen tak naoko (prototypování). Začneme tím nejhlavnějším: message boardem <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šechny zprávy jsem obalil do div s id msg_board, abych pak jednoduše uměl přidávat nové zprávy do tohoto elementu.[Image] Uděláme totéž pro seznam místností. Rozhodl jsem se, že do tohoto postranního panelu strčíme i formulář pro změnu jména uživatele. Ten by měl mít možnost změnit své jméno. Bude to vypadat 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é jsem přidal <input /> na vytváření nových místností. Myslím, že takovou možnost by uživatel mohl mít. Poslední skládačkou bude input pro naše zprá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álně mi nebudou fungovat radio buttony, protože semantic-ui potřebuje tyto inicializovat v javascriptu. Pome tedy na to. Otevřeme main.js a píšeme $(document).ready(function(){ // UI HANDLERS $('.ui.radio.checkbox').checkbox(); });Stejně tak můžeme rovnou vybavit iniciální spojení přes websockety mezi klientem a serverem. $(document).ready(function(){ var url = location.protocol + "//" + document.domain + ":" + location.port; socket = io.connect(url); // UI HANDLERS $('.ui.radio.checkbox').checkbox(); });Posílání zpráv mohu rovnou i vyzkoušet v konzoli prohlížeče. Stačí otevřít developer tools, přejít na záložku console a tam už můžeme psát socket.emit("test", "hello there")[Image] Nicméně, nic se neděje, protože můj backend dosud není vůbec připraven. Vrhneme se tedy na server side a implementujeme místnosti – room. RoomsPřesuneme se do souboru server.py a přidáme handler pro základní eventy které budeme používat: 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í velmi jednoduše. Pokaždé se podívám na data, která mi přišla (proměnná data) a vytvořím jednoduchou zprávu, kterou pak broadcastuji na všechny uživatele v té dané místnosti. Pokud si už pořádně nepamatuješ, co dělal ten broadcast, vzpomínej z minulého blogu. Důležité je použití funkcí join_room a leave_room. Tyto pocházejí z knihovny flask-socketio, kterou jsme instalovali na začátku. Slouží k tomu, abychom přiřadili danou session do nějaké místnosti. Potom, když pošlu zprávu do místnosti, dostanou ji všichni v té místnosti. Je to fajn mechanismus jak kontaktovat jiné klienty a uspořádat si je do nějakých kategorií. rooms nemusím nutně používat jen na chatovou funkcionalitu. Mohu to použít k tomu, abych si seřadil uživatele do nějaké společné skupiny, které posílám barsjaká data. Dejme tomu, že bych měl appku o počasí, a nějaká skupina uživatelů by měla zájem o notifikace, jestli bude pršet. Tak tyto bych hodil do společné skupiny - místnosti - a notifikace bych posílal jen jim. Využití je tedy všelijaké. JavaScriptBackend byl v tomto případě docela jednoduchý a nepotřebovali jsme toho mnoho implementovat. Zprávy se od našeho backendu jen odrážejí jako od relátka, který je dále rozesílá klientům. Na straně klienta toho bude trošku více. Pokračujeme v souboru main.js. Nyní se pokusíme implementovat posílání zprávy a zobrazení příchozí zprá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čátek vytvoříme nějaké random uživatelské jméno a zvolíme default místnost "Lobby". To abychom s tímto neměli starosti zatím. Používáme k tomu pomocné funkce, které si implementujeme stranou, aby nám nezavazovaly. Jméno uživatele a název aktuální místnosti si udržuji v sessionStorage, což je fajn dočasné úložiště v prohlížeči. Přežije také reload stránky a navíc se mi tento způsob více líbí jak udržovat informaci v cookies. Když máme potřebná data, můžeme se hned na začátku bouchnout do nějaké místnosti. V javascriptu používáme knihovnu socket.io, která ale žádný koncept místností nezná. Pokud se podíváš do dokumentace(pozor! otevři si client api), zjistíš, že nic takového jako rooms se tam nezmiňuje. Takže to je věcička knihovny flask-socketio. Použijeme tedy klasický emit na handler join, který existuje na serveru. Tento řádek $("form#send_msg_to_room").submit( se pomocí jquery napíchne na formulář a zachytí odeslání formuláře. Pak můžu dělat co se mi zachce a nakonec vrátím false, takže formulář se reálně ani neodešle. Odeslání zprávy je přímočaré. Zjistím UserName, zjistím RoomName, vytáhnu si text zprávy a vše pošlu do funkce sendMessage. Tato již zajistí zabalení informací do jsonu a posílám pomocí funkce emit. Posílám na handler msg_board, který jsem si udělal před chvilkou. Zbývá mi vyřešit přijetí zprávy. To dělám pomocí funkce socket.on, kde dám kód, který bude proveden při přijetí zprávy. Tady si jednoduše (ale zato strašně ošklivě) slepím kus HTML, které pak strčím na konec elementu s id msg_board. Než to budeš zkoušet, je fajn si ještě vymazat ty fejkové zprávy, které jsme tam dali natvrdo do HTML. Takže mažeme tyto řádky <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 tedy jako další věc vybavit změnu uživatelského jména. $(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 jako při posílání zprávy, napíchnu se na HTML formulář a zpracuji ho ještě před odesláním. Změny uložím do sessionStorage. Přidal jsem ještě 2 vychytávky. • funkce updateHeading nastaví aktuální název místnosti a uživatele jako hlavičku stránky, • notifyNameChange dá všem uživatelům vědět, že si někdo změnil jméno. Jméno si už můžu měnit, ale oznámení o změně jsem nedostal. Na to ještě musíme doplnit 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); }); }); ...Nyní se nám začnou zobrazovat i systémové notifikace o tom, co se děje. Kdo vešel do místnosti, kdo ji opustil nebo kdo si změnil jméno. Poslední věcí, kterou musíme udělat, je selekce místností. Toto bude vyžadovat trošku více práce. Seznam stávajících místností si musíme udržovat na backendu. Ani na klientské části ani na backendu z knihovny flask-socketio neumím získat seznam všech místností. Musím si ho tedy udržovat 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 jsem si začal posílat nějaká data. Vyhodím tedy ty fejkové, které jsou tam natvrdo, a uděláme loop, ve kterém přidám všechny stávající místnosti. <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čuji v souboru main.js, kde si vytvořím funkce, které se postarají o změnu místnosti + pokud byla vytvořena nová, tak ji přidám do seznamu. $(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 zde několik pomocných funkcí, které mi pomáhají při výběru místnosti nebo při vytváření nové. Problematické části nastávají právě tehdy, když chci místnost i vytvářet. V podstatě ale nejde o žádné komplikované věci. Funkce selectCurrentRoom mi pomůže přehodit radio button při změně místnosti. Tím, že používáme semantic-ui, tak se nám to také trošku zkomplikovalo, ale výsledek stojí za to.[Image] ZávěrPostavili jsme takzvaný proof of concept, udělali jsme chatovací appku jen pomocí websocketů. Není to dokonalé a určitě je tam spousta much, to nám však nebránilo pochopit jak fungují websockety. Všechny zprávy žijí pouze v prohlížeči uživatele a nejsou uloženy na žádném serveru. Někdo to může považovat za chybu, někdo za fičúru. To už nechám na vás. Celý projekt se dá stáhnout zde. Zanedlouho se opět vrhneme na nějaké zajímavé téma ;)

Nezmeškej info o nových kurzech a speciálních nabídkách