Android Room 使用詳解

一、概述

Room提供了一個訪問SQLite的抽象層,以便在利用SQLite的全部功能的同時進(jìn)行流暢的數(shù)據(jù)庫訪問。

要在項(xiàng)目中使用Room需要在程序的build.gradle文件中添加以下依賴

dependencies {
    def room_version = "2.1.0-alpha03"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version" // use kapt for Kotlin

    // 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 - Coroutines support for Room
    implementation "androidx.room:room-coroutines:$room_version"

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

Room主要有以下三個部分組成:

  1. Database:
    標(biāo)有 @Database注解的類需要具備以下特征:
    • 繼承RoomDatabase的抽象類
    • 在注釋中包括與數(shù)據(jù)庫關(guān)聯(lián)的實(shí)體列表(@Database(entities ={ }))
    • 包含一個無參的抽象方法并返回一個使用@Dao注解的類
  2. Entity:對應(yīng)數(shù)據(jù)庫中的表
  3. DAO:包含訪問數(shù)據(jù)庫的方法

以上各部分的依賴關(guān)系如下圖所示:

room_architecture.png

下面使用一個簡單的例子介紹各部分如何使用:
User

@Entity
public class User {
    @PrimaryKey
    public int uid;

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

    @ColumnInfo(name = "last_name")
    public String lastName;
}

UserDao

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
           "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

AppDatabase

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

在創(chuàng)建完上面的文件之后,可以使用以下代碼獲得數(shù)據(jù)庫的實(shí)例:

AppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();

在實(shí)例化AppDatabase對象時,應(yīng)遵循單例設(shè)計(jì)模式,因?yàn)槊總€RoomDatabase實(shí)例都相當(dāng)消耗性能,并且您很少需要訪問多個實(shí)例。

二、 Entity定義數(shù)據(jù)

默認(rèn)情況下,Room為實(shí)體中定義的每個字段創(chuàng)建一個列。如果實(shí)體有不想持久的字段,則可以使用@Ignore來注解它們。必須通過Database類中的entities數(shù)組引用實(shí)體類。
下面的代碼片段顯示了如何定義實(shí)體:

@Entity
public class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;
}

使用主鍵

每個實(shí)體必須定義至少1個字段作為主鍵。即使只有1個字段,仍然需要用@PrimaryKey注解字段。此外,如果您想Room自動分配IDs給實(shí)體,則可以設(shè)置@PrimaryKeyautoGenerate屬性。如果實(shí)體具有復(fù)合主鍵,則可以使用@Entity注解的primaryKeys屬性,如下面的代碼片段所示:

@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
    public String firstName;
    public String lastName;
}

默認(rèn)情況下,Room使用類名作為數(shù)據(jù)庫表名。如果希望表具有不同的名稱,請?jiān)O(shè)置@Entity注解的tableName屬性
SQLite中的表名不區(qū)分大小寫。
與tableName屬性類似,Room使用字段名稱作為數(shù)據(jù)庫中的列名。如果希望列具有不同的名稱,請將@ColumnInfo注解添加到字段中,如下面的代碼片段所示:

@Entity(tableName = "users")
public class User {
    @PrimaryKey
    public int id;

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

    @ColumnInfo(name = "last_name")
    public String lastName;
}

索引

需要索引數(shù)據(jù)庫中的某些列以加快查詢速度。若要向?qū)嶓w添加索引,在@Entity注釋中添加indices屬性 ,下面的代碼片段演示了這個注解過程:

@Entity(indices = {@Index("name"),
        @Index(value = {"last_name", "address"})})
public class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String address;

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

    @Ignore
    Bitmap picture;
}

有時,數(shù)據(jù)庫中的某些字段或字段組必須是唯一的。可以通過將@Index注解的unique屬性設(shè)置為true來強(qiáng)制執(zhí)行此唯一性屬性。下面的代碼示例防止表中包含兩行,它們包含firstName和lastName列的相同值集:

@Entity(indices = {@Index(value = {"first_name", "last_name"},
        unique = true)})
public class User {
    @PrimaryKey
    public int id;

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

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

    @Ignore
    Bitmap picture;
}

三、Dao訪問數(shù)據(jù)

DAO既可以是接口,也可以是抽象類。如果是抽象類,它可以有一個構(gòu)造函數(shù),它把RoomDatabase作為唯一的參數(shù)。Room在編譯時創(chuàng)建每個DAO實(shí)現(xiàn)。

Insert

當(dāng)您創(chuàng)建一個DAO方法并用@Insert注解時,Room生成一個實(shí)現(xiàn),在一個事務(wù)中將所有參數(shù)插入到數(shù)據(jù)庫中

@Dao
public interface MyDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertUsers(User... users);

    @Insert
    public void insertBothUsers(User user1, User user2);

    @Insert
    public void insertUsersAndFriends(User user, List<User> friends);
}

如果@Insert方法只接收1個參數(shù),則可以返回一個Long型的值,這是插入項(xiàng)的新rowId。如果參數(shù)是數(shù)組或集合,則應(yīng)該返回long[] 或者 List類型的值。
有時插入數(shù)據(jù)和更新數(shù)據(jù)會產(chǎn)生沖突,所以就有了沖突之后要怎么解決,SQLite對于事務(wù)沖突定義了5個方案
OnConflictStrategy

  • REPLACE:見名知意,替換,違反的記錄被刪除,以新記錄代替之
  • ignore: 違反的記錄保持原貌,其它記錄繼續(xù)執(zhí)行
  • fail: 終止命令,違反之前執(zhí)行的操作得到保存
  • abort 終止命令,恢復(fù)違反之前執(zhí)行的修改
  • rollback 終止命令和事務(wù),回滾整個事務(wù)

