Realm este cea mai bună soluție de bază de date Android

Publicat: 2022-03-11

De când a fost creat Android, noi, dezvoltatorii de aplicații, am folosit SQLite pentru a stoca datele noastre locale. Uneori direct cu instrucțiuni SQL, uneori folosind un Object-Relational Mapper (ORM) ca strat de abstractizare, dar în orice caz, am folosit SQLite la sfârșitul zilei.

Cu toate acestea, în ciuda tuturor avantajelor SQLite, au existat momente în care ne-am dorit să avem alternative la un model relațional: ceva care ne-ar putea scuti de a adăuga cod standard pentru a converti valorile în și din baza de date sau ne-ar permite să omitem configurarea mapărilor. între clase și tabele, câmpuri și coloane, chei externe etc.

Cu alte cuvinte, o bază de date cu structuri de date mai asemănătoare cu cele pe care le folosim de fapt la nivel de aplicație. Mai bine, dacă ar putea fi eficient în memorie prin design, permițând experiențe mai bune în dispozitivele cu resurse limitate, ar fi minunat.

Acestea sunt, de fapt, câteva dintre beneficiile ieșite din cutie pe care le obținem cu Realm, o platformă de baze de date cu o arhitectură distinctă, care a apărut ca o nouă alternativă la SQLite.

Acest articol prezintă câteva dintre principalele motive pentru care Realm a atras atât de multă atenție și de ce ați putea dori să vă gândiți să îl încercați. Acesta discută câteva dintre avantajele cheie pe care Realm le oferă dezvoltatorilor Android față de SQLite.

Deoarece Realm este disponibil pe mai multe platforme, o parte din ceea ce va fi tratat în acest articol are relevanță și pentru alte platforme mobile, cum ar fi iOS, Xamarin și React Native.

SQLite: Funcționează, dar nu este ceea ce aveți nevoie de cele mai multe ori

Majoritatea dezvoltatorilor de telefonie mobilă sunt probabil familiarizați cu SQLite. Există din 2000 și este, fără îndoială, cel mai folosit motor de baze de date relaționale din lume.

SQLite are o serie de beneficii pe care le recunoaștem cu toții, dintre care unul este suportul său nativ pe Android.

Faptul că este o bază de date relațională SQL standard minimizează și curba de învățare pentru cei care provin dintr-o bază de date relațională. De asemenea, oferă performanțe destul de bune dacă este folosit la întregul său potențial (funcții de levier, cum ar fi extrasele pregătite, operațiuni în bloc cu tranzacții etc.). Deși SQLite poate să nu se scaleze foarte bine pentru toate nevoile dvs.

Cu toate acestea, tratarea directă cu instrucțiunile SQL are o serie de dezavantaje.

Conform documentației oficiale Android, iată pașii necesari pentru a începe citirea/scrierea în SQLite:

  1. Descrieți schema dvs. în termeni de clase de contract.
  2. Definiți comenzile de creare/eliminare a tabelului în șiruri.
  3. Extindeți SQLiteOpenHelper pentru a rula comenzi de creare și a gestiona upgrade-urile/downgrade-urile.

După ce ați făcut acest lucru, veți fi gata să citiți și să scrieți în baza de date. Cu toate acestea, va trebui să convertiți înainte și înapoi între obiectele din aplicația dvs. și valorile din baza de date. Pe scurt: este o mulțime de cod standard!

O altă problemă este mentenabilitatea. Pe măsură ce proiectul dvs. crește și apare nevoia de a scrie interogări mai complexe, veți ajunge cu bucăți mari de interogări SQL brute în șiruri. Dacă mai târziu trebuie să schimbați logica acelor interogări, poate fi o bătaie de cap.

În ciuda dezavantajelor sale, există cazuri în care utilizarea SQL brut este cea mai bună opțiune. Un exemplu este atunci când dezvoltați o bibliotecă în care performanța și dimensiunea sunt factori critici și adăugarea unei biblioteci terță parte ar trebui evitată dacă este posibil.

