Realm 使用说明

Realm

一个跨平台移动数据库引擎

资料

Github

官网

说明文档

中文说明文档1.0.0

stetho 官网

stetho-realm Github

导入

  • 配置项目的 build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "io.realm:realm-gradle-plugin:2.1.1"
    }
}
  • 配置模组的 build.gradle
apply plugin: 'realm-android'

混淆

不需要

基本用法

模型

继承RealmObject,或者实现RealmModel接口并添加注解@RealmClass

public class User extends RealmObject {
    // 主键,可为空,默认已索引,String、byte、short、int、long、Byte、Short、Integer、Long
    @PrimaryKey
    private long id;

    // 非空,Boolean、ByteShort、Integer、Long、Float、Double、String、byte[]、Date
    @Required
    private String name;

    // 索引,String、byte、short、int、long、boolean、Date
    @Index
    private int age;

    @Ignore // 忽略
    private int tempReference;

    private Dog dog; // 对单
    private RealmList<Cat> cats; // 对多

    // 省略 get/set 方法
}

public class Dog extends RealmObject {
    public String name;
}

// 接口+注解,创建的托管对象缺少生成的部分方法,使用 RealmObject 的静态方法替代
@RealmClass
public class Cat implements RealmModel {
    public String name;
}

初始化

// Application 中初始化
Realm.init(context);

Realm实例

Realm 实例是线程单例化的,也就是说多次在同一线程调用静态构建器会返回同一 Realm 实例。

// Context.getFilesDir() 目录下的 default.realm 文件
Realm realm = Realm.getDefaultInstance();

RealmConfiguration config = new RealmConfiguration.Builder().build(); // 默认的 RealmConfiguration
Realm.setDefaultConfiguration(configuration); // 设置默认 RealmConfiguration

// 配合 Configuration 使用
Realm.deleteRealm(configuration); // 清除数据
Realm realm = Realm.getInstance(configuration); // 获取自定义的 Realm
RealmConfiguration config = new RealmConfiguration.Builder()
        .name("myrealm.realm")  // 库文件名
        .encryptionKey(getKey())  // 加密
        .schemaVersion(42)  // 版本号
        .modules(new MySchemaModule())  // 结构
        .migration(new MyMigration())  // 迁移
        .build();

// 非持久化的、存在于内存中的 Realm 实例
RealmConfiguration myConfig = new RealmConfiguration.Builder()
    .name("myrealm.realm")
    .inMemory()
    .build();

事务

所有的写操作(添加、修改和删除对象),必须包含在写入事务中,确保线程安全。如果一个写入事务正在进行,那么其他的线程的写入事务就会阻塞它们所在的线程,使用异步事务以避免阻塞

读取事务是隐式的,读操作可在任何时候进行。当写入事务被提交到 Realm 时,该 Realm 的所有其他实例都将被通知,读入隐式事务将自动刷新你每个 Realm 对象。

realm.beginTransaction(); // 开始事务
realm.commitTransaction(); // 提交事务
realm.cancelTransaction(); // 取消事务

// 自动处理写入事务的开始和提交,并在错误发生时取消写入事务
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        // ...
    }
});

// 异步事务,4种重载,onSuccess 和 onError 不是必须,非 Looper 线程中只有空(null)回调函数被允许使用
RealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() {
  @Override
  public void execute(Realm bgRealm) {
    // 异步不能使用外部的 Realm
    User user = bgRealm.createObject(User.class);
    user.setName("John");
    user.setEmail("john@corporation.com");
  }
}, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
        // 事务成功,Looper 传回前台执行
    }
}, new Realm.Transaction.OnError() {
    @Override
    public void onError(Throwable error) {
        // 事务失败,自动取消,Looper 传回前台执行
    }
});

// 退出注意取消事务
public void onStop () {
  if (transaction != null && !transaction.isCancelled()) {
      transaction.cancel();
  }
}

添加

User realmUser = realm.createObject(User.class);

// 有主键需要添加主键,主键无自增
User realmUser = realm.createObject(User.class, primaryKeyValue);

// 普通对象转化为托管对象,建议有主键的bean使用
User user = new User();
User realmUser = realm.copyToRealm(user); // 主键冲突时报异常
User realmUser = realm.copyToRealmOrUpdate(user); // 主键冲突时更新,无主键报异常

删除

final RealmResults<User> results = realm.where(User.class).findAll();

realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        // 删除一个托管对象
        results.get(0).deleteFromRealm();
        // 使用以下方法,可避免自动更新集合前,某些元素有可能不在集合内,引起的崩溃
        results.deleteFromRealm(0);

        // 删除集合的首末对象
        results.deleteFirstFromRealm();
        results.deleteLastFromRealm();

        // 删除所有集合内对象
        results.deleteAllFromRealm();

        // 删除所有
        realm.delete(User.class);
        realm.deleteAll();
    }
});

修改

直接修改托管对象,即修改了数据库。
bean 有主键时,可使用copyToRealmOrUpdate()转化相同主键的对象为托管来修改数据库。

查询

  • 查询条件

    between()、greaterThan()、lessThan()、greaterThanOrEqualTo()、lessThanOrEqualTo()
    equalTo()、notEqualTo()
    contains()、beginsWith()、endsWith()
    isNull()、isNotNull()
    isEmpty()、isNotEmpty()
    
    RealmResults<User> result = realm.where(User.class)
            .between("age", 0, 99)
            .findAll(); // 执行查询
    
    User user = realm.where(User.class)
            .equalTo("name", "John", Case.INSENSITIVE)  // 忽略大小写
            .findFirst(); // 执行查询
    
  • 关联查询

    realmresults<user> users = realm.where(user.class)
           .equalto("dogs.name", "fluffy")  // 关联查询,以“.”分隔
           .equalto("dogs.color", "brown")  // 条件与
           .findall();
    
    realmresults<user> users = realm.where(user.class)
           .equalto("dogs.name", "fluffy")
           .findall()
           .where()  // 在结果中继续查询
           .equalto("dogs.color", "brown")
           .findall();
    
  • 逻辑运算符

    or()、beginGroup()、endGroup()
    
    RealmResults<User> results = realm.where(User.class)
          .greaterThan("age", 10)  // 大于等于
          .beginGroup()  // 左括号
          .equalTo("name", "Peter")
          .or()  // 或,如果不加此操作符,默认为于
          .contains("name", "Jo")
          .endGroup()  // 左右括号
          .findAll();
    
  • 排序

    RealmResults<User> results = realm.where(User.class).findAll();
    results = result.sort("age"); // 升序
    results = result.sort("age", Sort.DESCENDING); // 降序
    
    RealmResults<User> results = realm.where(User.class)
          .findAllSorted("age", Sort.DESCENDING); // 降序
    
  • 聚合

    RealmResults<User> results = realm.where(User.class).findAll();
    long   sum     = results.sum("age").longValue();
    long   min     = results.min("age").longValue();
    long   max     = results.max("age").longValue();
    double average = results.average("age");
    long   matches = results.size();
    
  • 异步

步查询需要使用Handler来传递查询结果。在没有 Looper 的线程中使用异步查询会导致 IllegalStateException异常被抛出。

Listener 只工作于 Looper 线程。对于非 Looper 线程请使用Realm.waitForChange()

private RealmResults<User> results;

public void onStart() {
    realm = Realm.getDefaultInstance();
    // 立刻返回一个 RealmResults<User>,当其完成时,RealmResults 实例会被更新
    results = realm.where(User.class).findAllAsync();

    realm.addChangeListener(listener); // Realm 注册监听
    results.addChangeListener(listener); // 结果注册监听

    if (results.isLoaded()) {
      // 完成加载执行
    }

    results.load(); // 阻塞线程指导异步完成

}

public void onStop () {
  realm.removeChangeListener(listener); // Realm移除监听
  realm.removeAllChangeListeners(); // Realm移除所有监听

  results.removeChangeListener(listener); // 结果移除监听
  results.removeChangeListeners(); // 结果移除所有监听
}

private RealmChangeListener listener = new RealmChangeListener<RealmResults<User>>() {
  @Override
  public void onChange(RealmResults<User> results) {
      // 在 Looper 线程,每次更新后执行
      // 非 Looper 线程,使用 Realm.waitForChange()
  }
};

关闭

Realm 实例是基于引用计数的, 调用getInstance()获取了几次实例,就需要调用close()关闭几次

UI 线程外的 Looper 线程

public class MyThread extends Thread {

    private Realm realm;

    public void run() {
        Looper.prepare();
        try {
            realm = Realm.getDefaultInstance();
            //...
            Lopper.loop();
        } finally {
            if (realm != null) {
                realm.close();
            }
        }
    }
}
// AsyncTask
protected Void doInBackground(Void... params) {
    Realm realm = Realm.getDefaultInstance();
    try {
        // ...
    } finally {
        realm.close();
    }

    return null;
}
new Thread(new Runnable() {
    @Override
    public void run() {
        Realm realm = null;
        try {
            realm = Realm.getDefaultInstance();
            // ...
        } finally {
            if (realm != null) {
                realm.close();
            }
        }
    }
}).start();