Update

@Update注解在數(shù)據(jù)庫中用于修改一組實(shí)體的字段。它使用每個實(shí)體的主鍵來匹配查詢。

@Dao
public interface MyDao {
    @Update
    public void updateUsers(User... users);
}

Delete

用于從數(shù)據(jù)庫中刪除給定參數(shù)的一系列實(shí)體,它使用主鍵匹配數(shù)據(jù)庫中相應(yīng)的行

@Dao
public interface MyDao {
    @Delete
    public void deleteUsers(User... users);
}

Query

@Query是DAO類中使用的主要注解。它允許您在數(shù)據(jù)庫上執(zhí)行讀/寫操作。每個@Query方法在編譯時被驗(yàn)證,因此,如果存在查詢問題,則會發(fā)生編譯錯誤而不是運(yùn)行時故障。

Room還驗(yàn)證查詢的返回值,這樣如果返回對象中字段的名稱與查詢響應(yīng)中的相應(yīng)列名不匹配,則Room將以以下兩種方式之一提醒您:

  • 如果只有一些字段名匹配,則發(fā)出警告。
  • 如果沒有字段名匹配,則會出錯。

1、簡單查詢

@Dao
public interface MyDao {
    @Query("SELECT * FROM user")
    public User[] loadAllUsers();
}

2、將參數(shù)傳遞到查詢中

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge")
    public User[] loadAllUsersOlderThan(int minAge);
}

3、傳遞參數(shù)集合
有些查詢可能要求您傳遞一個可變數(shù)量的參數(shù),其中參數(shù)的確切數(shù)目直到運(yùn)行時才知道。例如,您可能希望從區(qū)域的子集檢索有關(guān)所有用戶的信息

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}

4、Observable查詢
當(dāng)執(zhí)行查詢時,您經(jīng)常希望應(yīng)用程序的UI在數(shù)據(jù)更改時自動更新。要實(shí)現(xiàn)這一點(diǎn),請?jiān)诓樵兎椒枋鲋惺褂妙愋?code>LiveData的返回值。當(dāng)數(shù)據(jù)庫被更新時,Room生成所有必要的代碼來更新LiveData。

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}

5、RXJava的響應(yīng)式查詢
Room還可以從您定義的查詢中返回RXJava2 PublisherFlowable對象。若要使用此功能,請將androidx.room:room-rxjava2庫添加到gradle的依賴關(guān)系中。

@Dao
public interface MyDao {
    @Query("SELECT * from user where id = :id LIMIT 1")
    public Flowable<User> loadUserById(int id);

    // Emits the number of users added to the database.
    @Insert
    public Maybe<Integer> insertLargeNumberOfUsers(List<User> users);

    // Makes sure that the operation finishes successfully.
    @Insert
    public Completable insertLargeNumberOfUsers(User... users);

    /* Emits the number of users removed from the database. Always emits at
       least one user. */
    @Delete
    public Single<Integer> deleteUsers(List<User> users);
}

6、直接Cursor訪問

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    public Cursor loadRawUsersOlderThan(int minAge);
}

四、 數(shù)據(jù)庫升級

在應(yīng)用程序中添加和更改特性時,你需要修改實(shí)體類以反映這些更改。當(dāng)用戶更新應(yīng)用程序到最新版本時,不希望它們丟失所有現(xiàn)有數(shù)據(jù),尤其是如果無法從遠(yuǎn)程服務(wù)器恢復(fù)數(shù)據(jù)。

如果您不提供必要的遷移,則Room會重新構(gòu)建數(shù)據(jù)庫,這意味著您將丟失數(shù)據(jù)庫中的所有數(shù)據(jù)

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
        .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
                + "`name` TEXT, PRIMARY KEY(`id`))");
    }
};

static final Migration MIGRATION_2_3 = new Migration(2, 3) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE Book "
                + " ADD COLUMN pub_year INTEGER");
    }
};

五、類型轉(zhuǎn)換器

TypeConverter,它將自定義類轉(zhuǎn)換為Room可以保留的已知類型。例如,如果想要持久化實(shí)例Date,可以編寫以下內(nèi)容 TypeConverter 來在數(shù)據(jù)庫中存儲等效的Unix時間戳

public class Converters {

    @TypeConverter
    public static Date fromTimestamp(Long value) {
        return value == null ? null : new Date(value);
    }

    @TypeConverter
    public static Long dateToTimestamp(Date date) {
        return date == null ? null : date.getTime();
    }

}

上面的例子中定義了兩個函數(shù),一個轉(zhuǎn)換Date對象到Long對象,另一個執(zhí)行逆變換,從Long到Date。由于Room是知道如何持久化Long對象的,因此它可以使用此轉(zhuǎn)換器來持久保存Date類型的值。接下來,添加 @TypeConverters 注釋到AppDatabase類,這樣Room就可以在AppDatabase中的實(shí)體和Dao上使用上面的定義的類型轉(zhuǎn)換器。還可以限制 @TypeConverters 到不同的范圍,包括單個實(shí)體,DAO和DAO方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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