Room使用詳解及常用數(shù)據(jù)庫對比

Android Jetpack組件 —— Room使用詳解及常用數(shù)據(jù)庫對比

一、 Room介紹

  • Room是Jetpack組件中一個對象關(guān)系映射(ORM)庫??梢院苋菀讓?SQLite 表數(shù)據(jù)轉(zhuǎn)換為 Java 對象。
  • Room 在 SQLite 上提供了一個抽象層,以便在充分利用 SQLite 的強大功能的同時,能夠流暢地訪問數(shù)據(jù)庫。
  • 支持與LiveData、RxJava、Kotlin協(xié)成組合使用。
  • Google 官方強烈推薦使用Room。
image.png

二、 Room接入以及基礎(chǔ)使用

Room引用配置

dependencies {
  def room_version = "2.2.5"

  implementation "androidx.room:room-runtime:$room_version"
  annotationProcessor "androidx.room:room-compiler:$room_version"

  // optional - RxJava support for Room
  implementation "androidx.room:room-rxjava2:$room_version"

  // optional - Guava support for Room, including Optional and ListenableFuture
  implementation "androidx.room:room-guava:$room_version"

  // optional - Test helpers
  testImplementation "androidx.room:room-testing:$room_version"

}

Room使用

  • 在使用數(shù)據(jù)的時候,需要主要涉及到Room三個部分:
    • Entity: 數(shù)據(jù)庫中表對應(yīng)的實體
    • Dao: 操作數(shù)據(jù)庫的方法
    • DataBase: 創(chuàng)建數(shù)據(jù)庫實例
第一步 創(chuàng)建實體類
@Entity(tableName = UserModel.USER_TABLE_NAME,
        indices = {@Index(value = {UserModel.FACE_ID}, unique = true),
                @Index(value = {UserModel.NAME}, unique = true)})
public class UserModel implements Parcelable {

    public static final String USER_TABLE_NAME = "user" ;


    public static final String NAME = "name";
    public static final String FACE_ID = "faceId";

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = BaseColumns._ID)
    public long id;

    @NonNull
    @ColumnInfo(name = FACE_ID)
    public String faceId;

    @NonNull
    @ColumnInfo(name = NAME)
    public String name;

    public UserModel(@NonNull String faceId, @NonNull String name) {
        this.faceId = faceId;
        this.name = name;
    }
   
}
  • @Entity: 代表一個表中的實體,默認類名就是表名,如果不想使用類名作為表名,可以給注解添加表名字段@Entity(tableName = "user")
  • @PrimaryKey: 每個實體都需要自己的主鍵
  • @NonNull 表示字段,方法,參數(shù)返回值不能為空
  • @ColumnInfo(name = “faceId”) 如果希望表中字段名跟類中的成員變量名不同,添加此字段指明
第二步 創(chuàng)建Dao
    @Dao
    public interface UserDao {
    
        @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                UserModel.NAME + " = :name")
        LiveData<UserModel> queryByName2Lv(String name);
    
    
        @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                UserModel.NAME + " = :name")
        UserModel queryByName2Model(String name);
    
    
        @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                UserModel.FACE_ID + " = :faceId")
        LiveData<UserModel> queryByFaceId2Lv(String faceId);

        @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                UserModel.FACE_ID + " = :faceId")
        UserModel queryByFaceId2Model(String faceId);
    
        @Query("SELECT COUNT(*) FROM " + UserModel.USER_TABLE_NAME)
        int count();
    
    
        @Query("SELECT * FROM " + UserModel.USER_TABLE_NAME)
        LiveData<List<UserModel>> queryAllByLv();
    
    
        @Query("SELECT * FROM " + UserModel.USER_TABLE_NAME)
        List<UserModel> queryAll();
    
        @Update
        public int updateUsers(List<UserModel> userModels);
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        long insertUser(UserModel userModel);
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        long[] insertAllUser(List<UserModel> userModels);

        @Delete
        void delete(UserModel... userModels);

        @Delete
        void deleteAll(List<UserModel> userModels);

        @Query("DELETE FROM " + UserModel.USER_TABLE_NAME + " WHERE " +
                UserModel.FACE_ID + " = :faceId")
        int deleteByFaceId(String faceId);

    }
  • DAO是數(shù)據(jù)訪問對象,指定SQL查詢,并讓他與方法調(diào)用相關(guān)聯(lián)。
  • DAO必須是一個接口或者抽象類。
  • 默認情況下,所有的查詢都必須在單獨的線程中執(zhí)行
第三步 創(chuàng)建Database
@Database(entities =  {
        UserModel.class
},
        version = 1, exportSchema = true)
public abstract class RoomDemoDatabase extends RoomDatabase {

