- 2017-10-14 创建
- 2017-10-15 更新
- 2017-10-16 更新
介绍
Realm Javascript版本能够帮助你安全、持续、快速有效的构建你的model层。Realm Javascript被设计用于React Native和Nodejs应用。
这里有个快速简单的例子:
1 | const Realm = require('realm'); |
请注意,如果你将要在server-side/node使用Realm,可以通过Realm对象服务查看一些额外信息。
开始
安装
跟着以下步骤通过npm来安装Realm Javascript,或者在Github查看源码
React Native
先决条件
确保你已经准备好React Native运行环境,按照React Native指引来开始;
使用了Realm的应用程序需要可以在IOS和Android平台运行;
React Native 版本必须大于等于 0.31.0
安装
创建一个React Native项目:
1
react-native init <project-name>
进入新创建的项目内并且添加realm依赖:
1
2
3
4# npm 安装
npm install --save realm
# yarn 安装
yarn add realm
接下来,将你的项目链接到Realm的本地模板:
1 | react-native link realm |
Android警告:由于版本关系,react-native link
可能会生成一个无效的配置,能够正确更新Gradle(android/settings.gradle
和android/app/build.gradle
)但是添加Realm模块失败。请确认react-native link
正确的添加了Realm模块;如果没有的话,按照下列步骤手动添加到库:
添加下列的代码到
android/settings.gradle
:1
2include ':realm'
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')在
android/app/build.gradle
文件中,将Realm库编译行添加到依赖:1
2
3dependencies {
compile project(':realm')
}在
MainApplication.java
中添加引用并且连接包:1
2
3
4
5
6
7
8
9
10
11import io.realm.react.RealmReactPackage; // 增加这个引入
public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RealmReactPackage() // 增加这一行
);
}
}
你现在已经准备完毕。想要实践下Realm,在index.ios.js
和index.android.js
内将class <project-name>
类定义替换成以下代码:
1 | const Realm = require('realm'); |
你可以在设备或者模拟器上运行你的代码。
Node.js
这些只是指导安装Realm Node.js SDK开发者版本。如果你已经下载了专业版或者企业版,按照你收到邮件的安装指导进行。
简单的使用npm包管理器来安装realm:
1 | npm install --save realm |
想要使用SDK,在你的项目中require('realm')
。
1 | var Realm = require('realm'); |
样例
可以在Github的realm-js库中找到。
在Android中要注意,你需要安装有NDK并且设置了ANDROID_NDK的环境变量。
1 | export ANDROID_NDK=/usr/local/Cellar/android-ndk/r10e |
获取帮助
由于你的代码需要帮助?在Stackoverflow上提问,我们在Stackoverflow上很活跃的查看和回答问题!
有bug需要报告?在我们的代码库上提ISSUE。如果可能的话,附带上Realm的版本,一个完整的日志,realm文件和ISSUE上提问相关的项目代码。
有新功能要求?在我们的代码库上提ISSUE。告诉我们这个功能实现什么,为什么需要这个功能。
模块
Realm 数据 models 通过 schema 信息定义,在Realm初始化期间将定义传入。
一个对象的schema定义包含了对象的名称和一系列的属性,每个属性都有它自己的名字和类型;同时也可以将属性定义为一个object或者list,引用其他对象类型。你也可以指定每个属性是否可选或者是否有个默认值。
1 | const Realm = require('realm'); |
Classes
在这点上,通过类定义模型的支持是有限的。它能够在React Native上生效,但是在Node中不起作用。
如果你想要使用ES2015样式的类(也有可能需要继承存在的一些特性),你只需要在构造器中定义schema就可以了:
1 | class Person { |
你现在就可以将类本身传递到open
配置中的schema
属性内:
1 | Realm.open({schema: [Person]}) |
你可以一直使用以下方法访问属性:
1 | realm.write(() => { |
支持的类型
Realm支持以下基本类型:bool
, int
, float
, double
, string
, data
和 date
。
bool
属性对应 JavaScriptBool
类型int
,float
和double
属性对应 JavaScriptNumber
类型. 另外 ‘int’ 和 ‘double’ 以64位存储; float 以32位存储.string
属性对应String
类型data
属性对应ArrayBuffer
类型date
属性对应Date
类型
指定一个基础类型的简写,你只需要指定一个类型而不需要指定一个含有单个键值对的对象:
1 | const CarSchema = { |
关系
对一关系
通过将你关联对象的name
属性作为主对象的属性类型,来形成对一关系:
1 | const PersonSchema = { |
当时用了对象作为属性之后,你需要确保所有关联的对象已经在开启Realm的时候声明。
1 | // 因为PersonSchema包含了一个类型为Car的属性,所以CarSchema也需要在此声明 |
当访问对象属性的时候,你可以以普通的属性访问语法访问对象属性内部的属性:
1 | realm.write(() => { |
对多关系
对于对多属性,你必须声明这个属性类型为list
类型,并且必须同时声明这个list
的item
类型:
1 | const PersonSchema = { |
当访问list
属性的时候,将会返回List
对象。List
对象拥有跟正常Javascript对象类似的方法(需要注意的是:typeof List
仍旧是对象类型,而不是Array)。跟普通JS数组对象最大的区别是,对List
的任意更改都会自动持久化到Realm
下。另外,List
属于底层对象的基础属性,你只能通过从主对象访问属性来获取List
实例,它们不允许被手动创建。
1 | let carList = person.cars; |
反向关系
关系是单向的。所以,当一个对多属性Person.dogs
关联了Dog
实例,一个对一属性Dog.owner
关联了Person
的同时,这两个联系对于互相来说是独立的。即添加一个Dog
到Person
实例的dogs
属性的时候,并不会自动更新dogs的owner
属性。因为,手动关联关系对会造成容易出错、更加复杂化并且会产生重复信息的问题,所以Realm提供了链接对象属性来表示这些反向关系。
当链接对象属性的时候,你可以通过那个特定的属性,获取关联的所有对象。比如:一个dog
对象可以有一个owners
属性,其中有一个包含了这个dog
对象的dogs
属性。以上可以通过指定owners
的属性类型为linkingObjects
并且关联对象为Person
对象来达成目的。
1 | const PersonSchema = { |
一个linkingObjects
属性可以关联到一个List
属性(对多关系)或者一个Object
属性(对一关系):
1 | const ShipSchema = { |
当访问linkingObjects
属性的时候,一个Results
对象将会被返回,目前为止,这个对象完全支持查询和排序。linkingObject
属性属于对象要求的基本属性,所以不能被设置值或者直接操作。它们会在一个事务完成后自动更新。
不通过schema
访问linkingObjects
:如果你在开启realm的时候,没有声明schema,举个例子,在一个 Realm Functions的回调里面,你可以调用一个对象实例的linkingObjects(objectType, property)
方法获取linkingObjects
属性。
1 | let captain = realm.objectForPrimaryKey('Captain', 1); |
Optional 属性
在你的属性定义中,一个属性可以通过optional
标记被声明可选或者必填:
1 | const PersonSchema = { |
可以从上面看出对象属性总是允许可选的,并不需要一个optional
标记。List属性不能被声明为可选并且值不能设置为null。你可以通过设置或者初始化一个空的list来清空它。
默认值设置
在属性定义中,可以通过设置default
标记来设置默认值。在对象创建期间,不给属性设置值将会使用默认值来填充。
1 | const CarSchema = { |
索引属性
你可以在属性定义中增加indexed
标记来使得整个属性被索引。支持int
, string
和bool
类型的属性。
1 | var BookSchema = { |
索引一个属性以插入的更慢为代价,非常大的提升了查询的速度。
主键
在对象模型内,可以对int
和string
属性指定一个primaryKey
属性。声明一个主键,可以使得查询和更新更有效率,并且将强制值唯一性。一旦一个带有主键的对象加入到Realm中,则这个主键无法变更。
1 | const BookSchema = { |
主键属性将会自动被索引。
写入
在Realm内更改对象内容——创建、更新和删除——必须放在一个write()
的事务块里面。需要注意的是写事务是一个不可忽略的开销;在你的代码中应该尽量减少write
事务块。
创建对象
使用create
方法创建对象:
1 | try { |
注意:在write()
内抛出任意异常都会取消事务。try/catch
块并不会在所有样例代码上出现,但是捕获异常这是一个好的习惯。
内嵌对象
如果一个对象有一个对象属性,通过特定的JSON值,会递归创建那些属性值。
1 | realm.write(() => { |
更新对象
类型更新
在写事务中你可以通过设置属性值来更新对象:
1 | realm.write(() => { |
根据主键创建和更新对象
如果你的模型类包含有主键,你可以让Realm智能的根据主键来创建和更新对象。这个功能可以通过在create
方法传递第三个值为true
来开启。
1 | realm.write(() => { |
在上述的案例中,因为已经存在了一个id值为1的对象并且我们传递了true
值给第三个参数,那么price
属性将会被更新,而不是尝试创建一个新的对象。由于name
属性被省略,对象保留此属性的原始值。注意:当用主键创建或者更新对象的时候,主键必须被指定。
删除对象
在write
事务块内,对象可以通过delete
方法删除。
1 | realm.write(() => { |
查询
查询允许你可以通过Realm获取单类型对象,并且对这些对象进行过滤和排序。在Realm中所有的查询(包括查询和属性获取)都是懒加载形式。数据只会在对象或者属性被访问的时候才会读取。这样做可以让你在请求大数据量的时候有个好的加载体验。当查询的时候,将会返回Results
对象。Results
只是你的数据的一个简单展示,并且是不可变更的。
最基础的获取数据的方式是通过在realm
中使用objects
方法,以此获取到所给类型的所有对象:
1 | let dogs = realm.objects('Dog'); // 从Realm中获取所有的'Dog' |
过滤
你可以通过调用filtered
方法附带查询字符串,来获取一个被过滤的结果对象Results
。
举个例子:以下的代码(附带了color
为tan
和name
以B
开头)将会改变我们之前样例查询所有Dogs的结果:
1 | let dogs = realm.objects('Dog'); |
目前为止,在查询语句中,只有NSPredicate
语法的子集才被支持。基本比较符==
, !=
, >
, >=
, <
和 <=
支持数字类属性。==
, BEGINSWITH
, ENDSWITH
, 和 CONTAINS
支持字符类属性。字符串类的比较可以通过添加[c]
到比较符号(==[c]
, BEGINSWITH[c]
等等)来忽略大小写。过滤关联的属性或者孩子对象,可以通过指定一个比如car.color == 'blue'
的值路径来进行过滤。
排序
Results
允许你指定基于单个或多个属性的排序标准和顺序。比如,下面的代码对样例上的返回结果根据miles
做了数字排序。
1 | let hondas = realm.objects('Car').filtered('make = "Honda"'); |
注意:Results
的排序只有在查询排序时才能保持一致。为了行能考虑,插入的顺序并不被保证保留。
自动更新结果集
Results
实例是实时的,自动更新视图到底层数据的,这意味着结果永远不必重新获取。修改查询的对象,将会立即反应到结果中。
1 | let hondas = realm.objects('Car').filtered('make = "Honda"'); |
这对所有的Results
实例都起作用,包括那些返回后又调用了objects
, filtered
和 sorted
方法。
Results
不止保证Realm更快更有效率,且使得你的代码更简单更灵活。比如,如果你的试图依赖查询结果,你可以存储Results
到一个属性并且不需要在每次访问这个视图前刷新试图的数据。
您可以订阅notifications,以了解Realm数据的更新时间,指明应用程序的UI应该如何刷新,而无需重新获取结果Results
。
限制结果
大多数的数据库技术都提供有分页的功能(例如MySQL的LIMIT
关键字)。这个通常是为了避免非必要的大量磁盘读取或者一次性拉取太多的结果。
因为Realm的懒查询,做这种分页行为并不必要,因为Realm只会在真正访问这个属性的时候才会从Results
实例中加载对象。
如果为了UI相关或者其他实现的理由需要结果集的某些特定的子结果集,就跟获取Results
对象一样简单,就是指读出你想要的对象即可。
1 | let cars = realm.objects('Car'); |
Realms
打开Realms
打开Realm只需要通过调用Realm
类中的静态open
方法来执行。传递一个配置对象。我们已经在使用包含schema
键的配置对象中看到过这个例子:
1 | // 获取支持我们对象的默认Realm |
关于配置对象完整细节,看这个API文档。其他更多配置项,除了schema
,还有:
- path: 指定一个给另一个Realm
- migration: 一个迁移函数
- sync:一个同步对象,用来同步打开与Realm对象服务器同步的Realm
- inMemory: Realm将会在内存中打开,对象不会持久化到硬盘;一旦最后的Realm实例关闭,所有的对象将会不复存在
默认Realm
在之前的所有例子中,你可能已经注意到路径参数已被省略。在这种情况下,将会使用默认的Realm路径。但是你可以通过使用Realm.defaultPath
这个全局属性来访问或者变更默认路径。
其他的Realms
有时候在不同的路径保存保存多个Realms是有用的。举个例子,除了你的主要Realm之外,你还可能需要将你的数据和应用打包在同一个Realm文件中。你可以通过初始化Realm的时候指定path
参数来执行此操作。所有的路径都是相对于你的应用的可写目录:
1 | // 在另一个位置打开Realm |
Schema版本
另一个在打开Realm时可用的参数是schemaVersion
。如果不设置,则默认为0
。在使用与之前包含对象不同的Realm初始化现有Realm的时候,你需要指定schemaVersion
的值。如果schema被更新了,但是没有指定schemaVersion
,则会抛出异常:
1 | const PersonSchema = { |
如果你在之后这么做:
1 | const UpdatedPersonSchema = { |
如果你希望取得一个Realm当前的schema版本,你可以使用Realm.schemaVersion
方法。
1 | const currentVersion = Realm.schemaVersion(Realm.defaultPath); |
同步打开Realms
你可以通过简单的调用构造函数并将配置信息传递给它来创建一个realm的实例。这通常不被推荐,因为它阻塞了程序可能潜在的产生耗时操作,特别是当有schema迁移需要进行或者realm是同步的,并且你不希望冒在数据完全下载之前修改它的风险。
如果你仍旧想要这么做,形式很简单:
1 | const realm = new Realm({schema: [PersonSchema]}); |
注意:如果一个realm只有只读权限,那么你必须以异步的方式打开它。因为使用上述形式同步打开一个只读的realm,将会一个错误。
迁移
在使用数据库的时候,你的数据模型可能随着时间的变化随时发生改变。举个例子,假设我们有一个Person
的模型:
1 | const PersonSchema = { |
我们想要更新数据模型,只需要一个name
属性而不是将它拆分成姓氏和名称两个属性。为此,我们只需要将schema更改成以下内容:
1 | const PersonSchema = { |
在这点上,如果你以前的模型版本上保存了任意的数据,新的代码与Realm存储在磁盘上的旧数据将不匹配。当这种情况发生的时候,当你用新的schema打开之前的Realm的时候,将会抛出一个异常,除非你进行数据迁移。
进行迁移
你可以通过更新schemaVersion并定义一个可选的migration
函数来定义一个迁移和与之关联的schema版本。你的迁移函数逻辑需要能够支持将之前的schema版本的数据模型转化为新schema的数据模型。当打开Realm
的时候,只有当需要迁移的时候,才会将Realm
更新到给定版本的schema。
如果没有提供迁移功能,则当更新到新的schemaVersion
的时候,新的属性将会添加而旧的属性将会从数据库删除。如果您需要在升级版本时更新旧的或者填充的新属性,那么可以在迁移功能中执行此操作。例如,假设我们想要迁移早先声明的Person
模型,你可以使用旧的firstName
和lastName
的数据填充新的name
:
1 | Realm.open({ |
一旦你迁移完全成功,所有Realm对象都可以像往常一样被使用。
线性迁移
在使用上述迁移模式的时候,你可能会在多版本的迁移上出现问题。比方用户跳过应用程序的更新,并且在跳过的版本中该属性已被更改多次,则发生这种情况可能会发生。在这种情况下,您可能需要编辑旧的迁移代码,以将数据从旧的schema下更新到最新的schema。
可以通过顺序运行多个迁移来避免这个问题,确保数据库根据版本依次升级并且与之关联的每个升级迁移都被执行。按照这个步骤的话,则旧的迁移代码不必被修改,尽管你需要为将来的使用保存所有旧的schema和迁移语句。一个类似的例子:
1 | const schemas = [ |
通知
Realm
, Results
和List
对象提供了addListener
方法来注册通知回调。无论对象什么时候更新,数据更改通知的回调都会被触发。
有两种通知方式:“Realm 通知”(当写事务被提交后简单的回调会被通知)和“集合通知”(更复杂的回调可以在插入、删除和更新的时候接收到更改的元数据)。
另外,此外,专业版和企业版提供事件处理通知。 阅读“Realm移动平台”了解更多信息。
Realm通知
Realm实例在每次写事务提交后都会发送通知给其他实例。注册通知:
1 | function updateUI() { |
集合通知
集合通知包含了描述在细粒度级别上发生了哪些更改的信息。这包括自上次通知以后,那些被插入、删除或者修改过的对象的索引。集合通知是异步推送:首先使用初始结果,然后在任何修改集合中的任何对象的写事务之后,从集合中删除对象,或向集合中添加新对象。
当这些改变发生的时候,addListener
的通知回调函数将接收到两个参数。第一个是集合已经被更改,第二个是changes
对象,包含了受删除、插入和修改影响的集合索引。
前两项,删除和插入,每当对象加入集合或从集合中被删除的时候,都会记录索引。每当你添加对象到Realm或者将其从Realm中删除的时候,都需要考虑到这点。
// TODO 量有点大,之后的内容待完善