古いバージョンのドキュメントを表示しています。
最新版のドキュメントはこちらからご覧になれます。
はじめに
realm-javaは、Androidプロジェクト以外では、サポートされていません。
必要なもの
- バージョン0.8.6以上のAndroid Studio(Eclipseをお使いの場合は、以下をご覧ください)
- 最新のAndroid SDK
- バージョン7以上のJDK
API Level 9(Android2.3 Gingerbread)からのは全てサポートしています。
インストール
Mavenを使うか手動でJarを追加するかでRealmをプロジェクトに追加してください。
Maven
- 依存レポジトリのために jcenter を使ってください。(Android Gradleプラグインではデフォルトです。)
compile 'io.realm:realm-android:0.79.0'
を追加してください。- Android Studioのメニューから
Tools->Android->Sync Project with Gradle Files
を選択してください。
Jar
- Downloadから最新のパッケージをダウンロードし解凍してください。
- Android Studioで新しいプロジェクトを作成します。
realm-VERSION.jar
をapp/libs
にコピーします。- Android Studioのメニューから
Tools->Android->Sync Project with Gradle Files
を選択してください。
- Downloadから最新のパッケージをダウンロードし解凍してください。
- jarファイルと
libtightdb-jni.so
を含んだdistribution/eclipse/
にあるフォルダをプロジェクトのlibs
フォルダにコピーしてください。 libs
にあるrealmのjarファイルを右クリックし、”Build Path” -> “Add to Build path” を選択してください。- プロジェクトで右クリックをし、”Properties”を選択してください。”Java Compiler” -> “Annotation Processing” を選択し、 “Enable project specific settings” にチェックを入れ “Apply” をクリックしてください。
- 次に “Annotation Processing” -> “Factory Path” を選択し、”Enable project specific settings” をチェックします。”Click Add JARs” で、”libs” にあるrealmのjarファイルを選びます。そしてビルドしてください。
- アノテーション処理のために、アノテーション
@RealmClass
をRealmObjectのサブクラスを定義するときに入れておくことをオススメします。
ProGuard
Realmは、RealmObjectのためにコンパイル時にproxyクラスを生成しています。難読化が行われ、ProGuardでそれらを利用できるようにするために、以下の、設定を追加してください。
-keepnames public class * extends io.realm.RealmObject
-keep class io.realm.** { *; }
-dontwarn javax.**
-dontwarn io.realm.**
Realm Browser
Realm Browserは、Mac OSX版でのみ対応しております。Windows, Linux版は現在、準備中ですので、もう少しお待ちください。
RealmBrowser は、Realmの中で使われるている .realm
ファイルを閲覧、編集するMacアプリです。最新のCocoa版Realm の browser/
フォルダの中に入っています。 また、Tools > Generate demo database を選択することでサンプルデータを含んだ、テスト用のRealmデータベースを作ることもできます。
APIリファレンス
Realmで使用できるClass, Methodに関しては、API Reference をご覧ください。
サンプルコード
最新のRealm には、いくつかのサンプルプロジェクトが含まれています。Android Studioで、Import Project
で追加し、run
で実行してみてください。
RealmIntroExample
は、簡単なAPIの使い方について学べます。アプリを実行し、出力されるログを見てみてください。
RealmGridViewExample
は、GridViewでのRealmの使い方について学べます。
RealmThreadExample
は、マルチスレッドでのRealmの使い方について学べます。
RealmAdapterExample
は、RealmBaseAdapterを使って、RealmResultsをリストにどのようにバインドするかについて学べます。
RealmJsonExample
は、JSONの扱い方について学べます。
RealmEncryptionExample
は、Realmを暗号化するやり方について学べます。
ヘルプ
- バグ報告やフィーチャーリクエストは、GitHub repo にお気軽に投稿してください。
- ディスカッション & サポート: realm-java@googlegroups.com.
- StackOverflow: 以前の質問はStackOverflowで #realm をご覧ください。
- Community Newsletter に参加することで定期的にRealmに関するのTipsやUseCase, ブログポストやチュートリアルなど、Realmの最新情報がGetできます。
モデル
Realmで使うモデルクラスは、一般的な Java Bean と同じように定義できます。以下のように RealmObject
のサブクラスを作ります。
public class User extends RealmObject {
@PrimaryKey
private String name;
private int age;
@Ignore
private int sessionId;
// Standard getters & setters generated by your IDE…
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public int getSessionId() { return sessionId; }
public void setSessionId(int sessionId) { this.sessionId = sessionId; }
}
注意することは、ゲッター/セッターメソッドはRealmObjectが作るプロキシクラスによって上書きされることです。 ゲッター/セッターに書かれたロジックは実行されません。 Realm とオブジェクトは強く結びついてるので、realm.createObject()
メソッドを使ってインスタンスを作成してください。
フィールドの型
Realmでは、以下の型がサポートされています。 boolean
, short
, int
, long
, float
, double
, String
, Date
, byte[]
Realmでは、short
, int
, long
型が全て long
型として扱われます。また、 RealmObject
のサブクラスと RealmList<? extends RealmObject>
が関連付けのために使用されます。
無視されるプロパティ
アノテーション @Ignore
を付けて宣言したフィールドは、ディスクに保存するときに除外されます。インプットされるデータが余分で使わないフィールドを含んでいる時に、そのフィールドを除外するときに有効となります。
検索インデックス
アノテーション @Index
を付けると、検索インデックスを作成することができます。これを作成することで、データが挿入される時は遅く、データファイルは大きくなりますが、クエリ実行時により速く取得することができます。クエリを速く実行する必要があるところでのみ使うことをオススメします。 現在、string
型でのみ(他の型のサポートは近々追加する予定です)使用可能です。検索インデックスは削除することはできません。
プライマリーキー
プライマリーキーを指定するには、@PrimaryKey
を使います。指定できるフィールドは、stringかinteger(short
, int
, long
)である必要があります。複数のフィールドを指定すること(複合プライマリーキー)はできません。String型のフィールドをPrimaryキーにすると、暗黙的にそのフィールドにインデックスを貼られます。(@PrimaryKey
をつけることで、自動的に @Index
が付きます。) Realmオブジェクトが作られるとき、全てのフィールドでデフォルト値がセットされます。すでに存在するデータのプライマリキーとの衝突を避けるためにStandAloneオブジェクトを作成した後は、プライマリキーのフィールドに値をセットしてください。copyToRealm()
を使い、Realmにオブジェクトをコピーしてください。StandAloneオブジェクトの扱い方については、後のセクションをご覧ください。 また、createOrUpdate()
を使うことでオブジェクトを作成し、更新することができます。 このメソッドでは、プライマリキーが一致するデータが既に存在すれば、更新を行い、無ければ、作成されます。 プライマリーキーを使うことによってパフォーマンスが向上されます。オブジェクトの作成と更新は、少し遅くなりますが、問い合わせは速くなります。パフォーマンスへの影響は、データセットの大きさによって違います。
書き込み
読み込み処理は、どこからでもクエリを投げることができ、いつでもデータにアクセスできますが、書き込み(追加、変更、削除)処理は、必ずトランザクションの中で行わなければいけません。 Writeトランザクションは、コミットかキャンセルができます。コミットした時にディスクにデータが書き込まれ、全てのデータが保存されるとコミット処理が成功となります。Writeトランザクションをキャンセルした場合、全ての変更は破棄されます。Writeトランザクションを使用することで、データの整合性が保たれます。
Writeトランザクションは、スレッドセーフを保証するために使用します。
// Obtain a Realm instance
Realm realm = Realm.getInstance(this);
realm.beginTransaction();
//... add or update objects here ...
realm.commitTransaction();
トランザクションの中で作った、Realmオブジェクトの変更は、反映させるかキャンセルするのか選ぶことができます。 変更のコミットをやめ、削除する場合、以下のように簡単にキャンセルすることができます。
realm.beginTransaction();
User user = realm.createObject(User.class);
// ...
realm.cancelTransaction();
書き込み処理が行われている間は、他の処理をブロックしていることになります。 UIスレッドで行い、スレッドを止めてしまうとANRエラーの原因になります。 これを避けるために、トランザクション外でメモリ上にオブジェクトを生成し、トランザクション内では、 Realm.copyToRealm() だけをしてあげると、少しのブロックで済むようになります。
RealmはMVCCアーキテクチャーであるため、Writeトランザクションが開始されている状態でも、読み込み処理は正しくできます。 同時に複数のスレッドから書き込みする場合でない限り、長めのWriteトランザクションを使うことをオススメします。 Realmに書き込み処理をコミットしたとき、全てのRealmインスタンスは通知を受け、Readトランザクションは自動的に更新されます。 Realmのトランザクションは、ACIDに準拠しています。
オブジェクトの作成
Realmオブジェクトのメソッドでオブジェクトを作ると、始めからRealmとオブジェクトが結びついています。
realm.beginTransaction();
User user = realm.createObject(User.class); // Create a new object
user.setName("John");
user.setEmail("john@corporation.com");
realm.commitTransaction();
また、それとは違って、始めにオブジェクトを作成し、realm.copyToRealm() を使って、後から保存し、紐付けることもできます。 Realmは、引数を取らないコンストラクタを含む、数多くのコンストラクタをサポートしています。
User user = new User("John");
user.setEmail("john@corporation.com");
// Copy the object to Realm. Any further changes must happen on realmUser
realm.beginTransaction();
User realmUser = realm.copyToRealm(user);
realm.commitTransaction();
realm.copyToRealm()
を使う上での注意点は、返り値として返されたオブジェクトのみRealmによって管理されているということです。その後、最初に作られたオブジェクトに変更を加えても何も反映されません。
トランザクションブロック
realm.beginTransaction()
, realm.commitTransaction()
, realm.cancelTransaction()
の代わりに、realm.executeTransaction() でブロック形式でトランザクションを記述できます。同様に、エラーが発生したときにキャンセルも行えます。
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
User user = realm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
}
});
クエリ
Realmでのフェッチ(クエリを含む)は全て遅延評価です。また、内部で使われるデータはコピーされません。
Realmのクエリの仕組みは複数の条件を指定できるようにFluent interfaceを使っています。 nameの値が John
か Peter
である全てのオブジェクトを取得の仕方は以下の通りです。
// Build the query looking at all users:
RealmQuery<User> query = realm.where(User.class);
// Add query conditions:
query.equalTo("name", "John");
query.or().equalTo("name", "Peter");
// Execute the query:
RealmResults<User> result1 = query.findAll();
// Or alternatively do the same all at once (the "Fluent interface"):
RealmResults<User> result2 = realm.where(User.class)
.equalTo("name", "John")
.or()
.equalTo("name", "Peter")
.findAll();
この例では、nameの値が John
か Peter
であるデータが RealmResults
として返ってきます。この時、データはコピーされません。 該当するオブジェクトのリストの参照を取得することができ、クエリで取得できるオブジェクトを直接、操作することができます。
RealmResultは、JavaのAbstractListを継承しており、その振る舞いは似ています。 例えば、RealmResultは、順番を持っていて、インデックス指定えアクセスできます。
実行されたクエリが何もマッチしなかった場合、返り値として返されるRealmResultオブジェクトは、null
になります。しかし、size()
メソッドは、0を返すものになります。
RealmResultオブジェクトに対して変更を加えたり、削除したりを行う場合、それらの操作は、Writeトランザクションの中で行わなければいけません。
型によるオブジェクトの取得
Realmからオブジェクトを取得するときの一般的なメソッドは、realm.allObjects()
です。このメソッドは、指定のモデルクラスのオブジェクト全てのデータをRealmResultとして返します。 また、allObjects()
に似たメソッドで realm.allObjectsSorted()
もあります。これを使うと取得時に、フィールドを指定してソートしたりすることができます。 詳しくは、realm.allObjectsSorted()
をご覧ください。
条件文
以下の条件文がサポートされています。
between
,greaterThan()
,lessThan()
,greaterThanOrEqualTo()
&lessThanOrEqualTo()
equalTo()
¬EqualTo()
contains()
,beginsWith()
&endsWith()
ただし、全てのデータタイプに対応しているわけではありません。 詳しくは、RealmQuery をご覧ください。
Modifiers
文字列の条件で、CASE_INSENSITIVE
を使うことで、アルファベットの大文字と小文字の区別をなくせます。
論理式
すべての条件文は論理積です。論理和を使う場合は or()
を使用します。 グループを特定するためにグループ挿入句を使用します。以下のように beginGroup()
で始め、endGroup()
で終わります。
RealmResults<User> r = realm.where(User.class)
.greaterThan("age", 10) //implicit AND
.beginGroup()
.equalTo("name", "Peter")
.or()
.contains("name", "Jo")
.endGroup()
.findAll();
さらに、not()
を使うことで、取り除くこともできます。not()
は、beginGroup()
/endGroup()
の中でのみ使用することができます。
ソート
クエリの実行後は、以下のように結果をソートすることができます。
RealmResults<User> result = realm.where(User.class).findAll();
result.sort("age"); // Sort ascending
result.sort("age", RealmResults.SORT_ORDER_DESCENDING);
連続したクエリの実行
連続してクエリを実行することで、データをフィルタリングしていくことができます。
RealmResults<Person> teenagers = realm.where(Person.class).between("age", 13, 20).findAll();
Person firstJohn = teenagers.where().equalTo("name", "John").findFirst();
集約
RealmResults
は様々な集約をすることができます。
RealmResults<User> results = realm.where(User.class).findAll();
long sum = results.sum("age").longValue();
long min = results.min("age").longValue();
long max = results.max("age").longValue();
double average = results.average("age");
long matches = results.size();
反復処理
RealmResults
は、イテレーションのために Iterable
インターフェイスを実装しています。
RealmResults<User> results = realm.where(User.class).findAll();
for (User u : results) {
// ... do something with the object ...
}
もちろん、以下のような for
ループも使用できます。
RealmResults<User> results = realm.where(User.class).findAll();
for (int i = 0; i < results.size(); i++) {
User u = results.get(i);
// ... do something with the object ...
}
削除
クエリの結果を使ってRealmからデータを削除することができます。
// obtain the results of a query
RealmResults<Dog> results = realm.where(Dog.class).findAll();
// All changes to data must happen in a transaction
realm.beginTransaction();
// remove single match
results.remove(0);
results.removeLast();
// remove a single object
Dog dog = results.get(5);
dog.removeFromRealm();
// Delete all matches
results.clear();
realm.commitTransaction()
Realms
Realmクラスのインスタンスは、データベースと同等のようなものです。異なるオブジェクトを保持しており、これらは一つのファイルに保存されています。
デフォルトRealm
Realm.getInstance(Context context)
を呼び出すことで、realm変数を初期化してることは、すでにお気付きのことでしょう。 このコンストラクタは、今いるスレッドで使えるRealmインスタンスを返します。default.realm
で呼ばれるファイルは、Context.getFilesDir()
が指すディレクトリにあります。 これは、プロジェクトフォルダのルートにある、Fileフォルダの中にある default.realm
のものです。
ファイルは、書き込み可能なディレクトリのルートに作成されます。 ほとんどの場合、/data/data/files/
にファイルがあることでしょう。
realm.getPath()
を使えば、Realmファイルへのパスを確認できます。 注意することは、Realmインスタンスは、スレッドシングルトンであり、このことは、別々のスレッドで同じRealmファイルへのRealmインスタンスを取得したとき、そのインスタンスは同一であることを意味します。
その他のRealm
場合によっては、複数のRealmを使いたい時があります。たとえば、機能ごとに異なるグループ分けされたデータベースがある場合などや、Read-Onlyなデータとユーザーが編集可能なデータをはっきりと分けたい場合などです。
Realm realm = Realm.getInstance(this, "allmymovies.realm");
スレッド間での実行
スレッド間での使用方法として注意することは、Realm
, RealmObject
, RealmResults
インスタンスは、スレッド間での受け渡しができないということです。 複数のスレッドで同じデータにアクセスしたい場合は、それぞれのスレッドで別々のRealmインスタンスを( Realm.getInstance(Context context)
や似たようなメソッドを使って)作成する必要があります。それに対してクエリを投げることでオブジェクトを取得する必要があります。 別々のスレッドから同じRealmファイルにアクセスすることができ、それぞれのスレッドで読み書きができます。
Realmインスタンスの閉じ方
メモリとファイルディスクリプタの解放のために、Realm
は、Closeable
を実装しています。Realmインスタンスを使い終わった後に、closeすることを忘れないでください。 Realm
インスタンスはリファレンスカウントで管理されています。これは、もし同じスレッドで二度、getInstance()
を呼んだ場合、同様に、close()
を二度、呼ばなければいけません。 このことは、どのスレッドで実行されているのか意識することなく、Runnable
クラスを実装することができます。 単に、getInstance()
で始め、終わりに close()
してあげればいいのです。 UIスレッドでの、一般的な方法は、onDestroy()
メソッドで、realm.close()
を呼ぶことです。 また、以下のようにAsyncTask
を使っての非同期処理にすることは良い書き方です。
protected Long doInBackground(Context... contexts) {
Realm realm = null;
try {
realm = Realm.getInstance(contexts[0]);
// ... Use the Realm instance
} finally {
if (realm != null) {
realm.close();
}
}
}
また、Looper
を使う場合、以下のように書きます。
public class MyThread extends Thread {
private final Context;
public MyThread(Context context) {
this.context = context;
}
public void run() {
Looper.prepare();
Realm realm = null;
try {
realm = Realm.getInstance(context);
//... Setup the handlers using the Realm instance
Lopper.loop();
} finally {
if (realm != null) {
realm.close();
}
}
}
}
アプリのAPIレベルが minSdkVersion >= 19
であれば、try-with-resources構文が使え、以下のように書けます。
try (Realm realm = Realm.getInstance(context)) {
// No need to close the Realm instance manually
}
自動リフレッシュ
Looperと紐付いてスレッドでRealmインスタンスが生成された場合、Realmインスタンスは、自動更新機能を持つことになります。これは、Realmインスタンスは、イベントループの中で、自動的に最新のバージョンに反映されるということです。非常に便利な機能であり、少ない労力で、継続的にUIの値などを更新していくことができます。 Looperが紐付いていないスレッドでRealmインスタンスを取得した場合、refresh()
メソッドを呼ばない限り、自動更新はされません。
ここで、注意するべきことは、古いバージョンを維持し続けることは、メモリやディスクの観点から見ても、コストのかかることであるということです。 そして、そのコストは新しいバージョンに更新されるにつれ、増加していきます。 これは、Realmインスタンスを使い終わった後、すぐに close()
をすべき理由でもあります。 自動更新機能が使えるかどうかの確認は、isAutoRefresh()
メソッドでできます。
関連
RealmObject
は、お互いに関連付けを行うことができます。
public class Email extends RealmObject {
private String address;
private boolean active;
// ... setters and getters left out
}
public class Contact extends RealmObject {
private String name;
private Email email;
// ... setters and getters left out
}
リレーションシップは、スピードの観点からいうと特にオーバーヘッドなく素早く動きます。また、メモリの消費量の点でも効率的に機能します。
多対一
モデル間で多対一や一対一の関連性を持たせたい場合は、RLMObjectモデルクラスのフィールドを宣言します。
public class Contact extends RealmObject {
private Email email;
// Other fields…
}
これで、Contact
インスタンスでは、Email
インスタンスと関連付けを行うことができます。Realmにおいて、複数の Contact
インスタンスから一つの Email
インスタンスの関連を持つこともできます。このモデルでは、多対一の関連付けも行うことができますが、たいてい一対一の関連付けで使われると思います。
多対多
RealmList<T>
フィールドを追加することで対多の関連を持たせることができます。
public class Contact extends RealmObject {
private RealmList<Email> emails;
// Other fields…
}
RealmList
クラスは、基本的にRealmObject
のコンテナクラスです。振る舞いは、JavaのList
クラスと似ています。異なるRealmList
から一つのオブジェクトへの関連付けを行うことはできます。特に制約はありません。 また、これらは一対多、多対多で使うことができます。ゲッター/セッターを関連付けのフィールドのために追加することができます。
realm.beginTransaction();
Contact contact = realm.createObject(Contact.class);
contact.setName("John Doe");
Email email1 = realm.createObject(Email.class);
email1.setAddress("john@example.com");
email1.setActive(true);
contact.getEmails().add(email1);
Email email2 = realm.createObject(Email.class);
email2.setNumber("jd@example.com");
email2.setActive(false);
contact.getEmails().add(email2);
realm.commitTransaction();
再帰的な関連付けの定義もできます。
public class Person extends RealmObject {
private String name;
private RealmList<Person> friends;
// Other fields…
}
現在、Realmでは循環された関連を見つける仕組みはまだありませんので、簡単に無限ループを発生しかねます。使用される場合は十分、ご注意ください。
Linkクエリ
関連付いてるオブジェクトに対してクエリを実行することも可能です。 以下のモデルを考えてみて下さい。email
で active=true
なContact
インスタンスだけを取得したい場合はこのようにします。
RealmResults<Contact> contacts = realm.where(Contact.class).equalTo("emails.active", true).findAll();
まず、equalsTo
を使ってフィールドの状態を調べていることが分かると思います。
上記のクエリは、「少なくとも一つはactiveフィールドがtrueなmailインスタンスを持ったContactインスタンスを取得してください」という意味になります。 ここでは、取得されるContact
インスタンスの、全てのEmail
オブジェクトがactive=true
ではないことを理解することが重要です。
JSON
JSONデータから直接、Realmオブジェクトを作ることができます。読み込むJSONデータは、String、JSONObject、InputStream であれば問題ありません。Realmは、RealmObjectに定義がないプロパティが、JSONに含まれていた場合は、無視します。 一つのオブジェクトを作る場合は、Realm.createObjectFromJson() を使い、JSONデータを全てオブジェクトにする場合は、Realm.createAllFromJson() を使用します。
// A RealmObject that represents a city
public class City extends RealmObject {
private String city;
private int id;
// getters and setters left out ...
}
// Insert from a string
realm.beginTransaction();
realm.createObjectFromJson(City.class, "{ city: \"Copenhagen\", id: 1 }");
realm.commitTransaction();
// Insert multiple items using a InputStream
InputStream is = new FileInputStream(new File("path_to_file"));
realm.beginTransaction();
try {
realm.createAllFromJson(City.class, is);
realm.commitTransaction();
} catch (IOException e) {
realm.cancelTransaction();
}
通知
バックグランド処理など他のスレッドやプロセスでRealmの状態に変更があった時に、Listenerを追加しておくことで、通知を受け取ることができます。
realm.addChangeListener(new RealmChangeListener() {
@Override
public void onChange() {
// ... do something with the updates (UI, etc.) ...
}
});
また、Listenersを簡単に削除することができます。
realm.removeAllChangeListeners();
マイグレーション
マイグレーションは、work in progress な機能です。機能的には、完全に動作します。しかし、現在のインターフェイスは、複雑ですので、書き換える予定です。
データベースを使ってる場合、時間が経つにつれ、データモデルというのは変更されていくものです。 Realmでのデータモデルは、一般的なクラスの定義と同じで、Realmモデルクラスに変更を加えるだけで、簡単にデータモデルを変更できます。
まだ、古いスキーマのデータがディスクに保存されていないなら、変更を加えても問題なく動きます。しかし、古いスキーマのデータがディスクに保存されている場合、ディスクに保存されているスキーマとRealmモデルクラスとして定義されてるスキーマでミスマッチが起こり、例外が投げられます。
マイグレーションのための用意されてるメソッドを使って、ディスクにあるデータを新しいスキーマに適応させる必要があります。 詳しくは、migration sample app をご覧ください。
暗号化
Please take note of the Export Compliance section of our LICENSE, as it places restrictions against the usage of Realm if you are located in countries with an export restriction or embargo from the United States.
Realm.create()
の呼び出し時に、256ビット長の暗号キーを使って、Realmファイルを暗号化してディスクに保存することができます。
byte[] key = new byte[32];
new SecureRandom().nextBytes(key);
Realm realm = Realm.create(this, key);
// ... use the Realm as normal ...
これでAES256を使って、暗号化し保存することができます。 Realmファイルが作成された時に使用された暗号化キーと同じものを毎回、使われなければいけません。
詳しくは、examples/encryptionExample をご覧ください。 ただし、他のアプリケーションからは、このファイルを読み込むことはできません。
暗号化を使用するにはソースコードからRealmをビルドする必要があります。
- ビルド手順に従ってビルドしてください。ただし、
./gradlew assemble
を実行する前にlocal.properties
ファイルにencryption=true
を一行追加してください。 - ビルド後は、プロジェクト内にある、
realm-<VERSION>.aar
とrealm/build/outputs/aar/realm-<VERSION>.aar
にあるものを置き換えてください。
アダプタ
Realmは、RealmResultsとして取得したデータをUIに反映させるための抽象クラスがあります。 RealmBaseAdapter
は、getView()
メソッドを実装することで、データのUIへのバインディングをやってくれます。
public class Person extends RealmObject {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
public class MyAdapter extends RealmBaseAdapter<Person> implements ListAdapter {
private static class MyViewHolder {
TextView name;
}
public MyAdapter(Context context, int resId,
RealmResults<Person> realmResults,
boolean automaticUpdate) {
super(context, realmResults, automaticUpdate);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = inflater.inflate(android.R.layout.simple_list_item_1,
parent, false);
viewHolder = new ViewHolder();
viewHolder.name = (TextView) convertView.findViewById(android.R.id.text1);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
Person item = realmResults.get(position);
viewHolder.name.setText(item.getName());
return convertView;
}
public RealmResults<Person> getRealmResults() {
return realmResults;
}
}
他のライブラリ
このセクションでは、Androidでよく使われる他のライブラリとRealmの統合の仕方について説明します。
GSON
GSON は、JSONをシリアライズ/デシリアライズするGoogle製のライブラリです。RealmとGson(最新のバージョン: 2.3.1)を使用する場合、ExclusionStrategy をきちんと指定する必要があります。
// Using the User class
public class User extends RealmObject {
private String name;
private String email;
// getters and setters left out ...
}
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;
}
})
.create();
String json = "{ name : 'John', email : 'john@corporation.com' }";
User user = gson.fromJson(json, User.class);
GSONとRealmを使ったサンプルコードとして、GridViewExample もご参照してください。
Otto
Otto は、イベントバスを扱うための、Square製のライブラリです。RealmとOttoは動作します。しかし、いくつか問題があります。
Otto は、基本的にイベントが送られた同じスレッドでイベントを受け取ります。RealmObjectをイベントに追加し、受け取ったメソッドでそのデータを読むことは問題なくできます。 しかし、メインスレッドでイベントをポストするために、ここに紹介されているようなHackをした場合、RealmObjectsを持たない、イベントデータを受け取ることになります。
Realmは、あるスレッドでRealmObjectに変更があった時、他のスレッドでHandlerを使い、データを更新しようとします。 一方で、Otto.post(event)
は、即座にイベントを送ります。Realmデータに変更があったことを他のスレッドに通知するイベントを送った場合、手動で、最新のデータに更新するために realm.refresh()
を呼ぶ必要があります。
@Subscribe
public void handleEvent(OttoEvent event) {
realm.refresh();
// Continue working with Realm data loaded in this thread
}
Retrofit
Retrofit は、REST APIとの統合を簡単にするSquare製のライブラリです。 Retrofitは、内部でGSONを使っており、同様にJSONからRealmObjectsに変換するのであれば、GsonConverterを適切に設定する必要があります。
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;
}
})
.create();
// Configure Retrofit to use the proper GSON converter
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.setConverter(new GsonConverter(gson))
.build();
GitHubService service = restAdapter.create(GitHubService.class);
Retrofitは、自動的にRealmにオブジェクトを追加してくれません。以下のように realm.copyToRealm()
メソッドを使って追加していく必要があります。
GitHubService service = restAdapter.create(GitHubService.class);
List<Repo> repos = service.listRepos("octocat");
// Copy elements from Retrofit to Realm to persist them.
realm.beginTransaction();
List<Repo> realmRepos = realm.copyToRealm(repos);
realm.commitTransaction();
Robolectric
Robolectric は、デバイスやエミュレータの代わりにJVM上で直接、JUnitテストを動作させるライブラリです。現在、Robolectricを使ってRealmのテストを行うことはできません。 詳しくは、フィーチャーリクエストのGitHub Issuesをご覧ください: https://github.com/robolectric/robolectric/issues/1389
次のステップ
Realmをより深く理解する、次のステップとして、サンプルコードを用意しています。 HAPPY HACKING!! Google グループ Realm で、いつでもRealmデベロッパーと議論ができます。
開発中の機能
Realmは、現在β版としてリリースされています。バージョン1.0に向けて、機能追加、バグの修正などを行っています。私たちは、次に導入予定のリストを作成しました。 詳しくは、GitHub issues をご覧ください。
一般的な制約
Realmは、柔軟性とパフォーマンスのバランスを上手く保つため、保存するデータに対していくつか制約があります。
- クラス名は、57文字が上限です。Realmの内部では、
class_
という文字をクラス名の前に付けて扱われます。ブラウザはその名前の一部を表示します。 - フィールド名の63文字が上限です。
- Date型のフィールドは、保存されると少数点以下が切り捨てられます。それと、32bit版と64bit版の互換性を保持するために1900-12-13より前と2038-01-19年よりも後の日付は保存することができません。
- ネストしたトランザクションはサポートしていません。検出された場合、例外が投げられます。
- 文字列とバイトの配列(
byte[]
)は、16MB以上は保存できません。 - 大文字小文字を区別しない文字列マッチの場合、Latin Basic, Latin Supplement, Latin Extended A, Latin Extended B の文字セット(UTF-8の範囲は、0-591)のみサポートしています。
ソート
現在、ソートは、Latin Basic, Latin Supplement, Latin Extended A, Latin Extended B の文字セット(UTF-8の範囲は、0-591)でのみサポートしています。それ以外の文字セットでソートを行われた場合、RealmResults
オブジェクトは変化しません。
スレッド
Realmは、複数のスレッドで同じRealmファイルを扱うことはできるのですが、スレッド間で、Realm, Realmオブジェクト, クエリ, RealmResultを渡すことはできません。 さらに、非同期のクエリは、現在サポートされていません。マルチスレッドでのRealmの扱い方については、詳しくは、thread example をご覧ください。
null値
値型(boolean, integer, floating-point number, date, string) に null
を保存することはできません。null
値については、現在、開発中ですが、しかし、完全にサポートしたとしても、nullかどうかを判定するための余分なbooleanのフィールドを追加することをお勧めします。
マイグレーション
マイグレーションは、機能的には、完全に動作します。しかし、現在のインターフェイスは、複雑ですので、書き換える予定です。 現在のマイグレーションについては、migration example をご参照ください。
FAQ
どのようにすれば、Realmファイルを閲覧することができますか?
このstackoverflow の質問で、Realmファイルの保存場所について説明しています。 Realmファイルの中身については、Realm Browserを使うことで閲覧できます。
Realmは、どれくらいの大きさですか?
アプリをリリースビルドすると、大半の場合は、800KBぐらいになりAPKに追加されます。 配布されているRealmは、複数のアーキテクチャサポート(ARM7, ARMv7, ARM64, x86, MIPS)を含んでいるため、著しく大きくなっています。 APKファイルは、複数のアーキテクチャの実行ファイルを含んでいますが、Androidインストーラは、アプリインストール時にそのデバイスのアーキテクチャ用のネイティブコードのみをインストールします。 最終的に、インストールされるアプリは、APKファイルよりも小さくなります。
Realmをプロダクション環境で使うことはできますか?
Realmは、2012年から商業利用がされています。 ご利用される場合は、RealmのJAVA APIが、頻繁に変わるものだとお考えの上、Community Feedback を確認しながらお使いください。 機能追加、バグ修正も同様にお考えください。
Realmを使うのにお金を払わないといけませんか?
いいえ、Realmは、完全に無料です。商業利用も可能です。
どのようなビジネスプランなのですか?
すでにエンタープライズ向けの商品の販売や、周辺サービスによって収益を得ています。もし、現在リリースされているものやrealm-javaで更に必要なものがあれば、いつでもメールで、お気軽にご連絡ください。また私たちのビジネスとは関係なく、realm-javaはオープンに開発をつづけていき、Apache License 2.0の元にオープンソースで公開し続けます。
“tightdb” や “core” という文字をコードの中で見たのですが、これは何ですか?
TightDBというのは、C++で実装されたストレージエンジンの名前です。現在、オープンソースではありませんが、Apache License 2.0として公開することを検討中です。 バイナリリリースは、Realm Core (TightDB) Binary License として利用可能です。
一般的なJavaオブジェクトとRealmオブジェクトの違いはありますか?
主な違いは、Javaオブジェクトは、データの値を保持しているが、一方で、Realmオブジェクトは、データそのものは保持しておらず、必要なときに直接、DBからセット/ゲットしてくるようになっています。 このことにより2つの影響があります。一つは、Realmオブジェクトは、一般的なJavaオブジェクトよりも軽量です。二つ目は、Javaオブジェクトは、値が更新されると手動で更新する必要がありますが、Realmオブジェクトは、いつも最新の値が自動で反映されます。
なぜモデルクラスの定義で、RealmObjectを継承する必要があるのですか?
主な理由は、モデルクラスに特定の機能を追加するためです。現在、removeFromRealm()のみ、サポートしています。他の機能もサポートする予定で、コードをより読みやすく、使いやすくするために一般的なAPIをご使用ください。
*RealmProxyクラスとは何ですか?
RealmProxyクラスは、データの値をオブジェクトに持たせず、DBのデータに直接、読み書きすることを実現するのに使われるクラスです。 Realmのアノテーションプロセッサが、プロジェクト内の各モデルクラスのRealmProxyクラスを生成します。このクラスは、モデルクラスを拡張し、Realm.createObject()が呼ばれたときに、返されます。 しかし、実際にIDEを使って使っている時には、それらの違いを感じないと思います。
なぜ、全てのフィールドで、ゲッタ/セッタが必要なのですか?
これは、Proxyクラスの動作のせいです。 全てのフィールドのアクセッサは、Proxyクラスによって、オーバーライドされます。こういったことは、Javaのプライベートなフィールドのゲッタ/セッタでのみ可能です。 これにより、Comparableのようなインターフェイスを実装した場合と同様に、 全てのプライベートメソッドでそのゲッタ/セッタを使う必要がでてきます。 実際、これが一番の理想的な解決方法とは思いません。なので、この制約を取り除きたいと思っています。AspectJ
や Javassist
のような解決策は、そういったことを可能にしてくれますが、私たちは、他の可能性を探し続けています。 Proxyクラスのセッタ/ゲッタのオーバーライドによって、自分でカスタマイズしたセッタ/ゲッタを定義することはできません。一時的な解決策は、@Ignore
アノテーションを付けて、セッタ/ゲッタを定義することです。 その時の、モデルクラスは以下のようになります。
package io.realm.entities;
import io.realm.RealmObject;
import io.realm.annotations.Ignore;
public class StringOnly extends RealmObject {
private String name;
@Ignore
private String kingName;
// custom setter
public void setKingName(String kingName) { setName("King " + kingName); }
// custom getter
public String getKingName() { return getName(); }
// setter and getter for 'name'
}
上のコードのように、setName()
の代わりに、setKingName()
を使うようにします。カスタムセッタの中で、setName()
を使い、name
フィールドに直接、値を割り当てなくします。
なぜ、RealmListをインスタンス化することはできないのですか?
RealmListは、普通のリストのように見えますが、実はそうではありません。リレーションシップを表現するもので、Realmによって管理される必要があります。 しかし、標準のリストに簡単にマッピングする機能をRealmListsに実装することも考えています。
なぜ、RealmObjectをインスタンス化してはいけないのですか?
これは、RealmListと同じ理由です。RealmObjectは、データベースとのプロキシの役割を果たします。なので、Realmによって適切に管理される必要があります。 しかし、これもRealmListsと同様に、モデルオブジェクトからRealmObjectに変換する機能を実装することも考えています。
なぜ、書き込み処理を行うときにトランザクションを使う必要があるのですか?
トランザクションは、複数のスレッドからの書き込みに対するアトミック性を保証するためのものです。
トランザクションの範囲をはっきりさせることによって、完全にコミットするか全てを戻すか、どちらか選べるようになります。(エラーが起こった場合は、ロールバックします) また、トランザクションの範囲を明確にしておくことで、更新をコントロールすることができます。(たとえば、複数のInsertを一つの操作として扱えます) SQLiteのようなSQLベースのデータベースで、複数のフィールドを一度に更新するような操作をしたとします。この時、トランザクションで自動的にラップされていますが、普通、ユーザーからは見えません。 しかし、Realmでは、トランザクションはいつも明示的に書きます。
メモリ不足の例外が出た時はどうすべきですか?
Realmは、組み込みストレージエンジンです。ストレージエンジン自体は、JVMのヒープ上にアロケートはしません。しかし、アプリ自体が、メモリ不足になる可能性があります。 そのような状況では、Realmは、io.realm.internal.OutOfMemoryError
Exceptionを投げます。この例外は無視するべきではなく、もし無視した場合、壊れた状態のRealmファイルが残ることになります。
ファイルサイズと中間バージョンについて
誰もが、SQLiteを使ってデータを保存した時よりもディスクの使用容量が少なくなることを期待されることかと思います。 Realmファイルの容量が非常に大きくなった場合、Realmがオブジェクトをトラッキングするために中間データを作っているためです。 データの一貫性を保つために、データをフェッチしたときのみバージョンがアップデートされます。 このことは、Realmからデータを読み込み、別のスレッドでそのデータの更新が行われた場合、読み込まれたデータの情報はアップデートされないということです。 そして、Realmは、この中間バージョンを管理するために、内部でのみ使用されるデータが増えていき、その結果、ディスクの使用領域が肥大化してしまうことになります。 (この余分なスペースは、今後の書き込みで再度、使われるか、圧縮されることになります。例えば、compactRealmFile
が呼ばれた時などです)
‘Annotation processor may not have been executed.’ という例外はどういう意味ですか?
アプリ実行の際、モデルクラスを読み込み、Proxyクラスが生成されます。 アノテーションプロセスが失敗した場合、Proxyクラスとそれらのメソッドは見つかりません。 アプリが起動時に、Realmが、’Annotation processor may not have been executed.’ と例外を投げます。 Java6を使っている場合、Inheritedアノテーションがサポートされていませんので、この例外が投げられると思います。 @RealmClass
をモデルファイルの前に追加する必要があります。そうしない場合は、生成されたファイルを全ての削除、クリーンし、リビルドし直してください。