Room Database完全使用手冊

前言

Android數據持久層直接使用SQLite很麻煩,Google官方推出了Room, Google對Room的定義:
The Room persistence library provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
Room是SQLite之上的一個抽象層,通過Room層來擺脫,繁瑣的數據庫操作,包括創建、更新數據庫、表以及進行增刪查改等。

Room數據庫結構

如下圖,Room數據庫包含三個對象:
1、Entity : 對應數據庫中的表,可以使用Entity註解將一個類變成數據庫中的一張表結構。
2、DAO : 全稱Database Access Object,定義了對數據庫中數據的讀寫等操作,DAO中可以使用SQL語句來操作數據庫。
3、RoomDatabase : 數據庫持有類,用於創建數據庫或者連接到數據庫。內部包含DAO和Entity。
在這裏插入圖片描述
上面總體說了Room數據庫的三個對象,下面分別介紹三個對象的作用和使用方法。

Entity

Room Database中的Entity表示一張數據表結構,一個Entity實例就是表中的一行,如定義一個Person類的Entity。

@Entity
public class Person{
    @PrimaryKey
    public int id;
    public String firstName;
    public String lastName;
}

當有兩個Person對象在表中時,表中的數據如下:
在這裏插入圖片描述
其中Person和Entity及數據庫的關係如下所示:
在這裏插入圖片描述
重點:
1、一個Entity對象代表數據表中的一行,一個Entity類代表一張數據表。
2、Entity中的成員變量都是數據表中的列。
3、一個Java類定義成Entity只要加上Entity註解就可以了。

Entity的使用

1、定義一個Entity

@Entity(tableName = "person_table")
public class Person{
    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;
}

如果想給Entity命名可以在@Entity註解的後面加上(tableName = "person_table"),如果不加會使用默認命名,上面的id,firstName, lastName都定義成了public類型,是爲了數據庫能夠有操作這些數據的權限,也可以選擇將這些都設置成private類型,然後通過setter和getter來操作。
2、定義主鍵
每一個Entity至少定義一個主鍵(primary key),哪怕Entity中只有一個變量也要將這個變量定義爲主鍵,在Room數據庫中使用註解 @PrimaryKey 來定義主鍵,@PrimaryKey 的使用方式有兩種一種是在類變量前面加,如果主鍵比較複雜可以加在@Entity註解的後面。

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

3、改變屬性列名
和改變表名稱tableName一樣,可以改變表中的列名稱,使用 @ColumnInfo來改變列的名稱。如果不改的話默認使用變量名的小寫形式。

@Entity(tableName = "person_table")
public class Person {
    @PrimaryKey
    public int id;

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

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

4、使某些變量不生成數據表中的字段
由上面可知當一個類前加了Entity註解後類中的所有成員變量都會生成表中的屬性列,如果我們不希望某個變量生成表中的屬性列,可以使用註解 @Ignore。

@Entity
public class Person {
    @PrimaryKey
    public int id;
    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

如果有類繼承上面的Person類,並且不想picture生成數據表中的列,那麼可以ignoredColumns

@Entity(ignoredColumns = "picture")
public class RemotePerson extends Person {
    @PrimaryKey
    public int id;

    public boolean hasVpn;
}

5、兩個數據表關聯
SQLite是關係數據庫,可以明確兩個對象間的關係,大多數的關係數據庫都可以指定表之間的關聯,但是Room數據庫不能夠之間指定兩個表之間的關係,但是可以通過@ForeignKey註解來進行表的關聯。

@Entity(foreignKeys = @ForeignKey(entity = Person.class,
                                  parentColumns = "id",
                                  childColumns = "user_id"))
public class Book {
    @PrimaryKey
    public int bookId;

    public String title;

    @ColumnInfo(name = "user_id")
    public int userId;
}

6、嵌套Entity
如果定義的Entity類裏面有個對象,並且希望定義的Entity中的表列字段包含Entity類對象中的變量,可以在Entity類對象中加@Embedded標註。

public class Address {
    public String street;
    public String state;
    public String city;

    @ColumnInfo(name = "post_code")
    public int postCode;
}

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

    public String firstName;

