之前使用本地数据库都是用sqllite的,偶尔用一下LitePal,听人说Realm多么多么好使,但是我一直都没有去学习,今天有空去瞅了瞅,果然很不错。
它有以下几种特点:
1.易用:Ream 不是在SQLite基础上的ORM,它有自己的数据查询引擎。并且十分容易使用。
2.快速:由于它是完全重新开始开发的数据库实现,所以它比任何的ORM速度都快很多,甚至比SLite速度都要快。
3.跨平台:Realm 支持 iOS & OS X (Objective‑C & Swift) & Android。我们可以在这些平台上共享Realm数据库文件,并且上层逻辑可以不用任何改动的情况下实现移植。
4.高级:Ream支持加密,格式化查询,易于移植,支持JSON,流式api,数据变更通知等高级特性
5.可视化
在开始配置之前你需要确保你满足以下条件:
Android
以外的Java
classpath "io.realm:realm-gradle-plugin:4.3.1"
apply plugin: 'realm-android'
public class MyApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
Realm.init(this);
//默认配置
RealmConfiguration configuration=new RealmConfiguration.Builder().build();
Realm.setDefaultConfiguration(configuration);
}
}
这时候会创建一个叫做 default.realm
的Realm文件,该文件就是realm的数据库文件,一般来说,这个文件位于/data/data/包名/files/。可以通过realm.getPath()
来获得该Realm的绝对路径,可以通过.assetFile(this,"realm file path in assets")方法指定初始化的数据库文件。Realm会把制定路径下的xxx.realm文件copy到Context.getFilesDir()目录中,以替换默认创建的空数据库文件。可以设置默认文件名,通过RealmConfiguration类进行配置。路径似乎改不了,需要看具体设备供应商的实现。
你也可以自定义设置
RealmConfiguration config = new RealmConfiguration.Builder()
.name("myrealm.realm") //文件名
.schemaVersion(0) //版本号
.build();
Realm realm = Realm.getInstance(config);
RealmConfiguration
支持的方法:
Builder.name
: 指定数据库的名称。如不指定默认名为default。Builder.schemaVersion
: 指定数据库的版本号。Builder.encryptionKey
: 指定数据库的密钥。Builder.migration
: 指定迁移操作的迁移类。Builder.deleteRealmIfMigrationNeeded
: 声明版本冲突时自动删除原数据库。Builder.inMemory
: 声明数据库只在内存中持久化。build
: 完成配置构建。创建一个Person类继承RealmObject,在Realm中一个实体类对应一张表,实体类中所包含的属性就是数据表的字段
public class PersonBean extends RealmObject {
private String name;
private int age;
@Override
public String toString() {
return "PersonBean{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public PersonBean setName(String name) {
this.name = name;
return this;
}
public int getAge() {
return age;
}
public PersonBean setAge(int age) {
this.age = age;
return this;
}
}
Realm几乎支持所有的数据类型,需要注意的一点是整数类型 short
、int
和 long
都被映射到 Realm 内的Long类型
它还有一些注解属性
@PrimaryKey
——表示该字段是主键
使用过数据库的同学应该看出来了,PrimaryKey
就是主键。使用@PrimaryKey
来标注,字段类型必须是字符串(String
)或整数(byte
,short
,int
或long
)以及它们的包装类型(Byte
,Short
, Integer
, 或 Long
)。不可以存在多个主键,使用字符串字段作为主键意味着字段被索引(注释@PrimaryKey
隐式地设置注释@Index
)。
重点:切记,Realm数据库的主键字段不是自动增长的,需要自己设置,做添加的时候如果不给id字段值,默认会为0。后面再添加会报错,说id为0的数据已经存在。尤其是批量添加的时候要注意,当心出现只添加了一条记录的悲剧。
@Required
——表示该字段非空
在某些情况下,有一些属性是不能为null
的。使用@Required
可用于用于强行要求其属性不能为空,只能用于Boolean
, Byte
, Short
, Integer
, Long
, Float
, Double
, String
, byte[]
和 Date
。在其它类型属性上使用 @Required
修饰会导致编译失败。
Tip:基本数据类型不需要使用注解 @Required
,因为他们本身就不可为空。
@Ignore
——表示忽略该字段
被添加@Ignore
标签后,存储数据时会忽略该字段。
@Index
——添加搜索索引
为字段添加搜索索引,这样会使得插入的速度变慢,数据量也变得更大。不过在查询速度将变得更快,建议只在优化读取性能的特定情况时添加索引。支持索引:String
,byte
,short
,int
,long
,boolean
和Date
字段。
1.获得Realm操作对象
Realm realm=Realm.getDefaultInstance();
记得在界面销毁时调用close方法
@Override
protected void onDestroy() {
super.onDestroy();
realm.close();
}
做数据操作时要使用事务,你可以使用beginTransaction()来开始事务,使用commitTransaction()来提交事务
realm.beginTransaction();
for (int i=0;i<10;i++){
PersonBean personBean=realm.createObject(PersonBean.class);
personBean.setAge(i).setName("流月");
}
realm.commitTransaction();
你也可以使用executeTransaction()来执行事务代码块
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
PersonBean personBean = realm.createObject(PersonBean.class);
personBean.setAge(66).setName("流月");
}
});
你还可以使用copyToRealmOrUpdate
或copyToRealm
方法插入数据
当Model中存在主键的时候,推荐使用copyToRealmOrUpdate
方法插入数据。如果对象存在,就更新该对象;反之,它会创建一个新的对象。若该Model没有主键,使用copyToRealm
方法,否则将抛出异常。
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(new PersonBean().setAge(66).setName("流月"));
}
});
注意:如果在UI线程中插入过多的数据,可能会导致主线程拥塞。
所以你可以使用executeTransactionAsync
,该方法会开启一个子线程来执行事务,并且在执行完成后进行结果通知。
RealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
PersonBean personBean = realm.createObject(PersonBean.class);
personBean.setAge(66).setName("流月");
}
}, new Realm.Transaction.OnSuccess() {
@Override
public void onSuccess() {
//成功回调
}
},
new Realm.Transaction.OnError() {
@Override
public void onError(Throwable error) {
//失败回调
}
});
注意:如果当
Acitivity
或Fragment
被销毁时,在OnSuccess
或OnError
中执行UI操作,将导致程序奔溃 。用RealmAsyncTask .cancel();
可以取消事务
在onStop
中调用,避免crash
public void onStop () {
if (transaction != null && !transaction.isCancelled()) {
transaction.cancel();
}
}
Realm还支持JSON操作,它可以从JSON中创建对象,官方例子:
// 一个city model public
class City extends RealmObject {
private String city;
private int id;
// getters and setters left out ...
}
// 使用Json字符串插入数据
realm.executeTransaction(new Realm.Transaction() {
@Override public void execute(Realm realm) {
realm.createObjectFromJson(City.class, "{ city: \"Copenhagen\", id: 1 }");
}
});
// 使用InputStream插入数据
realm.executeTransaction(new Realm.Transaction() {
@Override public void execute(Realm realm) {
try {
InputStream is = new FileInputStream(new File("path_to_file"));
realm.createAllFromJson(City.class, is);
} catch (IOException e) {
throw new RuntimeException();
}
}
});
Realm 解析 JSON 时遵循如下规则:
查询所有
public List queryAll() {
Realm mRealm=Realm.getDefaultInstance();
RealmResults PersonBeans= mRealm.where(PeronBean.class).findAll();
return mRealm.copyFromRealm(dogs);
}
注意:
RealmResults
虽然实现了List
接口,不过有很多方法是不能用的。比如add
、addAll
、remove
、clear
等,调用后会直接抛异常。不过也不用当心误用这些方法,因为它们都被标记为@Deprecated
了。
按条件查询
RealmResults personBeans = realm.where(PersonBean.class).lessThan("age", 5).findAll();
realm支持链式的查询方法
RealmQuery
以及or
的使用
在使用where()
方法时,能得到一个RealmQuery
对象,使用方法如下:
RealmResults personBeanList = mRealm.where(PersonBean.class)
.equalTo("name", "流月")
.or().equalTo("name", "幽蓝")
.findAll();
排序
RealmResults personBeanRealmResults = realm.where(PersonBean.class) .findAll();
personBeanRealmResults = personBeanRealmResults.sort("age"); //根据age,正序排列
personBeanRealmResults = personBeanRealmResults.sort("age", Sort.DESCENDING);//逆序排列
统计
RealmResults results = realm.where(PersonBean.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();
先查再改
public void update(View view) {
RealmResults personBeans = realm.where(PersonBean.class).lessThan("age", 5).findAll();
realm.beginTransaction();
for (PersonBean personBean : personBeans) {
personBean.setAge(66);
}
realm.commitTransaction();
}
先查再删
public void delete(View view) {
realm.beginTransaction();
RealmResults personBeans = realm.where(PersonBean.class).equalTo("age", 8).findAll();
personBeans.deleteAllFromRealm();
realm.commitTransaction();
}
删除有四种方式
results.deleteFirstFromRealm(); //删除user表的第一条数据
results.deleteLastFromRealm();//删除user表的最后一条数据
results.deleteFromRealm(index);//删除指定位置的数据
results.deleteAllFromRealm();//删除user表的全部数据
1、Realm保存的结果其实是在一个文件里面,默认的文件名是"default.realm",在"Context.getFilesDir()"目录中,即:/data/data//files/default.realm。意思是,当你在应用管理里面给当前app"清除数据",realm数据库的数据会丢失。故我们需要把默认的数据文件放到asset目录中,当数据库初始化时再copy到"Context.getFilesDir()"下。
2、Realm的实例需要在每次的具体操作中获取,可以看成是一个数据操作的sessin,用完后必须close关闭。
3、创建对象和操作对象必须在同一个线程.违反了这条会报错:java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.比如我们在UI线程查询出来的对象, 想要异步地删除或者更新, 我们必须在新的线程重新查询.
4、没有主键自增的功能, 需要自己控制主键自增.
5、从List中删除了一项之后, 最后的一项会移动过来补到被删除的那一项原来的位置. 这是因为人家就是这么设计的stackoverflow. 默认情况下是没有排序的, 数据按照添加的顺序返回, 但是这并不是一种保证, 所以当删除了中间的元素, 后面的会补上这个位置, 以保证底层的数据是放在一起的. 解决办法就是指定一个排序规则.
6、查询出来的对象不可以临时改变其数据, 否则会报错:java.lang.IllegalStateException: Changing Realm data can only be done from inside a transaction.
7、不支持级联删除. 即从数据库中删除一个对象的时候, 不会删除其中RealmObject子类或RealmList类型的字段在数据库中对应的数据. 这点也可以理解, 因为model之间的关系可能是多对多的. 所以需要实现级联删除的地方需要手动处理.