Realm 数据库封装地址: Realm 封装 Demo
Github 下载地址: Realm 数据库封装
Realm 数据库, 目前有 Java, Objective‑C, React Native, Swift, Xamarin 的几种实现, 是一套用来取代 SQLite 的解决方案.
本文面向 Android 开发, 所以只讨论 Java 实现.
目前 Realm Java 的最新版本是 2.3.1.
官方文档在此: realm java doc, 花一个下午就可以基本过一遍, 之后随时查用.
我写了一个小程序 TodoRealm, 使用 Realm 做数据库实现的一个 To-do 应用, 在实际使用的过程中也有一些发现.
本文是我自己看文档的时候的一些记录, 有一些实际使用时的发现也穿插在对应的章节了.
在项目的根 build.gradle 的文件中添加:
- buildscript {
- repositories {
- jcenter()
- }
- dependencies {
- classpath "io.realm:realm-gradle-plugin:2.3.0"
- }
- }
然后在 app 的 build.gradle 文件中添加:
- apply plugin: 'realm-android'
Done.
Model 类只要继承
即可.
- RealmObject
- public class User extends RealmObject {
- private String name;
- private int age;
- @Ignore
- private int sessionId;
- // Standard getters & setters generated by your IDE…
- public String getName() { return name; }
- public void setName(String name) { this.name = name; }
- public int getAge() { return age; }
- public void setAge(int age) { this.age = age; }
- public int getSessionId() { return sessionId; }
- public void setSessionId(int sessionId) { this.sessionId = sessionId; }
- }
Model 类中可以包含的字段类型包括基本数据类型 (及它们的装箱类型) 和 Date 类, 另外也可以包含
的子类或者是
- RealmObject
.
- RealmList<? extends RealmObject>
在字段上加注解可以定义字段的性质:
表明字段非 null. 原生类型和
- @Required
类型默认是非 null 的.
- RealmList
字段永远是可以为 null 的.
- RealmObject
表示字段不会被存储.
- @Ignore
加索引.
- @Index
加主键, 主键只能有一个, 主键默认加索引.
- @PrimaryKey
但是注意主键默认没有加
, 如果主键要求非 null, 需要显式添加
- @Required
.
- @Required
有主键才能使用
这个方法. 主键类型必须是 String 或者整型 (byte, short, int, long) 或者它们的装箱类型(Byte, Short, Integer, Long).
- copyToRealmOrUpdate()
有主键的对象创建的时候不能使用
方法, 而应该使用
- createObject(Class<E> clazz)
附上主键.
- createObject(Class<E> clazz, Object primaryKeyValue)
或者用
或
- copyToRealm(obj)
, 前者遇到主键冲突时会崩溃, 后者遇到主键冲突会更新已有对象.
- copyToRealmOrUpdate(obj)
Realm 中的数据对象是自动更新 (Auto-Updating) 的, 对象一旦被查询出来, 后续发生的任何数据改变也会立即反映在结果中, 不需要刷新对象.
这是一个非常有用的特性, 结合数据变化的通知可以很方便地刷新 UI.
Realm model 对象间可以很方便地建立关系.
你可以在 Model 中存储另一个对象的引用, 建立多对一的关系; 也可以存储一组对象
, 建立一对多或多对多的关系.
- RealmList<T>
的 getter 永远也不会返回 null, 它只会返回一个为空的 list. 把这个字段设置为 null 可以清空这个 list.
- RealmList<T>
Realm 在使用之前需要调用初始化:
- Realm.init(context);
建议把它放在 Application 的
里.
- onCreate()
配置类:
定义了 Realm 的创建配置. 最基本的配置:
- RealmConfiguration
- RealmConfiguration config = new RealmConfiguration.Builder().build();
它会创建一个叫
的文件, 放在
- default.realm
的目录下.
- Context.getFilesDir()
如果我们想自定义一个配置, 可以这样写:
- // The RealmConfiguration is created using the builder pattern.
- // The Realm file will be located in Context.getFilesDir() with name "myrealm.realm"
- RealmConfiguration config = new RealmConfiguration.Builder()
- .name("myrealm.realm")
- .encryptionKey(getKey())
- .schemaVersion(42)
- .modules(new MySchemaModule())
- .migration(new MyMigration())
- .build();
- // Use the config
- Realm realm = Realm.getInstance(config);
所以我们是可以有多个配置, 访问多个 Realm 实例的.
我们可以把配置设置为默认配置:
- Realm.init(this);
- RealmConfiguration config = new RealmConfiguration.Builder().build();
- Realm.setDefaultConfiguration(config);
之后用
取到的就是这个默认配置对应的实例.
- Realm.getDefaultInstance()
迁移的策略是通过 config 指定的:
- RealmConfiguration config = new RealmConfiguration.Builder()
- .schemaVersion(2) // Must be bumped when the schema changes
- .migration(new MyMigration()) // Migration to run instead of throwing an exception
- .build()
其中
实现了
- MyMigration
接口, 在
- RealmMigration
方法中根据新旧版本号进行一步一步地升级.
- migrate()
具体例子见 Migration.
开发的时候为了方便我用的是
, 这样在需要数据库迁移的时候直接就删了数据重新开始了.
- .deleteRealmIfMigrationNeeded()
一个打开的 Realm 实例会持有一些资源, 有一些是 Java 不能自动管理的, 所以就需要打开实例的代码负责在不需要的时候将其关闭.
Realm 的 instance 是引用计数的 (reference counted cache), 在同一个线程中获取后续实例是免费的, 但是底层的资源只有当所有实例被释放了之后才能释放. 也即你调用了多少次
, 就需要调用相应次数的
- getInstance()
方法.
- close()
比较建议的方法是在 Activity 或 Fragment 的生命周期中处理 Realm 实例的开启和释放:
中
- onCreate()
,
- getInstance()
中
- onDestroy()
.
- close()
中
- onCreateView()
,
- getInstance()
中
- onDestroyView()
.
- close()
如果多个 Fragment 相关的都是同一个数据库实例, 那么在 Activity 中处理更好一些.
写操作一般的流程是这样:
- // Obtain a Realm instance
- Realm realm = Realm.getDefaultInstance();
- realm.beginTransaction();
- //... add or update objects here ...
- realm.commitTransaction();
这里创建对象可以用
方法或者
- createObject()
方法. 前者是先创建再 set 值, 后者是先 new 对象再更新数据库.
- copyToRealm()
如果不想自己处理
,
- beginTransaction()
和
- cancelTransaction()
, 可以直接调用
- commitTransaction()
方法:
- realm.executeTransaction()
- realm.executeTransaction(new Realm.Transaction() {
- @Override
- public void execute(Realm realm) {
- User user = realm.createObject(User.class);
- user.setName("John");
- user.setEmail("john@corporation.com");
- }
- });
因为 transactions 之间是互相阻塞的.
异步执行可以用这个方法:
- realm.executeTransactionAsync(new Realm.Transaction() {
- @Override
- public void execute(Realm bgRealm) {
- User user = bgRealm.createObject(User.class);
- user.setName("John");
- user.setEmail("john@corporation.com");
- }
- }, new Realm.Transaction.OnSuccess() {
- @Override
- public void onSuccess() {
- // Transaction was a success.
- }
- }, new Realm.Transaction.OnError() {
- @Override
- public void onError(Throwable error) {
- // Transaction failed and was automatically canceled.
- }
- });
这两个回调是 Optional 的, 它们只能在有 Looper 的线程调用.
注意: 这个方法的返回值对象可以用于在 Activity/Fragment 生命周期结束的时候取消未完的操作.
所有的写操作都要放在 transaction 中进行, 如上, 不同的操作只是其中具体方法不同.
删除操作:
- final RealmResults < User > users = getUsers();
- // method 1:
- users.get(0).deleteFromRealm();
- // method 2:
- users.deleteFromRealm(0);
- // delete all
- users.deleteAllFromRealm();
更新操作:
- realm.copyToRealmOrUpdate(obj);
注意: 这个方法需要 Model 有主键, 会更新 obj 的主键对应的对象, 如果不存在则新建对象.
查询可以流式地写:
- // Or alternatively do the same all at once (the "Fluent interface"):
- RealmResults<User> result2 = realm.where(User.class)
- .equalTo("name", "John")
- .or()
- .equalTo("name", "Peter")
- .findAll();
查询条件默认是 and 的关系, or 则需要显式指定.
这个
是继承 Java 的
- RealmResults
的, 是有序的集合, 可以通过索引访问.
- AbstractList
永远不会为 null, 当查不到结果时, 它的
- RealmResults
返回 0.
- size()
基本上所有的查询都是很快进行的, 足够在 UI 线程上同步进行.
所以绝大多数情况在 UI 线程上使用
是没有问题的.
- findAll()
如果你要进行非常复杂的查询, 或者你的查询是在非常大的数据集上进行的, 你可以选择异步查询, 使用
.
- findAllAsync()
- in()
如果想要查询的某一个字段的值是在一个集合中, 比如我有一个 id 的集合, 我现在想把 id 在这个集合中的项目全都查出来, 这就可以使用 in 操作符:
- RealmResults < TodoList > toDeleteLists = realm.where(TodoList.class). in ("id", ids).findAll();
查询的时候可以利用 link 或关系来查询, 比如一个 Person 类中含有一个
的字段.
- RealmList<Dog> dogs
- RealmResults<Person> persons = realm.where(Person.class)
- .equalTo("dogs.color", "Brown")
- .findAll();
利用字段名
来查询一个 dog 的属性, 再查出拥有这种特定属性 dog 的人.
- dogs.
但是反向地, 我们能不能查询主人是满足特定属性的人的所有 dogs 呢? 目前 (2017.2.17) 这种查询仍是不支持的. 这里有讨论: realm-java-issue-607.
所以两种解决办法: 一是做两次查询; 二是在 Dog 类的 model 里加入对 Person 的引用.
可以添加一个 listener, 在数据改变的时候收到更新.
- public class MyActivity extends Activity {
- private Realm realm;
- private RealmChangeListener realmListener;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- realm = Realm.getDefaultInstance();
- realmListener = new RealmChangeListener() {
- @Override
- public void onChange(Realm realm) {
- // ... do something with the updates (UI, etc.) ...
- }};
- realm.addChangeListener(realmListener);
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- // Remove the listener.
- realm.removeChangeListener(realmListener);
- // Close the Realm instance.
- realm.close();
- }
- }
注意 listener 需要在不用的时候删除掉.
可以用这样删除所有的 listeners:
- realm.removeAllChangeListeners();
Listener 不一定要和 Realm 绑定, 也可以和具体的
或者
- RealmObject
绑定. 当 Listener 被调用的时候, 它绑定的对象是自动更新的, 不需要手动刷新.
- RealmResults
用 Stetho 不能直接查看 Realm 的数据库, 看不到.
需要用这个工具配置一下: stetho-realm.
之后就可以在浏览器中查看 Realm 的数据库了.
(但是感觉这个工具不是很好用, 有时候不显示数据, 有时候显示的是旧数据.)
也可以用官方提供的 Realm Browser 来查看, 但是只有 Mac 版.
如何查看看这里: StackOverflow answer.
比如一旦给 Adapter 绑定了数据, 之后的数据更新只需要在 onChange() 里面通知 Adapter 调用
即可.
- notifyDataSetChanged()
当然我并没有用
和
- RealmBaseAdapter
, 估计这两个更好用, 官方有例子, 这里不再赘述.
- RealmRecyclerViewAdapter
这里有的也不能说是缺点, 只是使用起来觉得不方便的地方.
比如我们在 UI 线程查询出来的对象, 想要异步地删除或者更新, 我们必须在新的线程重新查询.
- java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
- java.lang.IllegalStateException: Changing Realm data can only be done from inside a transaction.
子类或
- RealmObject
类型的字段在数据库中对应的数据. Issue #1104, Issue #2717. 这点也可以理解, 因为 model 之间的关系可能是多对多的. 所以需要实现级联删除的地方需要手动处理.
- RealmList
对象即不能被 mock 也不能被 new; 所有的 Model 对象也不能被 mock. 因为
- RealmResults
- Mockito can only mock non-private & non-final classes.
我的练习 Demo:
来源: http://blog.csdn.net/u014608640/article/details/77530917