    @Embedded
    public Address address;
}

這樣Person表中的列字段有id, firstName, street, state, city,和 post_code.

DAO

在DAO(data access object)中,可以使用SQL語句進行對數據庫的操作並且將這些語句與Java中方法關聯調用,編譯器會檢查SQL語句並且通過註解生成對應的查詢語句,例如@Insert。
注意:
1、DAO必現是抽象類或者接口
2、所有的查詢語句必須在單獨的線程裏面執行。
在這裏插入圖片描述

DAO的使用

可以使用sql中的語句來實現DAO的操作。
1、插入數據
當在方法前使用@Insert註解時,Room數據庫會將方法中的所有參數都插入到數據表中。

@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 方法只插入一個參數時,插入會返回long數據代表插入的對象在數據表中的行號,當插入一個數組或者集合形式的數據時,會返回long數組或者集合。
2、更新數據
@Update標註的方法會查找表中與所給數據主鍵一樣的數據進行更新。

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

3、查詢數據
@Query 標註的查詢方法是使用最多的方法,每個查詢方法在編譯期間就會被驗證是否正確,如果錯誤就會報錯,不會在運行時再提示失敗。在

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

如果存在查詢語法錯誤,或者數據庫中不存在user表,Room數據庫會在編譯時提示錯誤信息。
也可以使用SQL語句實現複雜的查詢

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
    public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

    @Query("SELECT * FROM user WHERE first_name LIKE :search " +
           "OR last_name LIKE :search")
    public List<User> findUserWithName(String search);
}

4、查詢列表中的數據並返回子列表
有時候並不需要將查詢數據的所有字段都返回,大部分時候只需要返回查詢數據的一個子域。下面實現返回查詢的一個子域。

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

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

然後查詢返回子域中的字段

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user")
    public List<NameTuple> loadFullName();
}

RoomDatabase

上面已經介紹了DAO和Entity,Entity定義表,DAO定義對錶中數據進行操作,RoomDatabase包含了DAO,並且提供創建和連接數據庫的方法。
在這裏插入圖片描述

創建Room database

創建Room database包括三個步驟:
1、創建繼承RoomDatabase的抽象類。
2、在繼承的類前使用註解@Database。
3、申明數據庫結構的Entity,並且設置數據庫的版本號。

@Database(entities = {Word.class}, version = 1)//申明Entity和數據庫的版本號
public abstract class WordRoomDatabase 
                      extends RoomDatabase { 
   public abstract WordDao wordDao();//創建DAO的抽象類

   private static WordRoomDatabase INSTANCE;//創建單例
   
   static WordRoomDatabase getDatabase(final Context context) {
 		 if (INSTANCE == null) {
    			synchronized (WordRoomDatabase.class) {
      			if (INSTANCE == null)    {
       				    INSTANCE = Room.databaseBuilder(
						context.getApplicationContext(),
						WordRoomDatabase.class, "word_database")
						.addCallback(sOnOpenCallback)
						.fallbackToDestructiveMigration()
						.build();                
      			}
      		}
      }
  return INSTANCE;}

private static RoomDatabase.Callback sOnOpenCallback =
  new RoomDatabase.Callback(){
	@Override
	public void onOpen (@NonNull SupportSQLiteDatabase db){
		super.onOpen(db);
		initializeData();
	}};

}

注意:
1、編譯時會檢查SQL語句是否正確
2、不要在主線程中進行數據庫操作
3、RoomDatabase最好使用單例模式
最後上一張Android在使用Room數據庫時和Room數據庫交互的圖
在這裏插入圖片描述
application從RoomDatabase中獲取DAO實例,並且通過DAO中定義的方法來操作數據庫中Entity。獲取設置entity的數據。

Room database Migration

當升級Android應用的時候,有時需要更改數據庫中數據的結構,要用戶升級應用的時候保持原有的數據不變,使用數據庫移植Migration是必須的。在Room數據庫中Room persistence library庫提供了升級數據庫時保存用戶數據的方法。
使用方法
定義Migration對象,每個對象包含數據庫的開始版本號和結束版本號。

    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            //這裏面實現更改數據表結構的方法
        }
    };

如上實現一個Migration對象,重寫migrate方法,在migrate方法中實現升級時要進行的操作。

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

如果我們數據庫要從版本1,升級到版本庫,就要定義MIGRATION_1_2 ,MIGRATION_2_3 ,MIGRATION_3_4 ,MIGRATION_1_4 並且改寫單例方法。

    public static UsersDatabase getInstance(Context context) {
        synchronized (sLock) {
            if (INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                        UsersDatabase.class, "Sample.db")
                        .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_1_4)
                        .build();
            }
            return INSTANCE;
        }
    }

當然不一定非要定義MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_1_4這麼多,也可以一次從MIGRATION_1_4,跳過中間的MIGRATION,但是如果不提供足夠的MIGRATION,從當前版本變到最新版本,Room數據庫會清楚原來的數據重寫創建。

參考文獻

1、https://developer.android.com/training/data-storage/room/#java
2、https://codelabs.developers.google.com/codelabs/android-room-with-a-view/#4

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