Rad sa SQLite bazom u Androidu uz pomoć Room bibiloteke

Uvod

Room je bibloteka preporučena od strane Google-a kao jedna od komponenti tzv. “Android Architecture Components” pristupa. Room je wrapper oko SQLite baze i predstavlja sloj apstrakcije koji olakšava rad sa bazom podataka. Room bibilioteka preuzima na sebe većinu obaveza tako da sada lakše kreiramo tabele i upravljamo podacima.
Room ima tzv. compile-time checks tj. kontrolu koda pri kompajliranju i ukoliko postoji greška ona će se pokazati pri samom kompajliranju. Room nas na taj način spašava iritantnih sitnih grešaka koje nastaju u radu sa SQL bazom (nedostatak tačka zareza ili razmaka…) a koje izazivaju RunTimeException.

Da bi radili sa ovom bibliotekom potrebno je da se definišu dependencies:

room and MVVM diagram

Room biblioteka se sastoji iz tri glavne komponente:

Entity (tabela)

Entity je klasa koja predstavlja tabelu baze podataka a obeležava se sa @Entity. U nastavku ove anotacije (u okviru zagrada) možemo definisati naziv tabele koji može biti drugačiji od samog naziva klase. Ukoliko ne definišemo ime tabele na ovaj način, Room će generisati tabelu sa istim imenom kao što je naziv klase.

Svaki entitet mora imati konstruktor! Konstruktor se najčešće definiše tako da mu se parametri podudaraju sa poljima (na osnovu tipa i imena). Medjutim konstruktor ne mora da primi sva polja kao parametre, polje koje ne želimo da stavimo u konstruktor moramo da obeležimo sa anotacijom @Ignore u suprotnom će Android studio prikazati grešku. Takodje u okviru konstruktora ne moramo da stavimo polje koje se autogeneriše (tj. autoGenerate = true), ali za to polje mora da postoji public setter (mi ga nećemo koristi ali neophodan je Room biblioteci).

@PrimaryKey

U radu sa Room biblioteko jedna kolona mora da ima definisan tzv. PrimaryKey. Obeležavanje kolone koja će predstavljati “Primary key” se vrši sa dodavanjem anotacije @PrimaryKey kod tog polja. Kad definišemo da je neko polje PrimaryKey onda možemo da koristimo njegovu metodu autoGenerate() ili jednostavnije samo u zagradama da dodamo (autoGenerate = true).

Primer

U ovome primeru prvo polje mId se automatski generiše, pa ga nećemo ubaciti u konstruktor, ali je ipak neophodno da se obezbedi njegov setter jer je potreban Room biblioteci (za ostala polja to nisu neophodni setteri).

NAPOMENA:
Konstruktor eventualno može biti i bez ijednog argumenata ali tada moraju postojati definisani setteri za sva polja.

@ColumnInfo

Room po default-u generiše tabelu sa nazivom kolone istio kao i polje klase, medjutim ukoliko želimo da naziv kolone ima drugačiji naziv od naziva polja koje predstavlja, onda moramo koristiti @ColumnInfo anotaciju i u zagradi definisti drugačije ime kolone.

@Embedded

Sa ovom notacijom je moguće ugneziditi jednu tabelu unutar druge (one-one reacija).

Sada možemo praviti upite (query) u ovoj tabeli a User objekat ima sve kolone: id, firstName, street, state, city, i post_code.

NAPOMENA:
Možemo dodati prefiks svim nazivima kolona ubačene (embendded) tabel ako to definišemo u zagradi sa prefiks:

@ForeignKey

Sa ovom anotacijom povezujemo dva entiteta (tabele) povezujeći njihove kolone (kolona iz child entitea sa vrednosti kolone iz parent entiteta). Ovo je praktično dodatna anotacija u okviru @Entity anotacije child entiteta:

Primer

Prvo definišemo parent entitet (tabelu) Course.

Prethodna tabela može biti povezana sa više studenata, pa ćemo za definisanje child tabele Student koristiti anotaciju @ForeignKey:

