前言
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、developer.android.com/training/da…
2、codelabs.developers.google.com/codelabs/an…