目录
一、简介
Realm是一个嵌入式数据库,是一个MVCC(多版本并发控制)数据库。他比SQLite轻量级,执行效率更高,使用更加方便
。它实现了零拷贝、内部加密等,并且支持JSON、流式api、Rxjava等。当然也兼容ACID,并且跨平台。
二、环境配置
先决条件:
- Android Studio 1.5.1或更高版本
- JDK 7.0或更高版本
- 最新版本的Android SDK
- Android API等级9或更高(Android 2.3及更高版本)
注意: Realm不支持Android之外的Java。我们不再支持Eclipse作为IDE; 请迁移到Android Studio。
安装配置:
1.在项目的build文件加上
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "io.realm:realm-gradle-plugin:5.7.0"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
2.在app的build文件加上
apply plugin: 'realm-android'
三、使用
3.1.初始化Realm
1 在Application的onCreate()方法中调用Realm.init()
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Realm.init(this);
}
}
2. 在Application的oncreate()方法中对Realm进行相关配置
- 使用默认配置
public class MyApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
Realm.init(this);
//默认配置会默认创建一个default.realm的Realm的文件,位于Context.getFilesDir
//使用Realm.getPath获取Realm的绝对路径。
RealmConfiguration config = new RealmConfiguration.Builder().build();
Realm.setDefaultConfiguration(config);
}
}
- 使用自定义配置
@Override
public void onCreate() {
super.onCreate();
Realm.init(this);
RealmConfiguration config = new RealmConfiguration.Builder()
.name("myrealm.realm") //文件名
.schemaVersion(1) //版本
.build();
Realm.setDefaultConfiguration(config);
//其他会用到的参数
//encryptionKey() 指定数据库的密钥
//migration(new StudentInfoBean()) 指定迁移操作的迁移类
//inMemory() 声明数据库只在内存中持久化
}
3.2创建实体
1.新建一个实体类继承RealmObject
注意:Realm 不支持嵌套类
public class StudentInfoBean extends RealmObject {
@Required
public String name;
public int age;
public String sex;
private RealmList<Course> mCourses;
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 String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public class Course extends RealmObject{
private String name;
}
}
也可以实现RealmModel,并且加上注解@RealmClass
@RealmClass
public class StudentInfoBean implements RealmModel {
}
2.支持类型:boolean
, byte
, short
,int
,long
,float
, double
,String
, Date
和,byte[]
, RealmObject
, RealmList<? extends RealmObject>
还支持Boolean
, Byte
, Short
, Integer
, Long
, Float
和 Double
Tip:整数类型 short
、int
和 long
都被映射到 Realm 内的相同类型(实际上为 long )
3.注解字段
@PrimaryKey
- 主键(不可以存在多个主键)
- 字段必须是String、 integer、byte、short、 int、long 以及它们的封装类Byte, Short, Integer, and Long
- 字符串上的注释会隐式设置注释
@Index(即索引)
@Required
- 表示该字段非空
- 只有
Boolean
,Byte
,Short
,Integer
,Long
,Float
,Double
,String
,byte[]
并且Date
可以进行注释@Required
。如果将其添加到其他字段类型,编译将失败。
@Ignore
- 如果您不想将模型中的字段保存到其Realm,请使用注释
@Ignore
@Index
- 添加搜索索引
- 主键一样,这会使写入速度稍慢,但会使读取速度更快。(它还会使您的Realm文件稍大,以存储索引。)最好只在优化特定情况下的读取性能时添加索引。
- 索引类型
String
,byte
,short
,int
,long
,boolean
和Date
3.3 事务操作
与读取操作不同,Realm中的写入操作必须包含在事务中
同步:
- 第一种方式:
realm.beginTransaction(); //开启事务
//do something...
realm.commitTransaction();//提交事务
在异常处理里写上
realm.cancelTransaction(); //取消事务
- 第二种方式:
realm提供了事务块,不用手动的写beginTransaction
,commitTransaction
以及cancelTransaction,你可以使用executeTransaction方法,它会自动处理开始/提交
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
//do something...
}
});
异步:
如果大量的操作,那当然需要异步,realm也提供了方法那就是 executeTransactionAsync,同时也提供了成功和失败的回调
注意点:需要在 Activity 或 Fragment 退出的时候需要取消事务。不然退出后更新ui会造成程序崩溃
RealmAsyncTask transaction =realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
//do something
}
}, 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.
}
});
//取消事务
public void onStop () {
if (transaction != null && !transaction.isCancelled()) {
transaction.cancel();
}
}
3.4 增
以下例子全部用同步事务块的例子来操作(异步的方法上面已经讲过了)
注意点:如果有内部类的情况下,只需要也将内部类添加近Realm,再进行外部添加就好了
如下
mRealm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
User user = realm.createObject(User.class);
user.setName("John");
user.setEmail("john@corporation.com");
Course course=realm.createObject(Course.class);
course.setName("English");
user.setCourse(course);
}
});
接下来的例子都是没有内部类的情况
- 第一种方式:
通过 Realm.createObject() 返回对象并添加到Realm
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");
}
});
- 第二种方式:
通过 Realm.copyToRealm拷贝一个对象并添加到Realm。
通过Realm.copyToRealmOrUpdate 拷贝对象并添加(或更新)到Realm。(什么情况下是更新呢,就是如果包含主键,且主键相同的情况下会更新原数据)
注意点:Realm只管理返回的对象,什么意思呢? Realm.copyToRealm 和 Realm.copyToRealmOrUpdate 会返回一个对象,如果要更改数据,请更改返回对象的数据,而不是最初的源数据
final User user = new User();
user.setName("John");
user.setEmail("john@corporation.com");
mRealm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.copyToRealm(user);//添加
realm.copyToRealmOrUpdate(user);//添加或更新
}
});
- 第三种方式:
通过 insert 或 insertOrUpdate 直接添加数据,它的好处少一步copy对象。所以如果要插入许多对象,建议使用
insert 或 insertOrUpdate。
注意点:insertOrUpdate 的用法与 copyToRealmOrUpdate 一样
final User user = new User();
user.setName("John");
user.setEmail("john@corporation.com");
mRealm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.insert(user);
realm.insertOrUpdate(user);
}
});
3.5 查
- 同步查询全部数据
需要用RealmResults接受数据
RealmResults<StudentInfoBean> realmResults=mRealm.where(User.class).findAll()
RealmResults是什么呢?
通过下面的源码追踪 发下最后也是继承 AbstractList 的
注意:虽然也实现了List接口,但是它还是有许多方法不能使用的,比如 add、addAll、remove、clear,都是不能使用的
public class RealmResults<E> extends OrderedRealmCollectionImpl<E>{}
abstract class OrderedRealmCollectionImpl<E>
extends AbstractList<E> implements OrderedRealmCollection<E> {}
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {}
- 异步查询全部数据
当你数据量大的时候采用异步查询(当然我都是异步查询的)
注意点:
- 异步查询的数据不是马上有的,当然Realm也提供了监听器,注册 addChangeListener 监听器,在回调里进行数据的处理。还有一个方法 realmResults.isLoaded ,这个只能判断是否查询完毕。
- 你只能在Looper线程上使用异步查询,如果没有Looper的线程使用异步查询,那么会抛出IllegalStateException
final RealmResults<User> realmResults = mRealm.where(User.class).findAllAsync();
realmResults.addChangeListener(new RealmChangeListener<RealmResults<User>>() {
@Override
public void onChange(RealmResults<User> user) {
mMainAdapter.setNewData(user);
}
});
- equalsTo
1.查询字段名是name,内容是John的数据。equalsTo的第一个参数一定是字符串,第二个参数就是你存储的类型
final RealmResults<User> realmResults = mRealm.where(User.class).equalTo("name", "John").findAllAsync();
2.(同时满足多个条件的多条件查询)如果是涉及到类与其内部类的查询 举个例子 如name为John 课程为英语课程的就如下
RealmResults<User> realmResults = mRealm.where(User.class).equalTo("name", "John").equalTo("course.name", "English").findAllAsync();
3.(满足其中一个条件即可的多条件查询)如果多条件查询 举个例子 查找name为John或者GG的数据
final RealmResults<User> realmResults = mRealm.where(User.class).equalTo("name", "John").or.equalTo("name", "GG").findAllAsync();
- in
举个例子 查找name为John或者GG的数据,等同于上买的
final RealmResults<User> realmResults = mRealm.where(User.class).equalTo("name", "John").or.equalTo("name", "GG").findAllAsync();
- findFirst
查询第一条数据
mRealm.where(User.class).findFirst();
- 聚合
RealmResults
为结果集中的求和和平均值等操作提供聚合便捷方法。
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();
还有其他的一些
- sum 计算总和
- sort 排序
- notEqualTo 不等于比较
- not() 否定条件
- min 最小值
- isEmpty 查找为空的值
- greaterThan 大于
- greaterThanOrEqualTo 大于等于
- distinct
- endsWith
- like
等等等等。更多的可以查看该网址 Realm Api
3.6 改
只要写入都是需要在事务中操作。先查询再修改
mRealm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
//先查找后得到User对象
User user = mRealm.where(User.class).findFirst();
user.setName("John")
}
});
3.7 删
删除还是很简单的,不过需要得到数据后才能删除。
final RealmResults<Dog> results = realm.where(Dog.class).findAll();
// All changes to data must happen in a transaction
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
// remove single match
results.deleteFirstFromRealm();
results.deleteLastFromRealm();
// remove a single object
Dog dog = results.get(5);
dog.deleteFromRealm();
// Delete all matches
results.deleteAllFromRealm();
}
});
3.8 版本升级(数据迁移)
Realm的其他操作都很简单。就是数据版本升级最麻烦,当数据库结构发生改变时,就需要升级版本了。
当然你也可以直接删掉目前存在的版本(一般用在前期开发阶段),这样子就不用更新了
使用方法如下:
RealmConfiguration config = new RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.build()
需要如下两个步骤
1.设置新的架构版本设置新的架构版本,在原版本代码+1,如老版本1,新版本2.并且设置migration 代码类
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()
2. 例子(包括MyMigration类)
版本1开始,没有Person类,我先创建Person
public class Person extends RealmObject {
private String name;
public void setDog(RealmList<Dog> dog) {
mDog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
版本2 让name属性设置required注解
public class Person extends RealmObject {
@Required
private String name;
public void setDog(RealmList<Dog> dog) {
mDog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
版本三:创建Dog类,并且再Person添加Dog对象,以及List类型
public class Person extends RealmObject {
@Required
private String name;
private RealmList<Dog> mDog;
private Dog mDogObject;
public Dog getDogObject() {
return mDogObject;
}
public void setDogObject(Dog dogObject) {
mDogObject = dogObject;
}
public RealmList<Dog> getDog() {
return mDog;
}
public void setDog(RealmList<Dog> dog) {
mDog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Dog extends RealmObject {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class MyMigration implements RealmMigration {
@Override
public void migrate(final DynamicRealm realm, long oldVersion, long newVersion) {
RealmSchema realmSchema = realm.getSchema();
if (oldVersion == 1) {
realmSchema.create("Person")
.addField("name", String.class);
oldVersion++;
}
if (oldVersion == 2) {
realmSchema.get("Person")
.setRequired("name", true)
.transform(new RealmObjectSchema.Function() {
@Override
public void apply(DynamicRealmObject obj) {
DynamicRealmObject person = realm.createObject("Person");
person.set("name", "John");
}
});
oldVersion++;
}
if (oldVersion == 3) {
RealmObjectSchema realmObjectSchema = realmSchema.create("Dog")
.addField("name", String.class);
realmSchema.get("Person")
.addRealmObjectField("mDogObject", realmObjectSchema)
.addRealmListField("mDog", realmObjectSchema);
oldVersion++;
}
}
}
realmSchema类其他用法
schema.setNullable("name", true):
取消name必填- shema.removeField("name"):移除name
- schema.renameField("name","personName"):重命名
3.9 加密
请注意我们许可证的“出口合规”部分,因为如果您位于有美国出口限制或禁运的国家/地区,
它会限制使用Realm。
通过将512位加密密钥(64字节)传递给配置,可以在磁盘上对Realm文件进行加密RealmConfiguration.Builder.encryptionKey:
byte[] key = new byte[64];
new SecureRandom().nextBytes(key);
RealmConfiguration config = new RealmConfiguration.Builder()
.encryptionKey(key)
.build();
Realm realm = Realm.getInstance(config);
当给出加密密钥时,Realm使用标准AES-256加密透明地加密和解密数据。每次打开Realm时都必须
提供相同的加密密钥。有关如何在Android KeyStore中的运行之间安全存储密钥以便其他应用程序
无法读取它们的示例,请参阅 加密例子
四:问题
主键(包括自增长)
Realm不支持自动增量ID。这主要是因为不可能在分布式环境中生成这样的密钥,并且存储在本地Realm和同步Realm中的数据之间的兼容性是高优先级
不是自增长
官方提供的解决方法是:
1.用GUID代替,它可以保证唯一性,即使在设备离线时也可以由设备创建:用GUID代替,它可以保证唯一性,即使在设备离线时也可以由设备创建:
2.或者用Date来保证唯一性
public class Person extends RealmObject {
@PrimaryKey
private String id = UUID.randomUUID().toString();
private Date createdAt = new Date();
private String name;
}
自增长
Number maxValue = realm.where(MyObject.class).max("primaryKeyField");
long pk = (maxValue != null) ? maxValue + 1 : 0;
支持SQL
这个是不是SQlite哦,所以不支持。
Intent传递:
因为RealmObjects不能直接传递,可以传递主键,接着在另外一个地方查询该条。
五:配合使用
rxjava
他是一个可选的依赖性,Realm不会自动包含他,这样的好处是,你可以选择使用那个版本的Rxjava,所以你要自己在build.gradle里添加
Realm realm = Realm.getDefaultInstance();
GitHubService api = retrofit.create(GitHubService.class);
realm.where(Person.class).isNotNull("username").findAllAsync().asObservable()
.filter(persons.isLoaded)
.flatMap(persons -> Observable.from(persons))
.flatMap(person -> api.user(person.getGithubUserName())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> showUser(user));
例子:https://github.com/realm/realm-java/tree/master/examples/encryptionExample
多线程例子
加密
单元测试
多进程
适配器
JSON
等等
六 :Demo
Demo
如有错误请指出 ,谢谢咯