    public abstract UserDao userDao();

    public static final String DATABASE_NAME = "room_demo";

    private static RoomDemoDatabase sInstance;

    public static RoomDemoDatabase getInstance(Context context) {
        if (sInstance == null) {
            synchronized (RoomDemoDatabase.class) {
                if (sInstance == null) {
                    sInstance = buildDatabase(context);
                }
            }
        }
        return sInstance;
    }


    private static RoomDemoDatabase buildDatabase(final Context appContext) {
        return Room.databaseBuilder(appContext, RoomDemoDatabase.class, DATABASE_NAME)
                .allowMainThreadQueries()
//                .openHelperFactory(new SafeHelperFactory("123456".toCharArray()))
                .addCallback(new Callback() {
                    @Override
                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
                        super.onCreate(db);

                    }

                    @Override
                    public void onOpen(@NonNull SupportSQLiteDatabase db) {
                        super.onOpen(db);
                    }

                })
                .build();
    }


}
  • 創(chuàng)建一個抽象類繼承自RoomDatabase

  • 給他添加一個注解@Database表名它是一個數(shù)據(jù)庫,注解有兩個參數(shù)第一個是數(shù)據(jù)庫的實體,它是一個數(shù)組,可以傳多個,當數(shù)據(jù)庫創(chuàng)建的時候,會默認給創(chuàng)建好對應(yīng)的表,第二個參數(shù)是數(shù)據(jù)庫的版本號

  • 定義跟數(shù)據(jù)庫一起使用的相關(guān)的DAO類

  • 創(chuàng)建一個RoomDemoDatabase的單例,防止同時打開多個數(shù)據(jù)庫的實例

  • 使用Room提供的數(shù)據(jù)庫構(gòu)建器來創(chuàng)建該實例,第一個參數(shù)application,第二個參數(shù)當前數(shù)據(jù)庫的實體類,第三個參數(shù)數(shù)據(jù)庫的名字

  • exportSchema = true 支持導(dǎo)出Room生成的配置文件

          javaCompileOptions {
              annotationProcessorOptions {
                  arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
              }
          }
    

三、 Room 數(shù)據(jù)庫遷移

添加新的實體類

@Entity(tableName = FaceModel.FACE_TABLE_NAME,
        foreignKeys = {
                @ForeignKey(entity = UserModel.class,
                        parentColumns = "faceId",
                        childColumns = "faceId",
                        onUpdate = ForeignKey.CASCADE,
                        onDelete = ForeignKey.CASCADE
                )
        },
        indices = {@Index(value = {"faceId"})}
)
public class FaceModel  {

    public static final String FACE_TABLE_NAME = "face";


    public static final String TYPE = "type";
    public static final String FACE_ID = "faceId";
    public static final String PATH = "path";


    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = BaseColumns._ID)
    public long id;

    @NonNull
    @ColumnInfo(name = PATH)
    public String path;

    @ColumnInfo(name = TYPE)
    public int type;

    @NonNull
    @ColumnInfo(name = FACE_ID)
    public String faceId;

    public FaceModel(@NonNull String path, int type, @NonNull String faceId) {
        this.path = path;
        this.type = type;
        this.faceId = faceId;
    }

}

配置實體類

@Database(entities =  {
    UserModel.class, FaceModel.class
        },
    version = 2, exportSchema = true)

添加Migration

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        Log.i(TAG, "migrate: ");
        // Create the new table
          String sql = "CREATE TABLE IF NOT EXISTS face (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `path` TEXT NOT NULL, `type` INTEGER NOT NULL, `faceId` TEXT NOT NULL, FOREIGN KEY(`faceId`) REFERENCES `user`(`faceId`) ON UPDATE CASCADE ON DELETE CASCADE )";
        database.execSQL(
                sql);
        String sql2 = "CREATE INDEX IF NOT EXISTS `index_face_faceId` ON face (`faceId`)";
        database.execSQL(
                sql2);
    }
};

private static RoomDemoDatabase buildDatabase(final Context appContext) {
    return Room.databaseBuilder(appContext, RoomDemoDatabase.class, DATABASE_NAME)
            .allowMainThreadQueries()
             .addMigrations(MIGRATION_1_2)
             //.openHelperFactory(new SafeHelperFactory("123456".toCharArray()))
            .addCallback(new Callback() {
                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                    super.onCreate(db);

                }

                @Override
                public void onOpen(@NonNull SupportSQLiteDatabase db) {
                    super.onOpen(db);
                }

            })
            .build();
}
  • Sql 升級語句,可以根據(jù)Room導(dǎo)出的json文件獲取

多版本升級

.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_1_4)
image

四、 Room 關(guān)聯(lián)表

