【Android】Realm代替SQLite詳解
目錄
一、簡介
Realm是一個嵌入式資料庫,是一個MVCC(多版本併發控制)資料庫。他比SQLite輕量級,執行效率更高,使用更加方便
。它實現了零拷貝、內部加密等,並且支援JSON、流式api、Rxjava等。當然也相容ACID,並且跨平臺。
二、環境配置
先決條件:
- 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("[email protected]");
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("[email protected]");
}
});
- 第二種方式:
通過 Realm.copyToRealm拷貝一個物件並新增到Realm。
通過Realm.copyToRealmOrUpdate 拷貝物件並新增(或更新)到Realm。(什麼情況下是更新呢,就是如果包含主鍵,且主鍵相同的情況下會更新原資料)
注意點:Realm只管理返回的物件,什麼意思呢? Realm.copyToRealm 和 Realm.copyToRealmOrUpdate 會返回一個物件,如果要更改資料,請更改返回物件的資料,而不是最初的源資料
final User user = new User();
user.setName("John");
user.setEmail("[email protected]");
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("[email protected]rporation.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();
還有其他的一些
等等等等。更多的可以檢視該網址 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));
多執行緒例子
加密
單元測試
多程序
介面卡
JSON
等等
六 :Demo
Demo
如有錯誤請指出 ,謝謝咯