Object-Relational Mapper: Ajutorul pentru provocările SQL

Pentru a ne salva de a avea de a face cu SQL brut, ORM-urile au venit în ajutor.

Unele dintre cele mai cunoscute ORM-uri Android sunt DBFlow, greenDAO și OrmLite.

Cea mai mare valoare pe care o aduc este abstractizarea SQLite, permițându-ne să mapam relativ ușor entitățile bazei de date la obiecte Java.

Printre alte beneficii, dezvoltatorii de aplicații ajung să lucreze cu obiecte, o structură de date mult mai familiară. De asemenea, ajută la mentenanță, deoarece acum manipulăm obiecte de nivel înalt cu o tastare mai puternică și lăsăm munca murdară în seama bibliotecilor. Mai puține probleme cu construirea de interogări prin concatenarea șirurilor de caractere sau gestionarea manuală a conexiunii cu baza de date. Mai puține greșeli de scriere.

Deși este un fapt că aceste ORM-uri au ridicat ștacheta bazelor de date Android, au și dezavantajele lor. În multe cazuri, ajungi să încarci date inutile.

Iată un exemplu.

Să presupunem că aveți un tabel cu 15 coloane și, într-un anumit ecran al aplicației dvs., este afișată o listă de obiecte din acest tabel. Această listă afișează valori din doar trei coloane. Prin urmare, încărcând toate datele din rândul tabelului, ajungeți să aduceți de cinci ori mai multe date decât aveți nevoie de fapt pentru acel ecran.

Adevărul să fie spus, în unele dintre aceste biblioteci puteți specifica ce coloane doriți să preluați în avans, dar pentru asta trebuie să adăugați cod suplimentar și, chiar și așa, asta nu va fi suficient în cazul în care puteți ști doar exact ce coloane veți utilizați după ce vă uitați la datele în sine: unele date ar putea fi oricum încărcate inutil.

În plus, există adesea scenarii în care aveți interogări complexe de făcut, iar biblioteca dvs. ORM pur și simplu nu vă oferă o modalitate de a descrie aceste interogări cu API-ul său. Acest lucru vă poate face să scrieți interogări ineficiente care fac mai multe calcule decât ceea ce aveți nevoie, de exemplu.

Consecința este o pierdere de performanță, ceea ce vă face să recurgeți la SQL brut. Deși acest lucru nu este o problemă pentru mulți dintre noi, dăunează scopului principal al mapării obiect-relaționale și ne duce înapoi la unele dintre problemele menționate mai sus referitoare la SQLite.

Tărâmul: o alternativă perfectă

Realm Mobile Database este o bază de date concepută pentru dispozitive mobile de la zero.

Diferența cheie dintre Realm și ORM-uri este că Realm nu este o abstracție construită pe SQLite, ci un motor de bază de date complet nou. Mai degrabă decât un model relațional, se bazează pe un depozit de obiecte. Nucleul său constă dintr-o bibliotecă C++ autonomă. În prezent, acceptă Android, iOS (Objective-C și Swift), Xamarin și React Native.

Realm a fost lansat în iunie 2014, așa că în prezent are doi ani și jumătate (destul de nou!).

În timp ce tehnologiile de baze de date pe server treceau printr-o revoluție din 2007, cu multe noi apărute, tehnologia bazelor de date pentru dispozitivele mobile a rămas blocată cu SQLite și wrapper-urile sale. Aceasta a fost una dintre motivațiile cheie pentru a crea ceva de la zero. În plus, după cum vom vedea, unele dintre caracteristicile Realm au necesitat modificări fundamentale ale modului în care o bază de date se comportă la un nivel scăzut, iar asta nu a fost posibil să construim ceva pe deasupra SQLite.

Dar oare Realm merită cu adevărat? Iată principalele motive pentru care ar trebui să luați în considerare adăugarea Realm la centura dvs. de instrumente.