關(guān)聯(lián)表配置
  • foreignKeys 配置外鍵
  • parentColumns:父表外鍵
  • childColumns:子表外鍵
  • NO_ACTION: parent表中某行被刪掉(更新)后。child表中與parent這一行發(fā)生映射的行不發(fā)生任何改變
  • RESTRICT: parent表中想要刪除(更新)某行。如果child表中有與這一行發(fā)生映射的行。那么改操作拒絕。
  • SET_NULL/SET_DEFAULT:parent表中某行被刪掉(更新)后。child表中與parent這一行發(fā)生映射的行設(shè)置為NULL(DEFAULT)值。
  • CASCADE:parent表中某行被刪掉(更新)后。child表中與parent這一行發(fā)生映射的行被刪掉(其屬性更新到對應(yīng)設(shè)置)
    @Entity(tableName = FaceModel.FACE_TABLE_NAME,
        foreignKeys = {
                @ForeignKey(entity = UserModel.class,
                        parentColumns = "faceId",
                        childColumns = "faceId",
                        onUpdate = ForeignKey.CASCADE,
                        onDelete = ForeignKey.CASCADE
                )
        },
        indices = {@Index(value = {"faceId"})}
    )
創(chuàng)建嵌套對象
    public class UserAndFaceModel {
    
        @Relation(parentColumn = "faceId", entityColumn = "faceId", entity = FaceModel.class)
        public List<FaceModel> faceModels;
    
        @Embedded
        public UserModel userModel;
    
    }
  • Relation: A convenience annotation which can be used in a POJO to automatically fetch relation entities.
    When the POJO is returned from a query, all of its relations are also fetched by Room.
  • Embedded: Marks a field of an Entity or POJO to allow nested fields (fields of the annotated
    field's class) to be referenced directly in the SQL queries.
創(chuàng)建關(guān)聯(lián)Dao
@Dao
public interface UserAndFaceDao {

    @Transaction // 保障事務(wù)
    @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
            UserModel.NAME + " = :name")
    LiveData<UserAndFaceModel> queryByName2Lv(String name);

    @Transaction
    @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
            UserModel.NAME + " = :name")
    UserAndFaceModel queryByName2Model(String name);

    @Transaction
    @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
            UserModel.FACE_ID + " = :faceId")
    LiveData<UserAndFaceModel> queryByFaceId2Lv(String faceId);

    @Transaction
    @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
            UserModel.FACE_ID + " = :faceId")
    UserAndFaceModel queryByFaceId2Model(String faceId);

    @Transaction
    @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME )
    List<UserAndFaceModel> queryAll();

}
關(guān)聯(lián)表數(shù)據(jù)插入注意
  • 保障事務(wù)
RoomDemoDatabase.getInstance(MainActivity.this.getApplicationContext()).runInTransaction(new Runnable() {
            @Override
            public void run() {
                UserModel userModel = new UserModel(1, "1", "2");
                RoomDemoDatabase.getInstance(MainActivity.this.getApplicationContext()).userDao().insertUser(userModel);
                FaceModel faceModel = new FaceModel("fa", 1, "fa");
                RoomDemoDatabase.getInstance(MainActivity.this.getApplicationContext()).faceDao().insertFace(faceModel);
            }
        });

五、 數(shù)據(jù)庫數(shù)據(jù)加密

5.1 文件加密

SQLCipher
SQLCipher是一個在SQLite基礎(chǔ)之上進行擴展的開源數(shù)據(jù)庫,它主要是在SQLite的基礎(chǔ)之上增加了數(shù)據(jù)加密功能。
  • 加密性能高、開銷小,只要5-15%的開銷用于加密
  • 完全做到數(shù)據(jù)庫100%加密
  • 采用良好的加密方式(CBC加密模式——密文分組鏈接模式)
  • 使用方便,做到應(yīng)用級別加密
  • 采用OpenSSL加密庫提供的算法
  • 開源,支持多平臺
  • SQLCipjer加密原理介紹
Realm
Realm 介紹:
  • Realm 是一個 MVCC (多版本并發(fā)控制)數(shù)據(jù)庫,
  • 由Y Combinator公司在2014年7月發(fā)布一款支持運行在手機、
  • 平板和可穿戴設(shè)備上的嵌入式數(shù)據(jù)庫,目標是取代SQLite。
  • Realm 本質(zhì)上是一個嵌入式數(shù)據(jù)庫,他并不是基于SQLite所構(gòu)建的。
  • 它擁有自己的數(shù)據(jù)庫存儲引擎,可以高效且快速地完成數(shù)據(jù)庫的構(gòu)建操作。
  • 和SQLite不同,它允許你在持久層直接和數(shù)據(jù)對象工作。
  • 在它之上是一個函數(shù)式風(fēng)格的查詢api,眾多的努力讓它比傳統(tǒng)的SQLite 操作更快 。
