React Native学习Hybrid开发RN

Realm数据库在RN中的使用教程

2018-04-19  本文已影响249人  挂着铃铛的兔

如果在使用过程中遇到什么问题,可以加入react-native兴趣交流群群号:397885169一起讨论学习,也欢迎在评论区评论。

我之前写过一篇React-Native中的数据持久化方式,里面主要讲了AsyncStorage怎么用,关于Realm只是一笔带过,最近公司项目正好要用到Realm,所以就把这个数据库的使用方法和部分理解分享出来。Demo会在稍后更新

操作数据库,首先是要建表,然后对表数据进行增删改查,本文会按照这个顺序介绍Realm的用法,在最后会介绍数据库版本升级和版本迁移。

我现在做的是一个本地书籍存储的项目,所以本文中的代码都会基于项目中的实例。稍后会将Demo放出来。

Realm数据库有一个特性是自动同步更新,简单解释下:Realm存储数据是同步的,不是类似setStateAsyncStorage这种异步操作。

Realm是支持从网络同步获取数据的,但因为我这个项目主要用的还是本地存储数据,对网络数据库就没有太多研究,若以后有用到,会再完善本文。

建表

Realm中建表和其他数据库基本一样,都存在表名,主键,属性,属性中可以设置必选参数,可选参数,默认参数等。

1、创建Book继承于Realm,这种方式类似单例,通过继承让Book拥有Realm的属性。 当然还有其他的方式,可以参考文档
class Book extends Realm.Object {}

2、建表

Book = {
    name: 'Book',                          // 表名
    primaryKey: 'id',                           // 设置id为主键
    properties: {                               // 属性
        id: { 'int', indexed: true },        // 书籍ID
        title: 'string?'                        // 书籍名称
        author: 'string?',                      // 作者名
        cover: { type: 'string'},               // 封面图
        isDownLoad:  { type: 'bool', default: false},   // 是否下载
        addBooksTime: { type: 'date', optional: true }, // 加入书架时间
        readBookProgress: 'string?',            // 阅读进度
        url: 'string?',                         // 下载链接
        path: 'string?',                        // 本地路径
        downloadProgress: { 'float?', default: 0 },    // 下载进度
    },
}

new Realm({schema: [ Book ], schemaVersion : 0});
或
Realm.open({schema: [ Book ], schemaVersion : 0});

new Realm或者Realm.open的参数

schema: schema是realm的模型,也就是说它定义了model的格式。

path: 指定数据库到另一个的路径,该属性有默认路径,可以通过Realm.defaultPath全局属性访问和更改默认的Realm路径。

migration: 迁移功能,后续会有介绍。

sync: 一个同步对象,用于打开与Realm Object Server同步的Realm。

inMemory: 数据库将在内存中打开,并且对象不会持久; 一旦最后一个Realm实例关闭,所有对象都会消失。

deleteRealmIfMigrationNeeded: 如果需要迁移,则删除Realm; 这在开发中很有用,因为数据模型可能经常变化。

schemaVersion: 数据库版本号,默认是0,数据库升级或者添加字段(修改模型),可以直接通过修改该参数实现。

如果想知道数据库当前版本,可以执行Realm.schemaVersion方法

const currentVersion = Realm.schemaVersion(Realm.defaultPath);

schema的属性:

name: 表名,通常是唯一的。

primaryKey: 主键,设置属性中的某个参数为主键。只能设置为int或者string类型

properties: 属性,配置数据库中参数。

realm支持的数据类型有

 `bool`: 属性映射到`JavaScript boolean`值。
 `int`, `float`, `double`: 属性映射到`JavaScript number`值。`int`和`double`存储为64位,而`float`存储32位。
 `string`: string 属性映射到 `string`。
 `data`: 属性映射到 ArrayBuffer。
 `date`: 属性映射到 Date。

realm数据类型支持的属性

`type`: 数据类型。
`objectType`: 通常用在多对多关系中,用来指定其他表名的类型。
`optional`: 可选属性,也可以通过直接在参数后面加'?'的方式使之成为可选属性。
`default`: 默认参数,设置属性的默认参数。
`indexed`: 索引属性,对属性进行索引将极大地加快查询的性能,但会影响插入速度。支持`string`,`int`,`bool`,`date`类型。

初始化

这里使用官方Demo的初始化方法,稍后的Demo中,应该会提供另外一种。

