現実的な領域。 1年の経験


レルムは、モバイル(だけでなく)開発者の間で長い間知られています。 残念ながら、RuNetにはこのデータベースに関する記事はほとんどありません。 この状況を修正しましょう。

1年前、プロジェクトのbuild.gradleに次の行が表示されました。

classpath "io.realm:realm-gradle-plugin:0.87.5" 

今年、Realmコードはバージョン3.3に成長し、多くの機能を取得し、多数のバグを修正し、新しい機能を実装し、クラウドバックエンドを受け取りました。 Andoroid開発の現実におけるRealmについて詳しく説明し、それを使用するときに生じる微妙なポイントについて説明します。



私達について


チーム内でのコミュニケーション用のアプリケーション、電報とスラックのクロスを開発しています。 AndroidアプリケーションはKotlinで記述されており、最初からオフライン優先アプローチが使用されていました。 画面に表示されるすべてのデータがキャッシュから取り出されるとき。 いくつかの異なるデータベースを試した後、Realmに落ち着き、1年間アクティブに使用しました。 この記事は、レルムの使用に関する内部文書から生まれました。 この記事はドキュメントの翻訳ではなく、完全な説明のふりをするものではなく、レシピのコレクションと微妙なポイントの分析です。 完全に理解するために、 公式ドキュメントを読むことを強くお勧めします 。 私たちの経験と今年の問題点について説明します。 この記事のコードはすべてKotlinで書かれています。Githubで見つけることができます。

スタートアップとしてのレルム


Realmを会社として話すと、2011年に設立されたデンマークのスタートアップです。 以前は、プロジェクトはtight.dbと呼ばれていました。 その存在の間に、2900万ドルの投資を集めました。 同社は、 Realm Mobile Platformに基づいて収益を上げることを計画しています。データベース自体は無料でオープンソースです。 Realm for Androidは2014年に登場し、それ以来常に進化しています。 一部のアップデートは後方互換性を壊しますが、修正は非常に簡単かつ迅速に行えます。

データベースとしてのレルム


レルムは、いくつかのプラットフォーム用のデータベースです。 彼らは自分自身について書いています:
Realm Mobile Platformは、アプリケーション用の次世代データレイヤーです。 レルムは、リアクティブ、コンカレント、および軽量であり、ライブのネイティブオブジェクトを操作できます。

