Android
Królestwo
Szukaj…
Wprowadzenie
Realm Mobile Database jest alternatywą dla SQLite. Realm Mobile Database jest znacznie szybsza niż ORM i często szybsza niż surowy SQLite.
Korzyści
Funkcjonalność offline, Szybkie zapytania, Bezpieczne wątki, Aplikacje wieloplatformowe, Szyfrowanie, Architektura reaktywna.
Uwagi
Korzystając z Realm, musisz pamiętać, że nie wolno przekazywać RealmObjects, RealmResults i instancji Realm między wątkami. Jeśli potrzebujesz zapytania do danego wątku, otwórz instancję Realm w tym wątku. Po zakończeniu wątku powinieneś zamknąć królestwo.
UWAGA PRAWNA : Użytkownik rozumie, że Oprogramowanie może zawierać funkcje kryptograficzne, które mogą podlegać ograniczeniom eksportowym, oraz oświadcza i gwarantuje, że nie znajduje się w kraju objętym ograniczeniami eksportowymi lub embargiem Stanów Zjednoczonych, w tym Kubą, Iranem, Północą Korei, Sudanu, Syrii lub regionu Krymu oraz że nie znajdujesz się na liście Departamentu Handlu osób odmówionych, niezweryfikowanych stron lub powiązanych z podmiotem objętym ograniczeniami.
Dodanie królestwa do twojego projektu
Dodaj następującą zależność do pliku build.gradle
poziomie projektu .
dependencies {
classpath "io.realm:realm-gradle-plugin:3.1.2"
}
Dodaj następujące polecenie u góry pliku build.gradle
poziomie aplikacji .
apply plugin: 'realm-android'
Wykonaj synchronizację stopniową, a teraz dodałeś Realm jako zależność do swojego projektu!
Realm wymaga początkowego wywołania od wersji 2.0.0 przed jego użyciem. Możesz to zrobić w klasie Application
lub w metodzie onCreate
pierwszej aktywności.
Realm.init(this); // added in Realm 2.0.0
Realm.setDefaultConfiguration(new RealmConfiguration.Builder().build());
Modele królestwa
Modele dziedzin muszą rozszerzać RealmObject
bazową RealmObject
, definiują schemat bazowej bazy danych.
Obsługiwane typy pól są boolean
, byte
, short
, int
, long
, float
, double
, String
, Date
, byte[]
, linki do innych RealmObject
s, a RealmList<T extends RealmModel>
.
public class Person extends RealmObject {
@PrimaryKey //primary key is also implicitly an @Index
//it is required for `copyToRealmOrUpdate()` to update the object.
private long id;
@Index //index makes queries faster on this field
@Required //prevents `null` value from being inserted
private String name;
private RealmList<Dog> dogs; //->many relationship to Dog
private Person spouse; //->one relationship to Person
@Ignore
private Calendar birthday; //calendars are not supported but can be ignored
// getters, setters
}
Jeśli dodasz (lub usuniesz) nowe pole do swojego RealmObject (lub dodasz nową klasę RealmObject lub usuniesz istniejącą), konieczna będzie migracja . Można też ustawić deleteIfMigrationNeeded()
w RealmConfiguration.Builder
lub określić konieczną migrację. Migracja jest również wymagana przy dodawaniu (lub usuwaniu) @Required
, @Index
lub @PrimaryKey
.
Relacje muszą być ustawiane ręcznie, NIE są one automatyczne na podstawie kluczy podstawowych.
Od wersji 0.88.0 w klasach RealmObject można również używać pól publicznych zamiast pól prywatnych / pobierających / ustawiających.
Możliwe jest także zaimplementowanie RealmModel
zamiast rozszerzenia RealmObject
, jeśli klasa jest również opatrzona adnotacjami @RealmClass
.
@RealmClass
public class Person implements RealmModel {
// ...
}
W takim przypadku metody takie jak person.deleteFromRealm()
lub person.addChangeListener()
są zastępowane przez RealmObject.deleteFromRealm(person)
i RealmObject.addChangeListener(person)
.
Ograniczenia są takie, że przez RealmObject
tylko RealmObject
może być rozszerzony i nie ma obsługi pól final
, volatile
i transient
.
Ważne jest, aby zarządzana klasa RealmObject mogła być modyfikowana tylko w transakcji. Zarządzanego obiektu RealmObject nie można przekazywać między wątkami.
Lista prymitywów (RealmList )
Dziedzina obecnie nie obsługuje przechowywania listy prymitywów. Jest na ich liście rzeczy do zrobienia ( GitHub nr 575 ), ale tymczasem jest to obejście.
Utwórz nową klasę dla swojego prymitywnego typu, używa ona Integer, ale zmień ją na cokolwiek, co chcesz przechowywać.
public class RealmInteger extends RealmObject {
private int val;
public RealmInteger() {
}
public RealmInteger(int val) {
this.val = val;
}
// Getters and setters
}
Możesz teraz użyć tego w swoim RealmObject
.
public class MainObject extends RealmObject {
private String name;
private RealmList<RealmInteger> ints;
// Getters and setters
}
Jeśli używasz GSON
do wypełnienia swojego RealmObject
, musisz dodać adapter typu niestandardowego.
Type token = new TypeToken<RealmList<RealmInteger>>(){}.getType();
Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.registerTypeAdapter(token, new TypeAdapter<RealmList<RealmInteger>>() {
@Override
public void write(JsonWriter out, RealmList<RealmInteger> value) throws IOException {
// Empty
}
@Override
public RealmList<RealmInteger> read(JsonReader in) throws IOException {
RealmList<RealmInteger> list = new RealmList<RealmInteger>();
in.beginArray();
while (in.hasNext()) {
list.add(new RealmInteger(in.nextInt()));
}
in.endArray();
return list;
}
})
.create();
try-with-resources
try (Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
//whatever Transaction that has to be done
}
});
//No need to close realm in try-with-resources
}
Try z zasobami może być używany tylko z KITKAT (minSDK 19)
Posortowane zapytania
Aby posortować zapytanie, zamiast używać findAll()
, należy użyć findAllSorted()
.
RealmResults<SomeObject> results = realm.where(SomeObject.class)
.findAllSorted("sortField", Sort.ASCENDING);
Uwaga:
sort()
zwraca całkowicie nową posortowaną RealmResults, ale aktualizacja tego RealmResults ją zresetuje. Jeśli używasz sort()
, zawsze powinieneś sortować go ponownie w RealmChangeListener
, usuń RealmChangeListener
z poprzednich RealmResults
i dodaj go do zwracanych nowych RealmResults
. Użycie sort()
w RealmResults
zwróconej przez zapytanie asynchroniczne, które nie jest jeszcze załadowane, zakończy się niepowodzeniem.
findAllSorted()
zawsze zwraca wyniki posortowane według pola, nawet jeśli zostanie zaktualizowane. Zaleca się użycie findAllSorted()
.
Zapytania asynchroniczne
Każda metoda synchronicznego zapytania (taka jak findAll()
lub findAllSorted()
) ma asynchroniczny odpowiednik ( findAllAsync()
/ findAllSortedAsync()
).
Zapytania asynchroniczne odciążają ocenę RealmResults
do innego wątku. Aby otrzymać te wyniki w bieżącym wątku, bieżący wątek musi być wątkiem looper (czytaj: zapytania asynchroniczne zwykle działają tylko w wątku interfejsu użytkownika).
RealmChangeListener<RealmResults<SomeObject>> realmChangeListener; // field variable
realmChangeListener = new RealmChangeListener<RealmResults<SomeObject>>() {
@Override
public void onChange(RealmResults<SomeObject> element) {
// asyncResults are now loaded
adapter.updateData(element);
}
};
RealmResults<SomeObject> asyncResults = realm.where(SomeObject.class).findAllAsync();
asyncResults.addChangeListener(realmChangeListener);
Używanie Realm z RxJava
W przypadku zapytań Realm udostępnia realmResults.asObservable()
. Obserwowanie wyników jest możliwe tylko w wątkach chwytacza (zazwyczaj wątku interfejsu użytkownika).
Aby to zadziałało, konfiguracja musi zawierać następujące elementy
realmConfiguration = new RealmConfiguration.Builder(context) //
.rxFactory(new RealmObservableFactory()) //
//...
.build();
Następnie możesz wykorzystać swoje wyniki jako obserwowalne.
Observable<RealmResults<SomeObject>> observable = results.asObservable();
W przypadku zapytań asynchronicznych należy filtrować wyniki według funkcji isLoaded()
, aby zdarzenie było odbierane tylko wtedy, gdy zapytanie zostało wykonane. Ten filter()
nie jest potrzebny w przypadku zapytań synchronicznych ( isLoaded()
zawsze zwraca wartość true
w przypadku zapytań synchronicznych).
Subscription subscription = RxTextView.textChanges(editText).switchMap(charSequence ->
realm.where(SomeObject.class)
.contains("searchField", charSequence.toString(), Case.INSENSITIVE)
.findAllAsync()
.asObservable())
.filter(RealmResults::isLoaded) //
.subscribe(objects -> adapter.updateData(objects));
W przypadku zapisu należy użyć metody executeTransactionAsync()
lub otworzyć instancję Realm w wątku w tle, wykonać transakcję synchronicznie, a następnie zamknąć instancję Realm.
public Subscription loadObjectsFromNetwork() {
return objectApi.getObjects()
.subscribeOn(Schedulers.io())
.subscribe(response -> {
try(Realm realmInstance = Realm.getDefaultInstance()) {
realmInstance.executeTransaction(realm -> realm.insertOrUpdate(response.objects));
}
});
}
Podstawowe użycie
Konfigurowanie instancji
Aby użyć Realm, musisz najpierw uzyskać jego instancję. Każde wystąpienie Realm jest mapowane na plik na dysku. Najbardziej podstawowym sposobem uzyskania instancji jest:
// Create configuration
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(context).build();
// Obtain realm instance
Realm realm = Realm.getInstance(realmConfiguration);
// or
Realm.setDefaultConfiguration(realmConfiguration);
Realm realm = Realm.getDefaultInstance();
Metoda Realm.getInstance()
tworzy plik bazy danych, jeśli nie został utworzony, w przeciwnym razie otwiera plik. Obiekt RealmConfiguration
kontroluje wszystkie aspekty tworzenia dziedziny - czy jest to inMemory()
danych inMemory()
, nazwa pliku Realm, czy dziedzina powinna zostać wyczyszczona, jeśli potrzebna jest migracja, dane początkowe itp.
Należy pamiętać, że wywołania Realm.getInstance()
są liczone jako referencje (każde wywołanie zwiększa licznik), a licznik jest zmniejszany, gdy realm.close()
jest realm.close()
.
Zamykanie instancji
W wątkach w tle bardzo ważne jest zamknięcie instancji Realm, gdy nie są one już używane (na przykład transakcja jest zakończona i kończy się wykonywanie wątku). Brak zamknięcia wszystkich instancji Realm w wątku w tle powoduje przypięcie wersji i może spowodować duży wzrost rozmiaru pliku.
Runnable runnable = new Runnable() {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
// ...
} finally {
if(realm != null) {
realm.close();
}
}
};
new Thread(runnable).start(); // background thread, like `doInBackground()` of AsyncTask
Warto zauważyć, że powyżej poziomu API 19 możesz zastąpić ten kod tylko:
try(Realm realm = Realm.getDefaultInstance()) {
// ...
}
Modele
Następnym krokiem byłoby stworzenie twoich modeli. Tutaj można zadać pytanie „co to jest model?”. Model jest strukturą, która określa właściwości obiektu przechowywanego w bazie danych. Na przykład poniżej modelujemy książkę.
public class Book extends RealmObject {
// Primary key of this entity
@PrimaryKey
private long id;
private String title;
@Index // faster queries
private String author;
// Standard getters & setter
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
Pamiętaj, że twoje modele powinny rozszerzyć klasę RealmObject. Klucz podstawowy jest także określony w adnotacji @PrimaryKey
. Klucze podstawowe mogą mieć wartość null, ale tylko jeden element może mieć null
jako klucz podstawowy. Możesz także użyć adnotacji @Ignore
dla pól, których nie należy @Ignore
na dysku:
@Ignore
private String isbn;
Wstawianie lub aktualizowanie danych
Aby zapisać obiekt książki w instancji bazy danych Realm, możesz najpierw utworzyć instancję swojego modelu, a następnie zapisać ją w bazie danych za copyToRealm
metody copyToRealm
. Do tworzenia lub aktualizacji możesz użyć copyToRealmOrUpdate
. ( insertOrUpdate()
alternatywą jest nowo dodana insertOrUpdate()
).
// Creating an instance of the model
Book book = new Book();
book.setId(1);
book.setTitle("Walking on air");
book.setAuthor("Taylor Swift")
// Store to the database
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.insertOrUpdate(book);
}
});
Pamiętaj, że wszystkie zmiany danych muszą nastąpić w transakcji. Innym sposobem utworzenia obiektu jest użycie następującego wzorca:
Book book = realm.createObject(Book.class, primaryKey);
...
Zapytanie do bazy danych
Wszystkie książki:
RealmResults<Book> results = realm.where(Book.class).findAll();
Wszystkie książki o identyfikatorze większym niż 10:
RealmResults<Book> results = realm.where(Book.class) .greaterThan("id", 10) .findAll();
Książki autorstwa
'Taylor Swift'
lub'%Peter%'
:RealmResults<Book> results = realm.where(Book.class) .beginGroup() .equalTo("author", "Taylor Swift") .or() .contains("author", "Peter") .endGroup().findAll();
Usuwanie obiektu
Na przykład chcemy usunąć wszystkie książki autorstwa Taylor Swift:
// Start of transaction
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
// First Step: Query all Taylor Swift books
RealmResults<Book> results = ...
// Second Step: Delete elements in Realm
results.deleteAllFromRealm();
}
});