Modelare usoara

Iată un exemplu de câteva modele create cu Realm:

 public class Contact extends RealmObject { @PrimaryKey String id; protected String name; String email; @Ignore public int sessionId; //Relationships private Address address; private RealmList<Contact> friends; //getters & setter left out for brevity }
 public class Address extends RealmObject { @PrimaryKey public Long id; public String name; public String address; public String city; public String state; public long phone; }

Modelele dvs. se extind de la RealmObject. Realm acceptă toate tipurile primitive și tipurile sale în casete (cu excepția char ), String , Date și byte[] . De asemenea, acceptă subclasele RealmObject și RealmList<? extends RealmObject> RealmList<? extends RealmObject> pentru a modela relațiile.

Câmpurile pot avea orice nivel de acces (privat, public, protejat etc.). Toate câmpurile sunt persistente în mod implicit și trebuie doar să adnotați câmpurile „speciale” (de exemplu, @PrimaryKey pentru câmpul cheie primară, @Ignore pentru a seta câmpuri nepersistente etc.).

Lucrul interesant despre această abordare este că menține clasele mai puțin „poluate” în comparație cu ORM-uri, deoarece în cele mai multe dintre ele aveți nevoie de adnotări pentru a mapa clasele la tabele, câmpuri obișnuite la coloanele bazei de date, câmpuri cheie străine la alte tabele și așadar. pe.

Relații

Când vine vorba de relații, există două opțiuni:

  • Adăugați un model ca câmp din alt model. În exemplul nostru, clasa Contact conține un câmp Address și care definește relația lor. Este posibil ca un contact să aibă o adresă, dar nimic nu împiedică adăugarea aceleiași adrese la alte persoane de contact. Acest lucru permite relații unu-la-unu și unu-la-mulți.

  • Adăugați o RealmList a modelelor la care se face referire. RealmLists se comportă destul de ca vechile Lists Java bune, acționând ca un container de obiecte Realm. Putem vedea că modelul nostru de Contact are o RealmList de contacte, care sunt prietenii ei în acest exemplu. Relațiile unu-la-mulți și mulți-la-mulți pot fi modelate cu această abordare.

Îmi place acest mod de a reprezenta relațiile pentru că ni se pare foarte natural pentru noi, dezvoltatorii Java. Adăugând aceste obiecte (sau liste ale acestor obiecte) direct ca câmpuri ale clasei noastre, la fel cum am face pentru alte clase non-model, nu trebuie să ne ocupăm de setările SQLite pentru cheile străine.

Avertisment: Nu există suport pentru moștenirea modelului. Soluția actuală este utilizarea compoziției. Deci, dacă, de exemplu, aveți un model Animal și sperați să creați un model Dog care se extinde din Animal , va trebui în schimb să adăugați o instanță Animal ca câmp în Dog . Există o mare dezbatere despre Compoziție vs. Moștenire. Dacă vă place să folosiți moștenirea, acesta este cu siguranță ceva ce trebuie să știți despre Realm. Cu SQLite, acest lucru ar putea fi implementat folosind două tabele (unul pentru părinte și unul pentru copil) conectate printr-o cheie străină. De asemenea, unele ORM-uri nu impun această restricție, cum ar fi DBFlow.

Preluați numai datele de care aveți nevoie! Design fără copie

Aceasta este o caracteristică ucigașă.

Realm aplică conceptul de design zero-copy, ceea ce înseamnă că datele nu sunt niciodată copiate în memorie. Rezultatele pe care le obțineți dintr-o interogare sunt de fapt doar indicii către datele reale. Datele în sine sunt încărcate leneș pe măsură ce le accesați.

De exemplu, aveți un model cu 10 câmpuri (coloane în SQL). Dacă interogați obiectele acestui model pentru a le afișa listate pe un ecran și aveți nevoie doar de trei din cele 10 câmpuri pentru a completa elementele din listă, acestea vor fi singurele câmpuri preluate.

În consecință, interogările sunt extraordinar de rapide (vezi aici și aici pentru câteva rezultate de referință).

Acesta este un mare avantaj față de ORM-urile care încarcă de obicei toate datele din rândurile SQL selectate în avans.

Încărcarea ecranului devine mult mai eficientă ca rezultat, fără a necesita niciun efort suplimentar din partea dezvoltatorului: este doar comportamentul implicit al Realm.

În plus, acest lucru înseamnă, de asemenea, că aplicațiile consumă mai puțină memorie și, având în vedere că vorbim despre un mediu cu resurse limitate, cum ar fi dispozitivele mobile, asta poate face o mare diferență.

O altă consecință a abordării zero-copy este că obiectele gestionate de Realm sunt actualizate automat.

Datele nu sunt niciodată copiate în memorie. Dacă aveți rezultate dintr-o interogare și un alt fir a actualizat aceste date în baza de date după interogarea dvs., rezultatele pe care le aveți vor reflecta deja aceste modificări. Rezultatele dvs. sunt doar indicii către datele reale. Deci, atunci când accesați valori din câmpuri, sunt returnate cele mai actualizate date.

Ilustrație: Accesarea datelor despre tărâm din mai multe obiecte și fire.

Dacă ați citit deja date de la obiecte Realm și le-ați afișat pe ecran, de exemplu, și doriți să primiți actualizări pentru când datele de bază se schimbă, puteți adăuga un ascultător:

 final RealmResults<Contact> johns = realm.where(Contact.class).beginsWith("name", "John ").findAll(); johns.addChangeListener(new RealmChangeListener<RealmResults<Contact>>() { @Override public void onChange(RealmResults<Contact> results) { // UPDATE UI } });

Nu este doar un înveliș

Deși avem zeci de opțiuni pentru ORM-uri, acestea sunt wrapper-uri și totul se reduce la SQLite de dedesubt, ceea ce limitează cât de departe pot ajunge. În schimb, Realm nu este doar un alt wrapper SQLite. Are libertatea de a oferi funcții pe care ORM-urile pur și simplu nu le pot oferi.

Una dintre schimbările fundamentale cu Realm este capacitatea de a stoca date ca un depozit de grafice de obiecte.

Aceasta înseamnă că Realm este obiecte până în jos, de la nivelul limbajului de programare până la baza de date. În consecință, se fac mult mai puține conversii înainte și înapoi pe măsură ce scrieți și citiți valori, în comparație cu o bază de date relațională.

Structurile bazelor de date reflectă mai îndeaproape structurile de date pe care le folosesc dezvoltatorii de aplicații. De fapt, acesta este unul dintre motivele majore pentru care se îndepărtează de modelarea relațională și se îndreaptă spre modele agregate pentru dezvoltarea pe server. Realm aduce în sfârșit câteva dintre aceste idei în lumea dezvoltării mobile.

Dacă ne gândim la componentele din arhitectura Realm, în partea de jos se află nucleul său cu cea mai fundamentală implementare a platformei. În plus, vom avea biblioteci obligatorii pentru fiecare platformă acceptată.

Ilustrație: Arhitectura tărâmului de la bază la diferite biblioteci.

Când utilizați un înveliș pentru o tehnologie asupra căreia nu aveți control, în cele din urmă trebuie să furnizați un fel de strat de abstractizare în jurul acestuia.

Bibliotecile de legare a tărâmului sunt concepute pentru a fi cât mai subțiri posibil, pentru a elimina complexitatea abstracției. Ei propagă în mare parte ideea de design de la Core. Deținând controlul asupra întregii arhitecturi, aceste componente funcționează mai bine sincronizate unele cu altele.

Un exemplu practic este accesul la alte obiecte la care se face referire (chei externe în SQL). Structura de fișiere a Realm se bazează pe legături native, așa că atunci când interogați relații, în loc să trebuiască să traduceți o abstractizare ORM în relaționale și/sau să vă alăturați mai multor tabele, obțineți legături brute către obiecte la nivel de sistem de fișiere în format de fișier.

Sunt obiecte care indică direct către alte obiecte. Astfel, interogarea unei relații este aceeași cu interogarea unei coloane întregi, de exemplu. Nu este nevoie de operațiuni costisitoare care traversează chei străine. Totul se referă la urmărirea indicațiilor.

Comunitate și asistență

Realm este în curs de dezvoltare activă și a lansat destul de des versiuni actualizate.

Toate componentele din baza de date Realm Mobile sunt open-source. Sunt foarte receptivi la instrumentul de urmărire a problemelor și la Stack Overflow, așa că vă puteți aștepta la un suport bun și rapid pe aceste canale.

De asemenea, feedback-ul din partea comunității este luat în considerare la prioritizarea problemelor (bugi, îmbunătățiri, solicitări de funcții etc.). Este întotdeauna bine să știi că poți avea un cuvânt de spus în dezvoltarea instrumentelor pe care le folosești.

Am început să folosesc Realm în 2015 și, de atunci, m-am lovit de mai multe postări pe web cu diverse opinii despre Realm. Vom vorbi în curând despre limitările sale, dar un lucru pe care l-am observat este că multe dintre reclamațiile făcute la momentul postării au fost remediate de atunci.

Când am cunoscut despre Realm, de exemplu, nu exista încă suport pentru metode personalizate pe modele și apeluri asincrone. Acestea au fost rupturi pentru mulți la acea vreme, dar ambele sunt acceptate în prezent.

O astfel de viteză de dezvoltare și capacitate de răspuns ne face mai încrezători că nu vom aștepta mult timp pentru funcții importante.

Limitări

Ca în orice în viață, Tărâmul nu este doar trandafiri. Pe lângă limitarea moștenirii menționată anterior, există și alte neajunsuri de avut în vedere:

  • Deși este posibil să existe mai multe fire de execuție care citesc și scriu în baza de date în același timp, obiectele Realm nu pot fi mutate pe fire . Deci, dacă, de exemplu, recuperați un obiect de tărâm folosind doInBackground() al lui AsyncTask, care rulează într-un fir de execuție de fundal, nu puteți transmite această instanță metodelor onPostExecute() , deoarece acestea rulează pe firul principal. Soluțiile posibile pentru această situație ar fi fie să faceți o copie a obiectului și să o transmiteți, fie să transmiteți id-ul obiectului și să preluați obiectul din nou pe onPostExecute() . Realm oferă metode sincrone și asincrone pentru citire/scriere.

  • Nu există suport pentru cheile primare cu incrementare automată , așa că va trebui să vă gestionați singur generarea acestora.

  • Nu este posibil să accesați baza de date din procese distincte în același timp . Conform documentației lor, suportul pentru mai multe procese va veni în curând.

Realm este viitorul soluțiilor de baze de date mobile

SQLite este un motor de baze de date solid, robust și dovedit, iar bazele de date relaționale nu vor dispărea prea curând. Există o serie de ORM-uri bune care vor face truc și pentru multe scenarii.

Cu toate acestea, este important să fii la curent cu tendințele actuale.

În acest sens, cred că Realm este una dintre cele mai mari tendințe viitoare din ultimii ani când vine vorba de dezvoltarea bazelor de date mobile.

Realm aduce cu sine o abordare unică de a trata datele care sunt valoroase pentru dezvoltatori, nu numai pentru că poate fi o opțiune mai bună decât soluțiile existente, ci și pentru că ne lărgește orizonturile în ceea ce privește noi posibilități și ridică ștacheta tehnologiei bazelor de date mobile.

Ai deja experiență cu Realm? Vă rugăm să nu ezitați să vă împărtășiți gândurile.