U ovome primeru smo povezali vrednosti id kolone User tabele sa vrednosti userId Repo tabele. Značenje dodeljenih vrednosti za onDelete/onUpdate su sledeće:

  • int CASCADE – Akcija „CASCADE“ propagira operaciju brisanja ili ažuriranja nadređenog ključa na svaki zavisni podređeni ključ.
  • int NO_ACTION – Podrazumevano ponašanje kada se roditeljski ključ modifikuje ili izbriše iz baze podataka, i ne preduzimaju se neke druge posebne radnje.
  • int RESTRICT – Akcija RESTRICT znači da je aplikaciji zabranjeno brisanje (za onDelete ()) ili menjanje (za onUpdate ()) nadređenog ključa kada postoji jedan ili više podređenih ključeva koji su na njega preslikani.
  • int SET_DEFAULT – Akcije “SET DEFAULT” slične su SET_NULL, osim što je svaki od podređenih stupaca ključa postavljen tako da sadrži podrazumevanu vrednost stupaca umesto NULL.

NAPOMENA:
Poznato je da kreiranje ovakve konekcije ne mora da vodi do relacije izmedju tih tabela, već samo pomaže da se jasno definiše šta će se desiti sa “child entitetom” kada se neki član “parent entiteta” obriše (onDelete) ili ažurira (onUpdate).

@Relations

Sa ovom anotacijom je omogućeno povezivanje dve tabele bez korišćenja @Foreign.

Primer

U prethodnom primeru je prikazano povezivanje dve tabele uz pomoć anotacije @ForeignKey, isti zahtev možemo rešiti bez Foreign kluča koristeći drugu notaciju pod imenom @Relation. Za to je neophodno kreirati novu klasu sa kojom možemo napraviti instancu koja sadrži i parent entity instancu i listu child entity instanci:

Kasnije se za kreira DAO na sledeći način:

Više o ovome u narednoj sekciji.

Data Access Object – DAO

DAO (data access object) kao što mu i samo ime kaže je: objekat za pristup podacima. DAO mora biti ili interfejs ili abstraktna klasa, tj. njegove metode nemaju telo jer će Room generisati sav nepohodan kod u zavisnosti od anotacije (@Insert, @Delete, @Query…). Upravo na taj način se smanjuje količina kod-a koju je neophodno da kreira programer.
Takodje velika prednost ovog objekta je mogućnost da validira SQL naredbe u toku kompajliranja (eng. “at compile-time”) i na taj način nam pravovremeno ukaže za greške što nije bilo moguće u radu sa SQLiteOpenHelper klasom.

@Dao

Sa ovom anotacijom se Room-u daje do znanja da je dati interfejs ili abstraktna klasa ustvari DAO. Generalno se za svaki entitet pravi njegov DAO, pa bi za naš entitet iz primera “BuyItem” Dao ovako izgledao:

@Insert

Sa ovom anotacijom se obeležava metoda koja je zadužena za unos podataka u bazu.

Ovo je sasvim dovoljno da zameni celu metodu insertItemToDB() koju smo koristili u okviru primera iz SQLiteOpenHelper klase:

@Delete

Sa ovom anotacijom se obeležava metoda koja je zadužena za brisanje podataka iz baze.

Ovo je sasvim dovoljno da zameni celu metodu removeItemFromDB() koju smo koristili u okviru primera iz SQLiteOpenHelper klase:

@Query

Sa ovom anotacijom se obeležava metoda koja je zadužena za dobijanje podataka iz baze u zavisnosti od definisnog uslova (query-ja):

Pri pisanju ovog queryja možete primetiti da Android studio ukazuje na greške ib nudi rešenja i promenjive. A ova dva reda su sasvim dovoljno da zamene celu metodu getAllItemsFromDB() koju smo koristili u okviru primera iz SQLiteOpenHelper klase.

Prosledjivanje parametra u query

Često je potrebno proslediti neki parametar sa kojim se filtira query pa to izgleda kao u sledećem primeru:

Primer

Čak možemo proslediti više parametara kao u sledećem primeru:

Primer

Takodje možemo proslediti kao parametari i neku kolekciju (može da vraća LiveData objekat):

NAPOMENA:
Kada prosleđujete podatke kroz slojeve arhitekture aplikacije (iz baze podataka Room, kroz Repository klasu, dalje zatim kroz ViewModel klasu sve do korisničkog interfejsa tj. View klase (aktivnost ili fragmenta), ti podaci moraju biti LiveData u svim slojevima, ili drugim rečima svi podaci koje Room šalje iz DAO kroz neki query u Repository, a zatim iz Repository u VievModel, moraju biti LiveData. Objašnjenje ovoga leži u tome da nigde u aplikaciji nemamo potrebu da setujemo to radi Room za nas, tako da nam ne treba nigde MutableLiveData objekat (koji za razliku od LiveData objekta ima public settere) metode).

Primer

Ovako izgleda jedan tipičan Dao interfejs:

DataBase