注意

  • 基本数据类型永远不能为空,RealmObject数据类型永远可以为空
  • 目前不支持finaltransientvolatile修饰的成员变量
  • 支持使用递归关系,但要注意死循环的问题, Realm 不会检查RealmList的循环嵌套
  • 设置一个类型为RealmList的属性为空值(null)会清空该列表,即列表长度变为0。但并不会删除列表中的任何RealmObject
  • 在没有 Looper 的线程中使用异步查询会导致IllegalStateException异常被抛出。

进阶用法

JSON

JSON 包含空值(null)属性,创建更新对象,对象属性不可为空时抛出异常

Json 和对象的属性不同的,对象属性不变

realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Dog dog = realm.createObjectFromJson(Dog.class, "{\"name\": \"dog\"}");
    }
});

realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        try {
            InputStream is = getAssets().open("user.json");
            realm.createAllFromJson(User.class, is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});

DynamicRealm

某些数据模型在编译期是无法获得的。例如在处理数据迁移(migration)或CSV文件的时候,此时使用 DynamicRealm 可以在没有 RealmObject 子类的情况下操作 Realm 数据

RealmConfiguration realmConfig = new RealmConfiguration.Builder().build();
DynamicRealm realm = DynamicRealm.getInstance(realmConfig);

// 创建 DynamicRealmObject 实例
DynamicRealmObject user = realm.createObject("User");

// 通过字符串访问数据,而不是 RealmObject 的定义
String name = person.getString("name");
int age = person.getInt("age");

// DynamicRealm 会忽略 schema、migration 以及 schema 版本的检查,但结构依然存在。获取不存在的属性会报异常
person.getString("I don't exist");

// 查询工作相同
RealmResults<DynamicRealmObject> users = realm.where("User")
    .equalTo("name", "John")
    .findAll();

schema 结构

  • Realm 使用所有项目中的 Realm 模型类来创建 schema。但这个行为是可以改变的,例如,你可以通过使用 RealmModule 让 Realm 只包含所有模型类的一个子集。
@RealmModule(classes = { User.class, Dog.class })
public class MyModule {
}

RealmConfiguration config = new RealmConfiguration.Builder()
        .modules(new MyModule())  // 设置使用的 schema
        .build();

RealmConfiguration config = new RealmConfiguration.Builder()
        .modules(new MyModule(), new MyOtherModule())  // 可以设置多个 schema
        .build();
  • 在库中使用到的 Realm 必须通过 RealmModule 来暴露和使用其 schema。
// 库必须使用 library = true,以阻止默认创建。
// allClasses = true,即为使用所有
@RealmModule(library = true, allClasses = true)
public class MyLibraryModule {
}

// 库需要确切的设置 RealmModule
RealmConfiguration libraryConfig = new RealmConfiguration.Builder()
  .name("library.realm")
  .modules(new MyLibraryModule())
  .build();

// Apps 中添加库的 RealmModule
RealmConfiguration config = new RealmConfiguration.Builder()
  .name("app.realm")
  .modules(Realm.getDefaultModule(), new MyLibraryModule())
  .build();

数据库升级

  • 不保存旧数据

    RealmConfiguration config = new RealmConfiguration.Builder()
        .deleteRealmIfMigrationNeeded()
        .build()
    
  • 数据迁移

    RealmConfiguration config = new RealmConfiguration.Builder()
        .schemaVersion(2)  // 结构改变时增加,默认初始值为0
        .migration(migration)  // 数据迁移方案
        .build()
    
    RealmMigration migration = new RealmMigration() {
      @Override
      public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
    
         // 动态 Realm 获取数据库结构
         RealmSchema schema = realm.getSchema();
    
         // 迁移版本 1: 增加一个类
         // Example:
         // public User extends RealmObject {
         //     private String name;
         //     private int age;
         //     // getters and setters left out for brevity
         // }
         if (oldVersion == 0) {
            schema.create("User")
                .addField("name", String.class)
                .addField("age", int.class);
            oldVersion++;
         }
    
         // 迁移版本 2: 增加一个主键和对象引用
         // Example:
         // public Person extends RealmObject {
         //     @PrimaryKey
         //     private long id;
         //     private String name;
         //     private int age;
         //     private Dog favoriteDog;
         //     private RealmList<Dog> dogs;
         //     // getters and setters left out for brevity
         // }
         if (oldVersion == 1) {
            schema.get("User")
                .addField("id", long.class, FieldAttribute.PRIMARY_KEY)  // 增加主键熟悉“id”
                .addRealmObjectField("favoriteDog", schema.get("Dog"))  // 增加对象
                .addRealmListField("dogs", schema.get("Dog")); // 增加对象列表
            oldVersion++;
         }
      }
    }
    