要するに、これはAndroid(Java、Kotlin)、iOS(Objective-C、Swift)、Xamarin(C#)、およびJavaScript(React Native、Node.js)のネイティブno-sqlデータベースです。
また、すべてのソースからのデータを同期できるバックエンドもあります。

重要な機能のうち、 ゼロコピーMVCC 、およびACIDに注目する価値があります。 組み込みの陳腐化およびデータクレンジングメカニズムはありません。

Realmには、 非常に優れたドキュメントgithubの多くの例があります。
レルムの従業員は定期的にStackOverflowを監視していますが、githubで問題を取得することもできます。

ハローワールド


AndroidのHello Worldは次のとおりです。

build.gradleに追加

 build.gradle (Project level) classpath "io.realm:realm-gradle-plugin:3.3.0" build.gradle (App level) apply plugin: 'realm-android' 

アプリケーションで、レルム構成を構成します

 Realm.init(this) val config = RealmConfiguration.Builder() .build() Realm.setDefaultConfiguration(config) 

そして、データベースの操作を開始できます。

 val realm = Realm.getDefaultInstance() realm.executeTransaction { realm -> val dataObject = realm.createObject(DataObject::class.java) dataObject.name = "A" dataObject.id = 1 } val dataObject = realm.where(DataObject::class.java).equalTo("id", 1).findFirst() dataObject.name // => A realm.executeTransaction { realm -> val dataObjectTransaction = realm.where(DataObject::class.java).equalTo("id", 1).findFirst() dataObjectTransaction.name = "B" } dataObject.name // => B 

他のデータベースとの比較


ハブには、2016年4月8日付の記事があり Realmを含むAndroidの9つのORMを比較しています。 領域は先頭にあり、ここにグラフィックがあります:

他のORMとの比較
画像

画像


Realmは、そのWebサイトで以下の統計を提供しています

レルムからのチャート



考慮しなければならない3つの主な機能があります。
ライブオブジェクト-レルムから取得したすべてのオブジェクトは、実際にはデータベースのプロキシです。 このため、ゼロコピーが実現されます(オブジェクトはデータベースからコピーされません)
トランザクション-添付されたデータオブジェクトに対するすべての変更は、トランザクション内で行う必要があります
開く\閉じる-インスタンスデータベースを開く\閉じる必要

ライブオブジェクト


レルムのすべてのオブジェクトは、同期または非同期で受信できます。

同期読み取り


 fun getFirstObject(realm: Realm, id: Long): DataObject? { return realm.where(DataObject::class.java).equalTo("id", id).findFirst() } 

Realmメソッドを呼び出し、オブジェクトまたはnullを取得するまでスレッドをブロックします。 他のスレッドで受信したオブジェクトは使用できないため、メインスレッドで使用するには、uiをブロックするか、非同期要求を使用する必要があります。 幸いなことに、Realmはオブジェクト自体ではなくプロキシを提供するため、すべてが十分に迅速に行われます。 受領後すぐにオブジェクトを操作できます。

非同期読み取り


非常に明白なケース。 このコードで何が起こると思いますか:

 val firstObject = realm.where(DataObject::class.java).findFirstAsync() log(firstObject.id) 

正解:エラーjava.lang.IllegalStateExceptionが発生します
非同期読み取りでは、オブジェクトをすぐに取得できますが、ロードするまで操作できません。 isLoaded()関数を使用してこれを確認するか、ブロッキングロード()関数を呼び出す必要があります。 かなり不快に見えるので、ここでrxを使用することをお勧めします。 監視可能に変換し、OnNextでロードされたオブジェクトを取得します。 非同期操作は、ルーパーを使用したスト​​リームでのみ使用できます。

 fun getObjectObservable(realm: Realm, id: Long): Observable<DataObject> { return realm.where(DataObject::class.java).findFirstAsync().asObservable() } 

レルムオブジェクトの主な機能



同様に、(RealmResult)オブジェクト(クエリ結果)のリストはRealmのプロキシです。これにより、次のようになります。

取引


レルムにバインドされたオブジェクトは、トランザクション内でのみ変更できます;トランザクション外で変更すると、エラーが発生します。 一方で、あまり便利ではありません。一方で、規律があり、コードのどの部分でもオブジェクトを変更できません。特定のレイヤー(データベース)でのみ変更できます。 また、別のトランザクション内のトランザクションは禁止されていることを覚えておく必要があります。

しない方法:

 val user = database.getUser(1) button.setOnClickListener { user.name = "Test" } 

できること:

 val user = database.getUser(1) button.setOnClickListener { database.setUserName(user, "Test") } 

トランザクションは、同期および非同期で実行できます。 各オプションを詳しく見てみましょう。

同期トランザクション:


 fun syncTransaction() { Realm.getDefaultInstance().use { it.executeTransaction { val dataObject = DataObject() it.insertOrUpdate(dataObject) } } } 

beginTransactionとcommitTransactionの間でトランザクションを実行することもできますが、executeTransactionを使用することをお勧めします。

残念ながら、同期トランザクションはonErrorコールバックをサポートしていないため、エラー処理はユーザーの責任です。 2016年6月以降、onErrorコールバックの追加に問題があります。

非同期トランザクション


非同期トランザクションは、asyncTransactionメソッドによってトリガーされます。 トランザクションとコールバックonSuccessとonErrorを入力に渡し、出力でRealmAsyncTaskオブジェクトを取得します。このオブジェクトを使用して、ステータスを確認したり、トランザクションをキャンセルしたりできます。 非同期トランザクションは、Looperを使用するスレッドでのみ実行されます。 非同期トランザクションの例:

 Realm.getDefaultInstance().use { it.executeTransactionAsync({ it.insertOrUpdate(DataObject(0)) }, { log("OnSuccess") }, { log("onError") it.printStackTrace() }) } 

いくつかの重要なニュアンス:

セッターを介してレルムに関連付けられていないオブジェクトを割り当てることはできません。 最初にオブジェクトをデータベースに配置してから、添付されたコピーを添付する必要があります。 例:

 val realm = Realm.getDefaultInstance() val parent = realm.where(Parent::class.java).findFirst() val children = Children() // parent.setChildren(children) <-- Error val childrenRealm = realm.copyToRealmOrUpdate(children) parent.setChildren(childrenRealm) /// Ok 

多くのトランザクションは1つに結合するのが最適です。 レルムには内部トランザクションキュー(サイズ100)があり、それを超えると例外がスローされます。

すべての非同期トランザクションは1つのexecutor'eで動作します

 // Thread pool for all async operations (Query & transaction) static final RealmThreadPoolExecutor asyncTaskExecutor = RealmThreadPoolExecutor.newDefaultExecutor(); 

短時間に多くの非同期操作がある場合、RejectedExecutionExceptionエラーが発生します。 この状況から抜け出す方法は、別のスレッドを使用してそのスレッドで同期トランザクションを開始するか、複数のトランザクションを1つに結合することです。

レルムを開く/閉じる


Realmの特定のインスタンスを使用してデータベースからすべてのオブジェクトを取得し、このインスタンスが開いているときにそれらを操作できます。 realm.close()を呼び出すとすぐに、オブジェクトを読み取ろうとすると例外が発生します。 Realmを時間どおりに閉じないと、メモリリークが発生します。 ガベージコレクタは、Realmが使用するリソースで正しく動作できません。

公式ドキュメントでは Realmを開いたり閉じたりすることをお勧めします。


ただし、レルムを操作するロジックをActivity \ Fragmentsからプレゼンターに転送する場合は、ライフサイクルメソッドを(スロー)使用する必要があります。

何らかの方法でデータを変更するか、新しいデータを追加する必要がある場合、最も簡単な方法は、新しいインスタンスを取得し、データを書き込んでから閉じることです。 Kotlinでは、このために.use()を使用できます

 Realm.getDefaultInstance().use { // it = realm instance} 

Rxを使用してオブジェクトを読み取るには、「分離された」インスタンスを使用し、doOnUnsubscribeでそれらを閉じます(またはObservable.usingを使用します)。

 // Use doOnUnsubscribe val realm = Realm.getDefaultInstance() realm.where(DataObject::class.java).findAllSorted("id").asObservable().doOnUnsubscribe { realm.close() } // Use Observable.using Observable.using(Realm.getDefaultInstance(), realm -> realm.where(DataObject::class.java).equalTo("id", id) .findFirstAsync() .asObservable() .filter(realmObject -> realmObject.isLoaded()) .cast(DataObject::class.java), Realm::close); 

onDestroy \ onDestroyViewでRealmを閉じることに関連する機能もあります。 Realmを閉じた後、FragmentManagerImpl.moveToState→ViewGroup.removeView→...→RecyclerViewAdapter.getItemCount()が呼び出され、無効なコレクションからlist.size()メソッドが呼び出されることがあります。 そのため、ここではisValid()を確認するか、recyclerViewからアダプターを解放する必要があります

Kotlin Android Extensionsを使用する場合は、ビューを操作できます(kotlinx.android.syntheticから)。onViewCreated()メソッドで始まるFragmentからのみ、NPEを取得しないようにこのメソッドですべてのリスナーを構成することをお勧めします。

3つの最も重要な機能を分析した後、あまり重要ではない機能を調べてみましょう。

通知、RxJava


レルムは、オブジェクト自体と埋め込みオブジェクト(すべてのリンクオブジェクト)の両方のデータ変更に関する通知をサポートしています。 これは、RealmChangeListener(オブジェクト自体が付属)、RealmObjectChangeListener(変更されたオブジェクトが付属し、どのフィールドが変更されたかを理解できます)またはRxJava(onNextでオブジェクトを取得し、非同期リクエストの場合はisLoaded()を確認する必要があります)を使用して実装されますルーパーとストリームで)。

RxJava2はまだ配信されていません。 2016年9月に実装されたときから問題が発生しています-不明です。Interopを使用してください。

同様に、コレクションまたはインスタンス全体のレルムの変更をリッスンできます。 トランザクション内の変更を聞くことは禁止されています。

Rxの例:

 fun getObjectObservable(realm: Realm, id: Long): Observable<DataObject?> { return realm.where(DataObject::class.java).equalTo("id", id).findFirstAsync() .asObservable<DataObject?>().filter({ it?.isLoaded }).filter { it?.isValid } } 

マルチスレッドと非同期


レルムはMVCCデータベースです。 ウィキペディアはMVCCについて述べています:
「MultiVersion Concurrency Control(MVCC)は、データベースへの並列アクセスを保証するメカニズムの1つです。これは、各ユーザーにデータベースのいわゆる「スナップショット」を提供します。トランザクションがコミットされるまでユーザー。 書き込みトランザクションがリーダーをブロックせず、読み取りトランザクションがライターをブロックしないように管理するこの方法。

実際には、次のようになります。オブジェクトの変更をリッスンするか、RxJavaを使用してonNextで変更されたオブジェクトを取得できます。 ストリームAで変更が発生し、ストリームBのオブジェクトを操作している場合、ストリームBは、ストリームAのレルムインスタンスを閉じた後の変更を認識します。変更はルーパーを介して送信されます。 ストリームBにルーパーがない場合、変更は届きません(isAutoRefresh()メソッドで確認できます)。 この状況から抜け出す方法は、waitForChange()メソッドを使用することです。

非同期呼び出しと非同期トランザクションについては、まったく使用しない方が良いでしょう。 アクションを別のスレッドに転送し、そこで同期操作を実行する方が便利です。 いくつかの理由があります


テスト中


以前は、Realm.javaは最終版であり、テストにはpowerMockまたは他の同様のツールが必要でした。 現時点では、Realm.javaは最終的なものではなく、通常のmockitoを安全に使用できます。 デモプロジェクトまたは公式リポジトリでのテストの例

1つのレルムは良いが、3つのレルムは良い


レルムを使用する場合、常に標準レルムを意味しますが、インメモリレルムとダイナミックレルムがまだあります。

標準レルム-Realm.getDefaultInstance()メソッドまたは特定のRealm.getInstance(config)構成を使用して取得できます。構成の数に制限はありません。これらは本質的に別個のデータベースです。

インメモリレルムは、記録されたすべてのデータをディスクに書き込まずにメモリに保存するレルムです。 このインスタンスを閉じるとすぐに、すべてのデータが失われます。 短期のデータストレージに適しています。

動的レルム-主に移行中に使用され、レルムを操作できます-生成されたRealmObjectクラスを使用せずにオブジェクト、フィールド名によってアクセスが実行されます。

継承と多型


レルムは継承をサポートしていません。 レルムオブジェクトは、RealmObjectを継承するか、RealmModelマーカーインターフェイスを実装し、@ RealmClassアノテーションでタグ付けする必要があります。 既存のレルムオブジェクトから継承することはできません。 継承の代わりに構成を使用することをお勧めします。 非常に深刻な問題で、 問題は2015年1月から続いていますが、まだ問題は残っています。

コトリン


すぐに使用できるレルムは、 Kotlinで動作します。
データクラスは機能しません。通常のオープンクラスを使用する必要があります。
また、注目に値するのはKotlin-Realm-Extensionsです。これは、RealmObjectを操作するための便利な拡張機能です。

レルムモバイルプラットフォーム


当初、レルムは異なるプラットフォームのデータベースのみで表されていましたが、現在ではすべてのデバイス間の同期のためにサーバーを展開しています。 現在、プラットフォームは次のもので構成されています。

モバイルプラットフォームの図


デバッグ


デバッグ用に、いくつかのツールがあります。


建築


Realmは、実装全体がデータベースインターフェイスの背後に隠れている場合、MV *アーキテクチャに最適です。すべての呼び出しと選択はデータベースモジュール(リポジトリ)で行われ、Observable cは、サブスクライブ解除時に自動的に閉じられたレルムで自動的にトップに戻ります。または、入力でインスタンスレルムを受け入れ、それを使用してすべてのアクションを実行します。オブジェクトを記録するとき、レルムを開き、データを書き込んで閉じます。保存するオブジェクトのみが入力されます。両方の例はgithubにあります。
悲しいかな、Realm(copyFromRealmなし)を使用すると、クリーンアーキテクチャの使用に重大な制限が課せられます。レイヤーごとに異なるデータモデルを使用することはできません。ライブオブジェクトとプロキシリストのポイントがすべて消えます。また、独立したレイヤーを作成し、レルムを開いたり閉じたりすると、この操作がアクティビティ\フラグメントライフサイクルに関連付けられるため、困難が生じます。適切なオプションは、オブジェクトを変換し、データベースに保存する分離されたデータ取得レイヤーです。

レルムは、データベースから表示するすべてのデータを取得するオフラインファーストアプリケーションを構築する場合に非常に便利です。

便利なリンク


:連絡先を継続し、細かい点を解析するために、我々は、以下の記事をお勧めします

記事からの3つの記事@Zhuinden
レルムの基本:レルム1.2.0使用へのガイド
チャンピオンのように全てのAndroid用のレルムを使用する方法を、どのようにあなたがやっている場合の伝えるために間違ったITの
レルムすべてのアンドロイド1.2.0 +データバインディング

レルムの統合について2件の記事Viraj.Tank @
セーフコードの下、生産のすべてのAndroidレルムの統合、とパート1-MVP
MVPとのコードの下に生産のレルムのすべてのAndroidの深い統合、パート2、

マルチスレッド、詳細分析:
データベースの設計:レルムスレッドディープダイブ
ドキュメント-自動更新
ドキュメント-スレッド

FairBearのHaberに関する最近の記事:
レルムと友達になる方法

おわりに


レルムは、一見思われるよりも複雑です。ただし、すべての欠点は、そのパワーと利便性によってカバーされています。ライブオブジェクト、通知、Rx、便利なAPIなど、さまざまなものがアプリケーションの作成を簡素化します。競合他社には、再クエリ、ObjectBox、GreenDaoがあります。レルムは、オフラインファーストアプリケーションを構築するとき、キャッシュからすべてのデータを取得し、複雑なサンプルとデータの絶え間ない更新が必要なときに完全に明らかになります。Githubにある
すべてのコード

Source: https://habr.com/ru/post/J328418/


All Articles