Klasa koja predstavalja Room bazu podataka mora da bude abstract i da ekstenduje klasu RoomDatabase:

Pored ovoga je potrebno da obeležimo ovu klasu da bi Room znao o kojoj klasi je reč, a to se postiže kroz anotaciju @Database. U nastavku ove anotacije (u zagradi) definišemo koje sve entitete (tabele) ova baza sadrži, kao i trenutnou verziju baze:

Kreiranje baze

Pri kreiranju baza je “pametno” da koristimo singleton patern. Baza se kreira uz pomoć Room metode pod nazivom databaseBuilder(). Ova metoda prihvata parametre: context, “klasu koja definiše bazu” i naziv baze. Da bi se baza kreirala i inicijalizovala potrebno je da pozovemo metodu build():

Ako želimo da sprečimo probleme pri migraciji baza onda je dobro da pozovemo metodu fallbackToDestructiveMigration().

NAPOMENA:
Ako radi debuging-a želite da pristupite bazi (koristeći DeviceFileExplore) i da je pregledate koristeći DB Browser for SQLite ili neku sličnu aplikaciju, potrebno je da pri kreiranju baze pozovete i metodu setJournalMode(JournalMode.TRUNCATE), u protivnom će baza koju pregledate će biti prazna.

Pored ovaga potrebno je kreirati abstraktnu metodu koja će da vraća odgovarajući Dao objekat:

Primer

Ovako izgleda jedna klasa u celosti:

Repository

Iako ova klasa ne pripada direktno Room biblioteci već je hijerarhijski iznad nje, ipak u ovome članku će biti obradjena zbog toga što Room biblioteka ne dozvoljava izvršenje operacija nad bazom iz glavnog threed-a! Zbog ovoga razloga sve metode vezane za CRUD operacije moraju da se izvršavaju na background thread-u, a jedini izuzetak je metoda koja vraća LiveData objekat, jer o njoj Room vodi računa i automatski je poziva iz background thread-a.

Jedan od načina da se neka metoda izvrši na background thread-u je da ona extenduje AsyncTask klasu, medjutim od verzije androida 11 (API 30) je klasa AsyncTask ukinuta, te je neophodno koristiti drugi pristup (pogledajte ovde kako bi izgledala naša Repository klasa ako bi koristili AsyncTask metode). Pošto moramo da izbegnemo korišćenje AsyncTask, drugi način za izvršenje je korišćenje Executor objekta.

Exexutor servis se kreira u Database klasi uz definisanje neohodnog broja thread-ova:

Sada u okviru naše repository klase možemo iskoristi ExecutorService i izvršiti DAO metodu asihorono sa backround thread-a.

Primer

Ceo projekat iz primera možete naći na GIthub-u pod naziom “sqliteWithRoomLib”.

×

Primer

×

×

×

×

fallbackToDestructiveMigration()

Ova metoda je zadužena da migraciju baze kada menjamo verziju baze i ima sličnu ulogu kao OnUpgrade() metoda iz klase SQLiteOpenHelper.

Ova metoda omogućava Room biblioteci da destruktivno ponovo kreira tabele baze podataka ako nisu pronađene migracije koje bi migrirale stare šeme baze podataka na najnoviju verziju šeme. Više o migraciji sa Room biblotekom pročitajte ovde.

×

Primary key

Primary key je kolona koja predstavlja jedinstveni indetifiaktor reda u tabeli baze podataka. Svaka tabela mora da ima bar jedan Primary key koji ne sme biti NULL. Primary key se definiše pri kreiranju tabele na sledeći način:

Druga sintaksa za definisanje Primary key-a ovako izgleda:


Sistemi za upravljanje SQL bazama (DBMS)

Uvod

mysql

Pre svega treba napraviti razliku izmedju pojmova: “baza podataka”, “sistem za upravljanje bazama podataka” (tzv.DBMS “Database Management System”) i samog “SQL jezika”.
“Relacijska baza podataka” je digitalna baza podataka na osnovu relacijskog modela podataka i predstavlja skup povezanih podataka koji zamenjuju neki aspekt stvarnog sveta.
“Relaciski sistem za upravljanje bazama podataka” (RDBMS) je softver koji se koristi za održavanje relacijskih baza podataka. On pruža interfejs između podataka i softverske aplikacije tj. prihvata zahtev za podacima iz aplikacije i nalaže operativnom sistemu da pruži određene podatke.
SQL (Structured Query Language) je jezik koji se koristi pri upravljanju relacionim bazama. Ovaj jezik je dizajniran da korisniku omogući čuvanje, pronalaženje, upravljanje ili manipulisanje podacima unutar sistema za upravljanje bazama
podataka (DBMS).

