Realm Database Javascript

  • 安装

1.新建一个react-native项目:react-native init <project-name>
2.cd <project-name> 安装realm包:npm install --save realm
3.link到你项目上:react-native link realm
如果你没有link成功,或者你不放心,你可以操作以下步骤添加或者查看下:
①将下面代码添加到文件android/settings.gradle:

include ':realm'
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')

②将realm添加到依赖文件中:android/app/build.gradle:

//当使用Android Gradle plugin等于3.0或者更高时
dependencies {
    implementation project(':realm')
}
//当使用Android Gradle plugin低于3.0时
dependencies {
    compile project(':realm')
}

③添加导入包文件中:MainApplication.java

import io.realm.react.RealmReactPackage; // add this import
public class MainApplication extends Application implements ReactApplication {
    @Override
    protected List<ReactPackage> getPackages() {
        return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new RealmReactPackage() // add this line
        );
    }
}

好啦,现在举个栗子:

const Realm = require('realm');

class <project-name> extends Component {
  constructor(props) {
    super(props);
    this.state = { realm: null };
  }

  componentWillMount() {
    Realm.open({
      schema: [{name: 'Dog', properties: {name: 'string'}}]
    }).then(realm => {
      realm.write(() => {
        realm.create('Dog', {name: 'Rex'});
      });
      this.setState({ realm });
    });
  }

  render() {
    const info = this.state.realm
      ? 'Number of dogs in this Realm: ' + this.state.realm.objects('Dog').length
      : 'Loading...';

    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          {info}
        </Text>
      </View>
    );
  }
}
  • 数据模型

Realm 数据模型由架构 (schema) 信息定义,而这个信息是在初始化过程中传递到 Realm 当中的。一个对象的架构包含了对象的 name 信息,以及一系列包含 name 和 type 信息的属性,此外对于对象和列表属性来说还包含有 objectType 信息。您同样可以将每个属性指定为 optional 类型,或者必须包含 default值。

const Realm = require('realm');

const CarSchema = {
  name: 'Car',
  properties: {
    make:  'string',
    model: 'string',
    miles: {type: 'int', default: 0},
  }
};
const PersonSchema = {
  name: 'Person',
  properties: {
    name:     'string',
    birthday: 'date',
    cars:     'Car[]'
    picture:  'data?', // optional property
  }
};

// Initialize a Realm with Car and Person models
Realm.open({schema: [CarSchema, PersonSchema]})
  .then(realm => {
    // ... use the realm instance to read and modify data
  })

如果您偏好于从既有类来继承对象的话,那么您只需要在对象构造器当中定义此架构即可,然后在创建 Realm 实例的时候将这个构造器传递进去:

class Person {
  get fullName() {
    return this.firstName + ' ' + this.lastName;
  }
}

Person.schema = {
  name: 'Person',
  properties: {
    firstName: 'string',
    lastName: 'string'
  }
};

现在可以将构造好的类 放到realm中了

Realm.open({schema: [Person]})
  .then(
 /* 这里就可以访问了*/ 
   realm.write(() => {
    const john = realm.create('Person', {
     firstName: 'John',
     lastName: 'Smith'
  });
  john.lastName = 'Peterson';
  console.log(john.fullName); // -> 'John Peterson'
});
);
  • 基础数据类型

Realm 支持以下基础类型:bool、int、float、double、string、data 和 date。
bool 属性将会映射为 JavaScript 的 Boolean 对象;
int、float 以及 double 属性将会映射为 JavaScript 的 Number 对象。其中,int 和 double 是以 64 位进行存储的,而 float 则是以 32 位进行存储的;
string 属性将会映射为 String;
data 属性将会映射为 ArrayBuffer;
date 属性将会映射为 Date。

const CarSchema = {
  name: 'Car',
  properties: {
    // 以下这两种属性类型定义是等价的
    make:   {type: 'string'},
    model: 'string',
  }
}
  • 对象属性

默认情况下,基本类型是非可选的,不支持存储null或未定义。属性可以通过在属性定义中指定可选的指示符来实现可选,也可以通过附加 ?到类型名称:

const PersonSchema = {
  name: 'Person',
  properties: {
    realName:    'string', // 必需属性
    displayName: 'string?', // 可选属性
    birthday:    {type: 'date', optional: true}, // 可选属性
  }
};

let realm = new Realm({schema: [PersonSchema, CarSchema]});

