Android實戰——Room的使用,升級沒煩惱

前言

Room相比GreenDao而言是官方推薦的一個關於數據庫的依賴庫,Room更需要開發人員有較專業的SQL數據庫知識,它涉及到SQL的語法編寫和SQL數據庫的升級,如果對SQL語法不懂的開發者來說,使用起來是很有難度的,但對於熟悉SQL語法的開發者來說,用起來比GreenDao好用許多

Room的簡介

Room是Google提供的一個ORM庫。Room提供了三個主要的組件:

  • @Database:@Database用來註解類,該類必須是繼承自RoomDatabase的抽象類。該類主要作用是創建數據庫和創建Dao
  • @Entity:@Entity用來註解實體類,@Database通過entities屬性引用被@Entity註解的類,並利用該類所有字段作爲表的結構
  • @Dao:@Dao用來註解一個接口或者抽象方法,該類的作用是提供訪問數據庫的方法

以上各部分的依賴關係如下圖所示:

在這裏插入圖片描述

Room的配置

配置比較簡單,但是這裏要注意的是,用的是kapt,如果用annotationProcess會報生成的類找不到,因爲我這裏用的是kotlin語言。由於很多項目用的是多Module的依賴形式,如果使用room需要跨module的話,需要使用api去替代implementation

implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation "android.arch.persistence.room:runtime:1.1.1"
implementation "android.arch.persistence.room:rxjava2:1.1.1"
kapt "android.arch.persistence.room:compiler:1.1.1"

Room的使用

下面就以學生信息的實戰來使用room,這裏的學生信息展示圖如下

在這裏插入圖片描述

一、創建Bean對象(表名和字段名)

room的創建通過註解去生成表的結構,主要由下面幾個註解形成

  • @Entity:表示需要持久化的實體,後面參數表示表名
  • @PrimaryKey:表示表中的主鍵
  • @ColumnInfo:表示表中的字段
  • @Embedded:表示表中需要嵌套的對象實體
  • @Ignore:表示表中不需要持久化的字段

特別需要注意的是:在定義的時候,記得對實體對象加上PrimaryKey,否則程序會報錯

@Entity(tableName = "tb_student")
public class Student {

    @PrimaryKey(autoGenerate = true)
    public long id;

    @ColumnInfo(name = "name")
    public String name;

    @ColumnInfo(name = "sex")
    public int sex;

    @Embedded
    public StudentExtendInfo extendInfo;

    @Ignore
    public String phone;

    public static class StudentExtendInfo {
        @ColumnInfo(name = "father_name")
        public String father;
        @ColumnInfo(name = "mother_name")
        public String mother;
    }
}

二、定義數據庫的增刪改查

數據庫的增刪改查都通過註解表示,可以對具體的操作書寫具體的SQL語句。由於room可以和Rxjava一起使用,所以在查詢的時候可以返回Flowable

  • @Dao:表示當前接口爲數據庫的操作接口
  • @Query:表示查詢操作,需要書寫具體的SQL語句
  • @Insert:表示插入操作,需要在參數中填寫插入時發生衝突時的策略
  • @Delete:表示刪除操作
  • @Update:表示修改操作
@Dao
public interface StudentDaoApi {

    @Query("SELECT * FROM TB_STUDENT")
    Flowable<List<Student>> query();

    @Query("SELECT * FROM TB_STUDENT WHERE id IN (:ids)")
    Flowable<List<Student>> queryByIds(long[] ids);

    @Query("SELECT * FROM TB_STUDENT WHERE id = (:id) LIMIT 1")
    Flowable<Student> queryById(long id);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insert(Student... entities);

    @Delete
    void delete(Student entity);

    @Update
    void update(Student entity);
}

三、創建數據庫

數據庫的創建也是通過註解@Database生成,在註解中填寫需要操作的表結構和版本。在這裏我們通過kt的語言,用單例的方式去實現當前的數據庫,並且要繼承RoomDatabase

@Database(entities = [Student::class], version = 1)
abstract class AppDatabaseBuilder : RoomDatabase() {

    abstract val studentDao: StudentDaoApi