SQLlite

SQLite je “file-based” sistem koji ne zahteva nikakvu instalaciju ili podešavanje izuzetno male veličine oko 700KB. To znači da se aplikacija ne pokreće u okviru odvojenog procesa servera koji treba pokrenuti, zaustaviti ili konfigurisati. Ova arhitektura bez servera omogućava da baza podataka bude kompatibilna sa više platformi.

Kompletna baza podataka SQL sadržana je u jednoj datoteci diska i sva čitanja i upisivanja odvijaju se direktno na ovoj datoteci diska. Budući da baza podataka SQLite ne zahteva administraciju, ona dobro funkcioniše na uređajima koji moraju da rade bez ljudske podrške. SQLite je pogodan za upotrebu u mobilnim telefonima, set-top box uređajima, televizorima, igraćim konzolama… Ovo je jednostavan sistem koji podržava samo pet tipova podataka: BLOB, NULL, INTEGER, TEKST, REAL (dok napreednije dbms podržavaju skoro sve moguće tipove). Upravo njegova jednostavnost omogućava da za razliku od drugih naprednijih dbms-a bude veoma brz.

sqlite

Jedan od glavnih nedostataka SQLite sistema je nedostatak mogućnosti rada sa više korisnika koje se mogu naći u RDBMS sistemima kao što su MySQL i PostgreSQL. Pošto radi samo sa jednim korisnikom SQLite ne pruža sistem za potvrdu identiteta pri pristupu datoteci baze podataka. Još jedan veliki nedostatak SQLite-a je rukovanje operacijama upisivanja koje su serializovane. Ovo može biti glavno usko grlo za aplikacije koje zahtevaju istovremeno izvršavanje operacija.

MySQL

Za razliku od SQLite, MySQL koristi arhitekturu server/klijent koja se sastoji od SQL servera sa više niti. Ova višenitna priroda MySQL-a omogućava veće performanse jer niti jezgra mogu lako koristiti više CPU-a. Ovaj sistem ima sigurnosne funkcije koje omogućavaju autentifikaciju korisnika, sistem upravljanja korisničkim računom i šifrovane veze pomoću SSL-a. Za razliku od SQLlite MySQL podržava rada sa više korisnika. Ovaj sistem je dostupan na svim većim platformama: Windows, Linux i Mac OS X.

Mana MySQL-a je ta da veći broj INSERT-ova može imati negativan uticaj na performanse (u ovome je odličan PostgreSQL). Takođe ne radi dobro sa dugotrajnim SELECT-ima te je u slučaju MySQL-a najbolje koristi manje SELECT-e.

PostgreSQL

postgresql

Kao i MySQL, PostgreSQL koristi model baze podataka tipa klijent/server. PostgreSQL ima mogućnost da obrađuje istovremene klijentske sesije stvaranjem („račvanje“) novog procesa za svaku vezu. Poput MySQL, PostgreSQL takođe ima nekoliko naprednih funkcija vezanih za sigurnosti i replikaciju. Zbog svojih superiornih mogućnosti paralelne obrade, PostgreSQL je odličan (za razliku od MySQL-a) kada pokreće dugačke SELECT-ove. Kada se poredi sa MySQL-om u potrošnji energije tu je značajno lošiji i ima veliku potrošnju. Još jedan veliki nedostatak može se uočiti tokom čestih UPDATE-a, gde zbog nepodržane grupisanih indeksa PostgreSQL može imati negativan uticaj na performanse u poređenju sa MySQL bazama.

Microsoft SQL Server

microsoft sql server

Ovo je Microsoft-ov sistem za upravljanje relacionim bazama koj takodje koristi model baze podataka tipa klijent/server. Prednosti ovog sistema su relativno jednostavno održavanje, lak za upotrebu i razumevanje, a pristup i administracija su vrlo jednostavni. Može se koristiti kod malih i velikih projekata. SQL Server je razvio Microsoft prvobitno samo za operativni sistem Vindovs, mada je Microsoft objavio odluku da RDBMS učini dostupnim i na Linuksu kao i na Mac OS (putem Dockera).

Jedna od mana ovoga sistem je što novije verzije trebaju napredne tehnologije za pokretanje. Dakle, ako se vaš hardver sastoji uglavnom od starijeg hardvera, možda ćete morati da investirate u novije mašine da biste koristili Microsoft SQL Server.