Realm 加密:
  • 借助 Realm,我們可以輕松地進行加密,
  • 因為我們可以輕松地決定數(shù)據(jù)庫內(nèi)核所應(yīng)該做的事情。
  • 內(nèi)部加密和通常在 Linux 當中做的加密哪樣很類似。
  • 因為我們對整個文件建立了內(nèi)存映射,
  • 因此我們可以對這部分內(nèi)存進行保護。
  • 如果任何人打算讀取這個加密的模塊,
  • 我們就會拋出一個文件系統(tǒng)警告“有人正視圖訪問加密數(shù)據(jù)。
  • 只有解密此模塊才能夠讓用戶讀取?!蓖ㄟ^非常安全的技術(shù)我們有一個很高效的方式來實現(xiàn)加密。
  • 加密并不是在產(chǎn)品表面進行的一層封裝,而是在內(nèi)部就構(gòu)建好的一項功能。

5.2 內(nèi)容加密

  • 在存儲數(shù)據(jù)時加密內(nèi)容,在查詢時進行解密。但是這種方式不能徹底加密,數(shù)據(jù)庫的表結(jié)構(gòu)等信息還是能被查看到,另外檢索數(shù)據(jù)也是一個問題。
加密算法 描述 優(yōu)點 缺點
DES,3DES 對稱加密算法 算法公開、計算量小、加密速度快、加密效率高 雙方都使用同樣密鑰,安全性得不到保證
AES 對稱加密算法 算法公開、計算量小、加密速度快、加密效率高 雙方都使用同樣密鑰,安全性得不到保證
XOR 異或加密 兩個變量的互換(不借助第三個變量),簡單的數(shù)據(jù)加密 加密方式簡單
Base64 算不上什么加密算法,只是對數(shù)據(jù)進行編碼傳輸
SHA 非對稱加密算法。安全散列算法,數(shù)字簽名工具。著名的圖片加載框架Glide在緩存key時就采用的此加密 破解難度高,不可逆 可以通過窮舉法進行破解
RSA 非對稱加密算法,最流行的公鑰密碼算法,使用長度可變的秘鑰 不可逆,既能用于數(shù)據(jù)加密,也可以應(yīng)用于數(shù)字簽名 RSA非對稱加密內(nèi)容長度有限制,1024位key的最多只能加密127位數(shù)據(jù)
MD5 非對稱加密算法。全程:Message-Digest Algorithm,翻譯為消息摘要算法 不可逆,壓縮性,不容易修改,容易計算 窮舉法可以破解

5.3 Room數(shù)據(jù)庫數(shù)據(jù)庫加密

  • SQLCipher并不直接支持Room的數(shù)據(jù)庫進行加密,所以沒法直接實現(xiàn)。
  • 可以通過開源庫(swac-saferoom)進行數(shù)據(jù)加密(底層也是通過SQLCipher對數(shù)據(jù)庫文件加密)
集成 swac-saferoom
添加 maven { url "https://s3.amazonaws.com/repo.commonsware.com" }
dependencies {
  implementation 'com.commonsware.cwac:saferoom:1.1.3'
}
添加openHelperFactory
    private static AppDatabase buildDatabase(final Context appContext) {
        return Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME)
            .allowMainThreadQueries()
            .openHelperFactory(new SafeHelperFactory("123456".toCharArray()))
            .addCallback(new Callback() {
                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                    super.onCreate(db);
                
                }

                @Override
                public void onOpen(@NonNull SupportSQLiteDatabase db) {
                    super.onOpen(db);
                }
              
            })
            .build();
    }

六、 Room與其他數(shù)據(jù)庫對比

參數(shù) Room GreenDao Realm
集成包大小 0.05M 0.05M 9.06M
插入10000條速度 551ms 806ms 195ms
查詢10000條速度 126ms 71ms 4ms
刪除10000條速度 3ms 6ms 5ms
更新10000條速度 622ms 838ms 242ms
image

七、 數(shù)據(jù)庫調(diào)試工具分享

debug調(diào)試

使用debug-db 可以在瀏覽器查看表結(jié)構(gòu)及數(shù)據(jù)

普通數(shù)據(jù)庫
    - implementation 'com.amitshekhar.android:debug-db:1.0.6'
加密數(shù)據(jù)庫
    debug {
        resValue("string", "PORT_NUMBER", "8081")
        resValue("string", "DB_PASSWORD_PERSON", "123456")
    }
    
    implementation 'com.amitshekhar.android:debug-db-encrypt:1.0.6'
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容