import { Realm } from './realmSchema';

this.realm = Realm.objects('Book');

写入数据

realm中对象的更改(创建更新删除)必须在write()中实现。

请注意,任何抛出的异常write()都会取消事务。使用try/catch是一个很好的做法。
const Book = {
    id: 2333,
    title: 'Harry Potter',
    author: 'J. K. Rowling',
    cover: 'https://upload.wikimedia.org/wikipedia/zh/3/3c/Hp1tw.jpg',
    isDownLoad: false,
    addBooksTime: time,
    readBooksTime: time,
    url: '',
};

try {
  Realm.write(() => {
    Realm.create('Book', Book);
  });
} catch (e) {
  console.log("Error on creation");
}

更新数据

realm中有两种更新,普通更新,直接更新属性;另一种是通过主键来更新。

普通更新方式,没有设置主键,通过write()会直接修改id
realm.write(() => {
  id = '666';
});
通过主键创建和更新对象

创建和更新的差异,主要在第三个参数上,默认为false,代表创建,当为true的时候,就是更新该主键

realm.write(() => {
  // 创建一条新数据
  realm.create('Book', {id: 10086, title: 'A Song of Ice and Fire'});

  // 更新主键id为2333的数据
  realm.create('Book', {id: 2333, title: 'Harry Potter and the Philosopher's Stone'}, true);
});

删除数据

realm.write(() => {
  // 创建一条新数据
  let book = realm.create('Book', {id: 1, title: 'Harry Potter'});

  // 删除该条数据
  realm.delete(book);

  // 得到表中所有数据,全部删除
  let allBooks = realm.objects('Book');
  realm.delete(allBooks); 
});

查询数据

Realm可以单个表中类型的对象,并可以筛选和排序这些结果。
Realm中的所有查询(包括查询和属性访问)都是懒惰的。只有在访问对象和属性时才会读取数据。这允许您以高性能的方式访问大量数据。

最简单的使用方法

执行查询将会返回一个 不可变Results对象。

let book = realm.objects('Book');

上面的方法会把Book表中所有的数据都查询出来。

过滤

Results对象可以通过调用filtered查询字符串上的方法来进行过滤。

let book = realm.objects('Book');
let bookInfo = book.filtered('title = "Harry Potter"');

Realm过滤有很多种方式,这里就不全部列举了,如果需要可以查看官方文档(Queries)

排序

Results允许您根据单个或多个属性指定排序标准和顺序。

let book = realm.objects('Book')

// 根据id排序
let bookSorted = book.sorted('id');

// 按照降序排序
bookSorted = book.sorted('id', true);

// id按照降序排序,添加时间按照升序
bookSorted = book.sorted([['id', true], ['addBooksTime', false]]);

自动更新结果

Results实例是实时的,自动更新视图到底层数据中,这意味着结果永远不需要重新获取。

let hondas = realm.objects('Car').filtered('make = "Honda"');
// hondas.length == 0

realm.write(() => {
  realm.create('Car', {make: 'Honda', model: 'RSX'});
});
// hondas.length == 1

这适用于所有Results情况,包括那些被返回objectsfilteredsorted方法。

属性Results不仅可以让Realm快速高效地工作,它还可以让你的代码变得更简单和更具响应性。例如,如果您的视图依赖于查询的结果,那么您可以将其存储Results在属性中并访问它,而无需在每次访问之前确保刷新其数据。

结果限制

大多数数据库技术提供了“分页”查询结果的功能(如SQLite中的LIMIT关键字)。这通常是为了避免从磁盘读取过多或者将太多结果一次性写入内存而做的。

由于Realm中的查询是懒惰的,执行这种分页行为根本就不是必需的,因为Realm只会在查询结果被明确访问后才加载对象。

如果出于UI相关或其他的原因,您需要查询中的特定对象子集,就像获取Results对象一样简单,只读出您需要的对象。

let book = realm.objects('Book')

// 得到5条书籍信息
let bookList = book.slice(0, 5);

数据迁移

在使用数据库时,您的数据模型很可能随需求/时间而改变。

假设需要Book中的addBooksTime改成time,我们需要将模型改成如下样式:

const Book = {
    name: 'Book',                          // 表名
    primaryKey: 'id',                           // 设置id为主键
    properties: {                               // 属性
        id: { 'int', indexed: true },        // 书籍ID
        title: 'string?'                        // 书籍名称
        time: { type: 'date', optional: true }, // 时间
    },
}

此时,如果您使用以前的型号版本保存了任何数据,则新代码与Realm在磁盘上存储的旧数据之间将存在不匹配。发生这种情况时,如果尝试使用新模式打开现有Realm,则会引发异常,除非您进行迁移。

执行迁移

您可以通过更新schemaVersion并定义可选migration函数来定义迁移和关联的模式版本。您的迁移功能提供了将数据模型从先前模式转换为新模式所需的任何逻辑。仅当需要Realm迁移时,才会应用打开迁移功能来更新Realm给指定的架构版本。

如果没有提供迁移功能,则在更新到新数据库时,会从数据库中删除所有属性,自动添加的属性和旧属性schemaVersion。如果您在升级版本时需要更新旧的或填充新的属性,则可以在迁移功能中执行此操作。例如,假设我们想要迁移Book之前声明的模型。您可以time使用旧的addBooksTime属性填充新模式的属性:

Realm.open({
  schema: [Book],
  schemaVersion: 1,
  migration: (oldRealm, newRealm) => {
    // 只有当schemaVersion小于1的时候才会执行if中代码
    if (oldRealm.schemaVersion < 1) {
      const oldObjects = oldRealm.objects('Book');
      const newObjects = newRealm.objects('Book');

      // 遍历所有对象,并将新的模型中设置属性
      for (let i = 0; i < oldObjects.length; i++) {
        newObjects[i].time = oldObjects[i].addBooksTime
      }
    }
  }
}).then(realm => {
  const newBook = realm.objects('Book')[0].time;
});

迁移成功完成后,Realm及其所有对象都可以正常使用属性。

线性迁移

通过上述迁移模式,您可能会在迁移多个版本时遇到问题。如果用户跳过应用更新并且在跳过的版本中多次更改属性,则可能会发生这种情况。在这种情况下,您可能需要编辑旧的迁移代码以正确地将数据从旧模式更新为最新模式。

可以通过按顺序运行多个迁移来避免此问题,确保数据库升级到每个以前的版本并且运行相关的迁移代码。遵循这种模式时,永远不必修改旧的迁移代码,尽管您需要保留所有旧的模式和迁移块以供将来使用。下面是一个例子:

const schemas = [
  { schema: schema1, schemaVersion: 1, migration: migrationFunction1 },
  { schema: schema2, schemaVersion: 2, migration: migrationFunction2 },
  ...
]

// the first schema to update to is the current schema version
// since the first schema in our array is at
let nextSchemaIndex = Realm.schemaVersion(Realm.defaultPath);
while (nextSchemaIndex < schemas.length) {
  const migratedRealm = new Realm(schemas[nextSchemaIndex++]);
  migratedRealm.close();
}

// 打开最新的schema
Realm.open(schemas[schemas.length-1]);

通知

RealmResultsList对象提供addListener方法来注册的通知回调。每当对象更新时,都会调用更改通知回调。

有两种通知Realm Notifications(简单回调,提交write()时的通知)和Collection Notifications(复杂回调,它们在插入,删除和更新时接收更改数据)。

Realm Notifications

每次提交写入事务时,Realm实例都会向其他实例发送通知。要注册通知:

function updateUI() {
  // ...
}

// 添加realm的通知监听
realm.addListener('change', updateUI);

// 移除指定的通知监听
realm.removeListener('change', updateUI);

// 移除所有的通知监听
realm.removeAllListeners();

Collection Notifications

Collection Notifications包含描述在细粒度级别发生了哪些更改的信息。这包括自上次通知以来已插入删除修改的对象的索引。收集通知是异步传递的:首先是初始结果,然后是任何修改集合中任何对象的写入数据,从集合中删除对象,或向集合添加新对象

通知回调函数用于addListener在发生这些更改时接收两个参数。第一个是已更改的集合,第二个是具有changes删除插入修改影响的集合索引信息的对象。

每当对象的属性发生变化时,您的应用程序都会收到有关修改的通知,这是以前的集合的一部分,仍然是其中的一部分。当一对多关系发生变化时,也会发生这种情况,但不会考虑反向关系的变化。

这段内容比较复杂,例子就不在这里展示了。如果在使用中需要用到可以查看官方文档(Notifications)

上一篇下一篇

猜你喜欢

热点阅读