    companion object {
        private var INSTANCE: AppDatabaseBuilder? = null

        fun getInstance(context: Context): AppDatabaseBuilder {
            if (INSTANCE == null) {
                synchronized(AppDatabaseBuilder::class.java) {
                    
                    // 生成數據庫文件
                    val builder = Room.databaseBuilder(context.applicationContext,
                            AppDatabaseBuilder::class.java, "db_common.db")

                    if (!BuildConfig.DEBUG) {
                        // 遷移數據庫如果發生錯誤,將會重新創建數據庫,而不是發生崩潰
                        builder.fallbackToDestructiveMigration()
                    }
                    INSTANCE = builder.build()
                }
            }
            return INSTANCE!!
        }
    }
}

四、使用數據庫

在使用的時候,只需要獲取對應的Dao接口就行操作即可

AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().query();
AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().queryByIds(ids);
AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().insert(student);
AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().delete(student);
AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().update(student);

但是我們發現query操作有返回Rxjava的特性,而插入、刪除、修改並沒有,我們可以通過再增加一層封裝,讓插入、刪除、修改也支持Rxjava的特性

public class StudentDao {

    private StudentDao() {

    }

    public static StudentDao getInstance() {
        return SingletonHolder.sInstance;
    }

    private static class SingletonHolder {
        private static final StudentDao sInstance = new StudentDao();
    }

    public Flowable<List<Student>> query(Context context) {
        return AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().query();
    }

    public Flowable<List<Student>> queryByIds(Context context, long[] ids) {
        return AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().queryByIds(ids);
    }

    public Flowable<Student> queryById(Context context, long id) {
        return AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().queryById(id);
    }

    public Observable<Boolean> insert(final Context context, final Student student) {
        return Observable.fromCallable(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().insert(student);
                return true;
            }
        });
    }

    public Observable<Boolean> delete(final Context context, final Student student) {
        return Observable.fromCallable(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().delete(student);
                return true;
            }
        });
    }

    public Observable<Boolean> update(final Context context, final Student student) {
        return Observable.fromCallable(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                AppDatabaseBuilder.Companion.getInstance(context).getStudentDao().update(student);
                return true;
            }
        });
    }
}

封裝過後的使用就貼近Rxjava的特性,需要注意的是對數據庫的操作是需要異步操作的,這在Rxjava也是特別簡單

StudentDao.getInstance().insert(this, student)
            .subscribeOn(Schedulers.io())
            .subscribe(
                    { Log.e("TAG", it.toString()) },
                    { Log.e("TAG", it.toString()) }
            )

五、升級數據庫

在使用的過程中,經歷了一次發版後,發現需要對原來的數據庫表的結構進行修改,這個時候就需要我們掌握升級的SQL語法,room也提供了比較人性化的升級方式Migration,當然也逃不過SQL語法的編寫

在升級的時候,不要忘記將版本號進行更新到新的版本號

@Database(entities = [Student::class], version = 2)
abstract class AppDatabaseBuilder : RoomDatabase() {

    abstract val studentDao: StudentDaoApi

    companion object {
        private var INSTANCE: AppDatabaseBuilder? = null

        fun getInstance(context: Context): AppDatabaseBuilder {
            if (INSTANCE == null) {
                synchronized(AppDatabaseBuilder::class.java) {
                
                    // 生成數據庫文件
                    val builder = Room.databaseBuilder(context.applicationContext,
                            AppDatabaseBuilder::class.java, "db_common.db")
                            .addMigrations(MIGRATION_1_2, MIGRATION_2_3) // 升級數據庫

                    if (!BuildConfig.DEBUG) {
                        //遷移數據庫如果發生錯誤,將會重新創建數據庫,而不是發生崩潰
                        builder.fallbackToDestructiveMigration()
                    }
                    INSTANCE = builder.build()
                }
            }
            return INSTANCE!!
        }

        /**
         * 版本1升級到2的SQL語句
         */
        private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE 'tb_student' ADD COLUMN 'Sid' INTEGER NOT NULL DEFAULT 0")
            }
        }
        
        /**
         * 版本2升級到3的SQL語句
         */
        private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE 'tb_student' ADD COLUMN 'Stext' TEXT NOT NULL DEFAULT ''")
            }
        }
    }
}

結語

在Room的使用中更結合了新技術Rxjava的使用,可見Rxjava也越來越得到重視,不僅如此,SQL語法也給大家提個醒要去掌握基礎的操作,否則room使用起來是很困難的。毫無疑問,google已經將Rxjava和SQL語法當做Android程序員必備的知識,所以不懂得這方面的同學,要加緊補回來哦

Github:RoomDbDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章