将数据写入同步 Realm - Kotlin SDK
在此页面上
使用 Flexible Sync将数据写入同步域时,您可以使用与写入本地域相同的 API。 但是,在开发应用程序时,需要记住一些行为差异。
当您写入同步 Realm 时,写入操作必须匹配以下两项:
同步订阅查询
App Services App 中的权限
如果您尝试写入与查询订阅或用户权限不匹配的数据,Realm 会使用称为补偿写入的非致命错误操作来恢复写入。
要了解有关为应用程序配置权限的更多信息,请参阅 Device SyncAtlas App Services文档中 的基于角色的权限 和 权限指南 。
要了解有关权限被拒绝错误、补偿写入错误和其他Device Sync 错误类型的更多信息,请参阅 Atlas App Services文档中的 同步错误 。
确定同步哪些数据
您可以写入同步 Realm 的数据由以下因素决定:
Device Sync 配置
应用中的权限
打开 Realm 时使用的 Flexible Sync 订阅查询
本页上的示例使用具有以下 Device Sync 配置的 Atlas App Services App 以及具有以下 Realm 软件开发工具包(Realm SDK) Realm 数据模型和订阅的客户端应用程序。
在此示例中,客户端应用程序使用以下对象模型:
class Item : RealmObject { var _id: ObjectId = ObjectId() var ownerId: String = "" var itemName: String = "" var complexity: Int = 0 }
App Services 配置
根据上述示例对象模型,Device Sync 配置有以下可查询字段:
_id
(始终包含在内)complexity
ownerId
App Services App 的权限配置为仅允许用户读取和写入自己的数据:
{ "roles": [ { "name": "readOwnWriteOwn", "apply_when": {}, "document_filters": { "write": { "ownerId": "%%user.id" }, "read": { "ownerId": "%%user.id" } }, "read": true, "write": true, "insert": true, "delete": true, "search": true } ] }
在Atlas collection中的任何对象,如果ownerId
与登录用户的user.id
不匹配,则无法同步到此 域。
客户端Realm 数据模型和配置
使用对象模型,示例将同步域配置为同步与此订阅查询匹配的对象:
val app = App.create(FLEXIBLE_APP_ID) val user = app.login(credentials) val flexSyncConfig = SyncConfiguration.Builder(user, setOf(Item::class)) // Add subscription .initialSubscriptions { realm -> add( // Get Items from Atlas that match the Realm Query Language query. // Uses the queryable field `complexity`. // Query matches objects with complexity less than or equal to 4. realm.query<Item>("complexity <= 4"), "simple-items" ) } .build() val syncRealm = Realm.open(flexSyncConfig) syncRealm.subscriptions.waitForSynchronization() Log.v("Successfully opened realm: ${syncRealm.configuration}")
在Atlas collection中,complexity
属性的值大于4
的任何对象都无法同步到此域。
写入同步 Realm
对Flexible Sync Realm的写入大致可以分为以下两类Flexible Sync
成功写入:写入的对象同时与查询订阅和用户权限匹配。 该对象成功写入到域,并成功同步到 App Services 后端和其他设备。
补偿写入:写入的对象与订阅查询不匹配,或者用户没有足够的权限来执行写入。 Realm 通过补偿写入操作来恢复非法写入。
提示
如果要写入与查询订阅不匹配的对象,可以打开该对象与查询订阅匹配的另一个域。或者,您可以将对象写入不强制执行权限或订阅查询的非同步 Realm。
成功写入
当写入与用户权限和客户端中的查询订阅匹配时,Realm Kotlin SDK 可以成功将该对象写入同步 Realm。当设备具有网络连接时,此对象与 App Services 后端同步。
// Per the Device Sync permissions, users can only read and write data // where the `Item.ownerId` property matches their own user ID. val userId = user.id val newItem = Item().apply { ownerId = userId itemName = "This item meets sync criteria" complexity = 3 } syncRealm.write { // `newItem` is successfully written to the realm and synced to Atlas // because its data matches the subscription query (complexity <= 4) // and its `ownerId` field matches the user ID. copyToRealm(newItem) }
补偿写入
当写入与查询订阅或用户权限不匹配时,Realm 会恢复写入并抛出CompensatingWriteException。
更详细地说,当写入超出查询订阅范围或与用户权限不匹配的数据时,会出现以下情况:
由于客户端 Realm 没有“非法”写入的概念,因此写入最初会成功,直到 Realm 使用 App Services 后端解析变更集。
同步后,服务器会应用规则和权限。 服务器确定用户无权执行写入操作。
服务器向客户端发回恢复操作(称为“补偿写入”)。
客户端 Realm 恢复非法写入操作。
在对给定对象的非法写入和相应的补偿写入之间,客户端对该对象的任何写入都将丢失。 实际上,这可能看起来像写入成功,但当 Realm 与 App Services 后端同步并执行补偿写入时,该对象“消失”。
发生这种情况时,您可以参考Atlas App Services日志或使用客户端中的CompensatingWriteInfo对象来获取有关该错误的其他信息。
写入与查询订阅不匹配
根据上面详细介绍的 Flexible Sync Realm配置,尝试写入此对象会导致补偿写入错误,因为该对象与查询订阅不匹配:
// The complexity of this item is `7`. This is outside the bounds // of the subscription query, which triggers a compensating write. val itemTooComplex = Item().apply { ownerId = user.id itemName = "This item is too complex" complexity = 7 } syncRealm.write { copyToRealm(itemTooComplex) }
[Session][CompensatingWrite(231)] Client attempted a write that is disallowed by permissions, or modifies an object outside the current query, and the server undid the change.
您将在 App Services 日志中看到以下错误消息:
Error: Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231) Details: { "Item": { "63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\" in table \"Item\" not allowed; object is outside of the current query view" } }
写入与权限不匹配
给定上述 Device Sync 配置中的权限,尝试写入此对象会导致补偿写入错误,因为ownerId
属性与登录用户的user.id
不匹配:
// The `ownerId` of this item does not match the `user.id` of the logged-in // user. The user does not have permissions to make this write, which // triggers a compensating write. val itemWithWrongOwner = Item().apply { ownerId = "not the current user" itemName = "A simple item" complexity = 1 } syncRealm.write { copyToRealm(itemWithWrongOwner) }
[Session][CompensatingWrite(231)] Client attempted a write that is disallowed by permissions, or modifies an object outside the current query, and the server undid the change.
您将在 App Services 日志中看到以下错误消息:
Error: Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231) Details: { "Item": { "63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\" in table \"Item\" was denied by write filter in role \"readOwnWriteOwn\"" } }
补偿写入错误信息
1.9.0 版本中的新增功能。
您可以使用CompensatingWriteInfo对象在客户端中获取有关为何进行补偿写入的其他信息,该对象提供:
客户端尝试写入的对象的
objectType
特定对象的
primaryKey
用于补偿写入错误的
reason
此信息与您在App Services日志中找到的信息相同。 出于方便和调试目的, Kotlin SDK在客户端公开此对象。
以下示例说明了如何记录有关补偿写入错误的信息:
val syncErrorHandler = SyncSession.ErrorHandler { session, error -> runBlocking { if (error is CompensatingWriteException) { error.writes.forEach { writeInfo -> val errorMessage = """ A write was rejected with a compensating write error The write to object type: ${writeInfo.objectType} With primary key of: ${writeInfo.primaryKey} Was rejected because: ${writeInfo.reason} """.trimIndent() Log.e(errorMessage) } } } }
A write was rejected with a compensating write error The write to object type: Item With primary key of: RealmAny{type=OBJECT_ID, value=BsonObjectId(649f2c38835cc0346b861b74)} Was rejected because: write to "649f2c38835cc0346b861b74" in table "Item" not allowed; object is outside of the current query view
此消息中的
Item
是此页面上的对象模型中使用的Item
对象。主键是客户端尝试写入的特定对象的
objectId
。table "Item"
指的是此对象将同步的 Atlas collection。此示例中的
object is outside of the current query view
原因是查询订阅设置为要求对象的complexity
属性小于或等于4
,并且客户端尝试写入此边界之外的对象。
对写入进行分组以提高性能
订阅集的每个写入事务都会产生性能成本。如果需要在会话期间对 Realm 对象进行多次更新,请考虑将编辑的对象保留在内存中,直到所有更改完成。这通过仅将完整且更新的对象写入 Realm 而不是每次更改来提高同步性能。