加密

Realm 文件可以通过传递一个512位(64字节)的密钥参数给Realm.getInstance().encryptionKey()来加密存储在磁盘上

byte[] key = new byte[64];
new SecureRandom().nextBytes(key);
RealmConfiguration config = new RealmConfiguration.Builder()
  .encryptionKey(key)
  .build();

Realm realm = Realm.getInstance(config);

高阶用法

Android相关

  • Adapter

    ListView使用RealmBaseAdapterRecyclerViews使用RealmRecyclerViewAdapter

    dependencies {
      compile 'io.realm:android-adapters:1.4.0'
    }
    
  • Intent

    RealmObject 不能通过 Intent 传递,可以通过传递属性然后再查询

  • AsyncTask

    private class DownloadOrders extends AsyncTask<Void, Void, Long> {
        @Override
        protected Long doInBackground(Void... voids) {
            // 后台子线程,获取使用并关闭 Realm
            Realm realm = Realm.getDefaultInstance();
            try {
                realm.createAllFromJson(Order.class, api.getNewOrders());
                Order firstOrder = realm.where(Order.class).findFirst();
                long orderId = firstOrder.getId();
                return orderId;
            } finally {
                realm.close();
            }
        }
    
        @Override
        protected void onPostExecute(Long orderId) {
            // 返回主线程,通过id查询对象,进行操作
        }
    }
    
  • IntentService

    ChangeListener 在 IntentService 中不会工作。尽管 IntentService 本身是一个 Looper 线程,但每次 onHandleIntent 的调用是独立的事件。你可以注册监听器的调用不会返回失败,但他们永远不会被触发。

    public class OrdersIntentService extends IntentService {
        public OrdersIntentService(String name) {
            super("OrdersIntentService");
        }
    
        @Override
        protected void onHandleIntent(Intent intent) {
            // 后台子线程,获取使用并关闭 Realm
            Realm realm = Realm.getDefaultInstance();
            realm.createAllFromJson(Order.class, api.getNewOrders());
            Order firstOrder = realm.where(Order.class).findFirst();
            long orderId = firstOrder.getId();
            realm.close();
        }
    }
    

Retrofit

GitHubService service = restAdapter.create(GitHubService.class);
List<Repo> repos = service.listRepos("octocat");

// Retrofit 获取的对象转换成 Realm 对象
realm.beginTransaction();
List<Repo> realmRepos = realm.copyToRealmOrUpdate(repos);
realm.commitTransaction();

RxJava

RealmRealmResultsRealmObjectDynamicRealmDynamicRealmObject可以转化为Observable

Realm realm = Realm.getDefaultInstance();
GitHubService api = retrofit.create(GitHubService.class);
// 组合 Realm, Retrofit 和 RxJava (使用 Retrolambda),
realm.where(Person.class)
        .isNotNull("username")
        .findAllAsync()
        .asObservable()  // 转化为 Observable
        .filter(persons.isLoaded)
        .flatMap(persons -> Observable.from(persons))
        .flatMap(person -> api.user(person.getGithubUserName()))
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(user -> showUser(user));

Parceler

compile "org.parceler:parceler-api:1.0.3"
apt "org.parceler:parceler:1.0.3"
// 项目编译完成,RealmObject 转化成 RealmProxy
@Parcel(implementations = { UserRealmProxy.class },
        value = Parcel.Serialization.BEAN,
        analyze = { User.class })
public class User extends RealmObject {
    // ...
}
  • 如果你的模型包含 RealmList,那么你需要注册一个特殊 adapter
  • 一旦对象被打包(parcelled),它将变为一个有当前数据快照,不再被 Realm 管理的一个 unmanaged 对象。之后该对象的数据变化不会被 Realm 写入。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 介绍 Realm 是一个 MVCC (多版本并发控制)数据库,由Y Combinator公司在2014年7月发布一...
    带心情去旅行阅读 64,217评论 34 134
  • 目录 Getting StartedGetting HelpModelsRelationshipsWritesQu...
    Jafir阅读 8,056评论 2 4
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 听说最近外卖快送员很火,是不是很久没看到香港黑帮大片了,看看这些你会觉得,红星的马仔改头换面了,:)~(比喻而已)...
    大海的喜欢阅读 189评论 0 0
  • 跟好久没联系的初中好友们联系上了 建了一个聊天群 熊宝宝跟我说突然想起了初中 因为我们共同的初中同学一个学霸今天在...
    辛夷柴胡法半夏阅读 171评论 0 0