1. 程式人生 > >【Android】Realm代替SQLite詳解

【Android】Realm代替SQLite詳解

目錄

一、簡介

先決條件:

安裝配置:

三、使用

3.4 增

3.5 查

3.6 改

3.7 刪

3.9 加密

四:問題

不是自增長

自增長

支援SQL

多執行緒例子

加密

單元測試

多程序

介面卡

JSON

一、簡介

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.支援型別:booleanbyteshort,int,long,floatdouble,StringDate和,byte[]RealmObjectRealmList<? extends RealmObject> 還支援BooleanByteShortIntegerLongFloat和 DoubleTip:整數型別 shortint和 long都被對映到 Realm 內的相同型別(實際上為 long )

3.註解欄位

  @PrimaryKey

  • 主鍵(不可以存在多個主鍵)
  • 欄位必須是String、 integer、byte、short、 int、long 以及它們的封裝類Byte, Short, Integer, and Long
  • 字串上的註釋會隱式設定註釋@Index(即索引)

  @Required

  • 表示該欄位非空
  • 只有BooleanByteShortIntegerLongFloatDoubleStringbyte[]並且Date可以進行註釋@Required。如果將其新增到其他欄位型別,編譯將失敗。

  @Ignore

  • 如果您不想將模型中的欄位儲存到其Realm,請使用註釋@Ignore

  @Index

  • 新增搜尋索引
  • 主鍵一樣,這會使寫入速度稍慢,但會使讀取速度更快。(它還會使您的Realm檔案稍大,以儲存索引。)最好只在優化特定情況下的讀取效能時新增索引。
  • 索引型別StringbyteshortintlongbooleanDate

3.3 事務操作

與讀取操作不同,Realm中的寫入操作必須包含在事務中

同步:

  • 第一種方式:
realm.beginTransaction(); //開啟事務

//do something...

realm.commitTransaction();//提交事務

在異常處理裡寫上 

realm.cancelTransaction(); //取消事務
  • 第二種方式:

realm提供了事務塊,不用手動的寫beginTransactioncommitTransaction以及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> {}

            
  • 非同步查詢全部資料

當你資料量大的時候採用非同步查詢(當然我都是非同步查詢的)

注意點:

  1. 非同步查詢的資料不是馬上有的,當然Realm也提供了監聽器,註冊 addChangeListener 監聽器,在回撥裡進行資料的處理。還有一個方法 realmResults.isLoaded ,這個只能判斷是否查詢完畢。
  2. 你只能在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

如有錯誤請指出 ,謝謝咯