realm.write(() => {
  // 可选属性可以在创建时设置为null或未定义
  let charlie = realm.create('Person', {
    realName: 'Charlie',
    displayName: null, // 也可以完全省略
    birthday: new Date(1995, 11, 25),
  });

  // 可选属性可以设置为' null ', ' undefined ',或新的非空值
  charlie.birthday = undefined;
  charlie.displayName = 'Charles';

  // 将非可选属性设置为null将抛出' TypeError '
  // charlie.realName = null;
});
  • 列表属性

除了存储单个值之外,还可以将属性声明为受支持的任何基本类型的列表。这是通过在类型名称后面追加[]来实现的:

const PersonSchema = {
  name: 'Person',
  properties: {
    name: 'string',
    testScores: 'double?[]'   //可选列表属性
  }
};

let realm = new Realm({schema: [PersonSchema, CarSchema]});

realm.write(() => {
  let charlie = realm.create('Person', {
    name: 'Charlie',
    testScores: [100.0]
  });

  // Charlie had an excused absense for the second test and was allowed to skip it
  charlie.testScores.push(null);

  // And then he didn't do so well on the third test
  charlie.testScores.push(70.0);
});

注意:当访问列表属性时,返回一个列表对象。List的方法非常类似于常规的JavaScript数组。最大的区别是,对列表所做的任何更改都会自动持久化到底层领域,因此只能在写事务中修改它们。此外,列表属于从其获取的底层对象——您只能通过访问所属对象的属性来获取列表实例,而不能手动创建列表实例。
虽然列表属性中的值可以是可选的,但列表属性本身不能是可选的。使用longhand语法(values: {type: 'int[]', optional: true})将list属性指定为可选的,这将使list中的值成为可选的。

  • 主键

您可以在对象模型中,指定一个类型为 string 或者 int 的 primaryKey 属性。声明主键将允许对象在检索、更新时更有效率,并且对于每个值来说将保证其唯一性。一旦某个带有主键的对象被添加到 Realm 数据库之后,那么其主键将不能更改。

const BookSchema = {
  name: 'Book',
  primaryKey: 'id',
  properties: {
    id:    'int',    // primary key
    title: 'string',
    price: 'float'
  }
};

主键将自动被索引。

  • 对象存储

对对象的所有更改(添加,修改和删除)都必须通过写入事务 (transaction) 完成。注意,写事务的开销不可忽略;您应该尽量减少代码中的写块数量。

1.创建对象:使用 create 方法来创建对象:

try {
  realm.write(() => {
    realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
  });
} catch (e) {
  console.log("Error on creation");
}

注意:write()中抛出的任何异常都将取消事务。try/catch块不会在所有示例中显示,但这是一个很好的实践。
2.嵌套对象
如果某个对象拥有对象属性,那么这些属性的值可以通过为每个子属性指定 JSON 值来依次进行创建:

realm.write(() => {
  realm.create('Person', {
    name: 'Joe',
    // nested objects are created recursively
    car: {make: 'Honda', model: 'Accord', drive: 'awd'},
  });
});

3.更新对象
内容直接更新
您可以在写入事务中通过设置某个对象的属性从而完成对象的更新操作。

realm.write(() => {
  car.miles = 1100;
});
Creatin

通过主键更新
如果您的数据模型中设置了主键的话,那么您可以基于它们的主键值让 Realm 自行更新或者添加对象。这可以通过向 create 方法中将 true 作为第三个参数进行传递来实现:

realm.write(() => {
  // Create a book object
  realm.create('Book', {id: 1, title: 'Recipes', price: 35});

  // Update book with new price keyed off the id
  realm.create('Book', {id: 1, price: 55}, true);
});

在上面这个例子中,由于对象已经存在了值为 1 的 id,然后我们在第三个参数中传递了 true 参数,这时候价格属性将会被更新,而不是试图创建一个新的对象。由于 name 属性被忽略了,因此这个对象将会为此属性保留其原始值。注意到,当创建或者更新带有主键属性的对象时,主键必须要指明出来。

  • 删除对象

通过在写入事务中调用 delete 方法,可以对对象进行删除。

realm.write(() => {
  // Create a book object
  let book = realm.create('Book', {id: 1, title: 'Recipes', price: 35});

  // Delete the book
  realm.delete(book);

  // Delete multiple books by passing in a `Results`, `List`,
  // or JavaScript `Array`
  let allBooks = realm.objects('Book');
  realm.delete(allBooks); // Deletes all books
});
  • 查询

查询允许您从 Realm 数据库中获取某个单独类型的对象,您同样还可以对这些结果进行匹配和排序。所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才能够读取相应的数据。这允许您在处理大数据集合的时候以一个更高性能的方式进行处理。
当执行查询操作后,会返回一个 Results 对象。Results 只是数据的投影,不能够被修改。
从 Realm 中检索对象的最基本方法是使用 Realm 中的 objects 方法,这会返回该给定类型的所有对象:

let dogs = realm.objects('Dog'); // 检索 Realm 中所有的 Dog 对象
  • 条件查询

您可以调用 filtered 方法,通过检索字符串来对 Results 进行匹配。
例如,下面的例子将会检索所有棕黄色,并且以“B”开头命名的狗狗:

let dogs = realm.objects('Dog');
let tanDogs = dogs.filtered('color = "tan" AND name BEGINSWITH "B"');

目前,只有 NSPredicate 语法的某些部分能够在查询语言当中进行使用。基础的比较运算符 ==、!=、>、>=、< 以及 <= 支持数字类型。==、BEGINSWITH、ENDSWITH 以及 CONTAINS 支持字符串属性。字符串之间的比较可以通过添加 [c] 运算符来让其能够区分大小写:比如 ==[c]、BEGINSWITH[c] 之类。检索链接或者子对象的属性可以通过在查询过程中指定 keypath 来完成,例如 car.color == 'blue'。

1.比较操作数可以是属性名或常量。至少有一个操作数必须是属性名。特殊常量有false、true和null。

2.时间戳可以指定为“YYYY-MM-DD@HH:MM:SS:纳秒”格式,其中纳秒可以省略。

3.比较运算符=/==、<=、<、>=、>和!=/<>支持int、float、double和Date属性类型,例如age = 45。

4.boolean (bool)属性支持比较运算符=/==和!=/<>。

5.对于字符串和数据(ArrayBuffer)属性,支持= (and ==)、!= (and <>)、BEGINSWITH、CONTAINS和ENDSWITH操作符,例如name CONTAINS 'Ja'。

6.可以对带有LIKE操作符的字符串进行通配符比较,例如'*an? '与“Jane”、“Dan”、“Shane”等匹配。

7.使用[c]的字符串不区分大小写的比较,例如,包含[c] 'Ja'。注意,大小写中只忽略字符“A-Z”和“A-Z”。

8.Realm支持以下复合操作符:AND/&&, OR/ ||, AND NOT/!例如,姓名以“J”开头,年龄>= 32。

9.列表属性(例如employees)支持聚合表达式@count/@size、@min、@max、@sum和@avg。@count > 5查找包含5个以上元素的员工列表。

10.字符串和二进制属性的聚合表达式@count/@size,例如name。查找名称为5个字母的所有项。

11.关键路径可以遵循列表属性关系,例如子属性。年龄>= 13,车辆。@avg.milage > 1000。

12.关键路径也可以跟随链接对象(反向链接),例如父对象。年龄:> 25岁及父母。@count = = 2。

  1. 可以用来替换参数,例如child。年龄>= $0(参见下面的示例)。

14.使用函数SORT和distinct可以对不同的值进行排序和查找,例如age > 20 SORT(name ASC, age DESC) distinct (name)。

  • 排序的顺序可以是下列不区分大小写的文字之一:ASC、升序、DESC、降序。

  • 在逗号分隔的列表中,括号内可以出现任意数量的属性。

  • 可以指定任意数量的排序/不同的条件,它们将按照指定的顺序应用。

  • Sort或distinct不能独立操作,这些条件必须附加到至少一个查询筛选器。
    栗子:

const Realm = require('realm');

const CarSchema = {
  name: 'Car',
  properties: {
    make:  'string',
    model: 'string',
    miles: {type: 'int', default: 0},
  }
};

const PersonSchema = {
  name: 'Person',
  properties: {
    name:     'string',
    cars:     {type: 'list', objectType: 'Car'},
  }
};

// Initialize a Realm with Car and Person models
Realm.open({schema: [CarSchema, PersonSchema]})
    .then(realm => {

        // Add persons and their cars
        realm.write(() => {
            let john = realm.create('Person', {name: 'John', cars: []});
            john.cars.push({make: 'Honda',  model: 'Accord', miles: 1500});
            john.cars.push({make: 'Toyota', model: 'Prius',  miles: 2780});

            let joan = realm.create('Person', {name: 'Joan', cars: []});
            joan.cars.push({make: 'Skoda', model: 'Octavia', miles: 1120});
            joan.cars.push({make: 'Ford',  model: 'Fiesta',  miles: 95});
            joan.cars.push({make: 'VW',    model: 'Golf',    miles: 1270});

            let jill = realm.create('Person', {name: 'Jill', cars: []});

            let jack = realm.create('Person', {name: 'Jack', cars: []});
            jack.cars.push({make: 'Porche', model: '911',    miles: 965});
        });

        // Find car owners
        let carOwners = realm.objects('Person').filtered('cars.@size > 0');
        console.log('Car owners')
        for (let p of carOwners) {
            console.log(`  ${p.name}`);
        }

        // Find who has been driver longer than average
        let average = realm.objects('Car').avg('miles');
        let longerThanAverage = realm.objects('Person').filtered('cars.@sum.miles > $0', average);
        console.log(`Longer than average (${average})`)
        for (let p of longerThanAverage) {
            console.log(`  ${p.name}: ${p.cars.sum('miles')}`);
        }

        realm.close();
});

//运行结果:
Car owners
  John
  Joan
  Jack
Longer than average (1288.3333333333333)
  John: 4280
  Joan: 2485
  • 排序

Results 允许您指定一个排序标准,从而可以根据一个或多个属性进行排序。比如说,下面的调用按照英里数对上面例子中返回的汽车进行排序:

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

// 按里程对Honda汽车进行分类
let sortedHondas = hondas.sorted('miles');

// 按降序排序
sortedHondas = hondas.sorted('miles', true);

// 按价格降序排序,然后按里程升序排序
sortedHondas = hondas.sorted([['price', true], ['miles', false]]);

结果也可以根据对象链接到的对象的值进行排序:

let people = realm.objects('Person');

// Sort people by the milage of their cars
let sortedPeople = people.sorted('car.miles');

基本类型的列表可以通过调用sort()按值排序,而不需要指定属性:

let person = realm.objects('Person')[0];
let sortedTestScores = person.testScores.sorted();

注意:只有在对查询进行排序时,结果的顺序才能保证保持一致。出于性能原因,不能保证保留插入顺序。

  • 自动更新

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 实例都有影响,不管其是通过 objectsfiltered 还是 sorted 方法所返回的。

Results 的这个特性不仅让 Realm 保证速度和效率,它同时还让代码更加简洁、更为灵活。比如说,如果您的视图控制器是基于查询结果而现实的,您可以将 Results 存储为一个属性,这样就无需在每次访问前都要刷新数据以确保数据最新了。

您也可以查看变化事件一节以确认 Realm 数据何时被更新,比如说由此来决定应用 UI 何时需要被更新,这样就无必重新检索 Results 了。

  • 查询结果限制

大多数其他数据库技术都提供了从检索中对结果进行“分页”的能力(例如 SQLite 中的 “LIMIT” 关键字)。这通常是很有必要的,可以避免一次性从硬盘中读取太多的数据,或者将太多查询结果加载到内存当中。

由于 Realm 中的检索是惰性的,因此这行这种分页行为是没有必要的。因为 Realm 只会在检索到的结果被明确访问时,才会从其中加载对象。

如果由于 UI 相关或者其他代码实现相关的原因导致您需要从检索中获取一个特定的对象子集,这和获取 Results 对象一样简单,只需要读出您所需要的对象即可。

let cars = realm.objects('Car');

// get first 5 Car objects
let firstCars = cars.slice(0, 5);
  • 架构版本 (schemaVersion)

当打开 Realm 数据库的时候,最后一个可供设置的选项就是 schemaVersion 属性了。如果其被忽略的话,那么 schemaVersion 属性将默认为 0。您需要在初始化现有的某个内含对象的 Realm 数据库架构和其之前架构发生变化的时候,指定这个 schemaVersion 属性。如果架构发生了更新,而 schemaVersion 没有更新,那么就会抛出一个异常。

const PersonSchema = {
  name: 'Person',
  properties: {
    name: 'string'
  }
};

// schemaVersion defaults to 0
Realm.open({schema: [PersonSchema]});
const UpdatedPersonSchema = {
  // 架构名称相同,因此之前 Ralm 当中的
  // `Person` 对象将会被更新
  name: 'Person',
  properties: {
    name: 'string',
    dog:  'Dog'     // 新属性
  }
};

// 这样会抛出异常,因为架构发生了改变
// 而 `schemaVersion` 没有变化
let realm = new Realm({schema: [UpdatedPersonSchema]});

// 这样不会有任何问题,会将 Realm 数据库更新为最新的架构
let realm = new Realm({schema: [UpdatedPersonSchema], schemaVersion: 1});

如果您希望获取当前 Realm 数据库的架构版本的话,您可以使用 Realm.schemaVersion 方法来实现。

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

推荐阅读更多精彩内容