本內容主要介紹 Android 中使用 Room 保存數據到本地數據庫的方法。
以下是 Android Room 的官方介紹文檔:
Room Persistence Library
(Room 庫的簡單介紹) https://developer.android.com/topic/libraries/architecture/room
Save data in a local database using Room
(Room 的使用指南) https://developer.android.com/training/data-storage/room/
Android Room with a View - Java
(Room 的使用實例) https://codelabs.developers.google.com/codelabs/android-room-with-a-view/#0
一、簡介
Room 是一個對象關系映射(ORM)庫。可以很容易將 SQLite 表數據轉換為 Java 對象。Room 在編譯時檢查 SQLite 語句。
Room 為 SQLite 提供一個抽象層,以便在充分利用 SQLite 的同時,可以流暢地進行數據庫訪問。
1.1 添加依賴
在build.gradle 添加依賴
dependencies { def room_version = "2.2.0-beta01" implementation "androidx.room:room-runtime:$room_version" annotationProcessor "androidx.room:room-compiler:$room_version" }
然后再在build.gradle 添加如下,不然build不過:
android { ... defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [ "room.schemaLocation":"$projectDir/schemas".toString(), "room.incremental":"true", "room.expandProjection":"true"] } } } }
1.2 Room 組件
Room 有 3 個主要的組件:
Database:包含數據庫持有者,並作為與 App 持久關聯數據的底層連接的主要訪問點。
用 @Database 注解的類應滿足以下條件:
是一個繼承至 RoomDatabase 的抽象類。
在注解中包含與數據庫相關聯的實體列表。
包含一個具有 0 個參數的抽象方法,並返回用 @Dao 注解的類。
在運行時,您可以通過調用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 獲取 Database 實例。
Entity:表示數據庫內的表(Table)。
DAO:包含用於訪問數據庫的方法。
1.3 Room 各組件間關系
Room 的大致使用方法如下:
App 通過 Room 的 Database 獲取與數據庫相關的數據庫訪問對象(DAO)。
然后,App 使用 DAO 從數據庫中獲取 Entity,並且將 Entity 的變化保存到數據庫中。
最后,APP 使用 Entity 獲取和設置數據庫中表的數據。
Room 中各組件之間的關系如圖-1 所示:
二、Entity(實體)
在使用 Room 持久化庫(Room persistence library)時,需要將相關字段集定義為 Entity。對於每一個 Entity,在與其相關的 Database 對象中會創建一個表(Table)。
必須通過 Database 類的 entities 數組引用這個 Entity 類。
下面的代碼片段展示如何定義 Entity:
package com.example.jetpackdemo; import androidx.room.Entity; import androidx.room.PrimaryKey; @Entity public class Word { @PrimaryKey(autoGenerate = true) private Integer id; private String word; private String chinese; public Word(String word, String chinese) { this.word = word; this.chinese = chinese; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getWord() { return word; } public void setWord(String word) { this.word = word; } public String getChinese() { return chinese; } public void setChinese(String chinese) { this.chinese = chinese; } @Override public String toString() { return "Word{" + "id=" + id + ", word='" + word + '\'' + ", chinese='" + chinese + '\'' + '}'; } }
法。在提供 getter 和 setter 方法時,需要遵守 Room 中的 JavaBeans 協議。
2.1 設置 Table 名稱
Room 默認使用類名作為數據庫的 Table 名稱。可以通過 @Entity 的 tableName 屬性設置 Table 的名稱。
(注意:在 SQLite 中,Table 名稱是不區分大小寫的。)
@Entity(tableName = "word") public class Word { }
三、DAO(Data access object)
在 Room 持久化庫中,使用數據訪問對象(data access objects, DAOs)訪問 App 的數據。Dao 對象集合是 Room 的主要組件,因為每個 DAO 提供訪問 App 的數據庫的抽象方法。
通過使用 DAO 訪問數據庫,而不是通過查詢構造器或直接查詢,可以分離數據庫架構的不同組件。此外,在測試應用時,DAOs 可以輕松模擬數據庫訪問。
DAO 可以是接口(interface),也可以是抽象類(abstract class)。如果是一個抽象類,可以有一個構造函數,其只接收一個 RoomDatabase 參數。在編譯時,Room 為每個 DAO 創建具體實現。
package com.example.jetpackdemo; import androidx.room.Dao; import androidx.room.Insert; import androidx.room.Query; import androidx.room.Update; import java.util.List; @Dao public interface WordDao { @Insert void insert(Word...words); @Update void update(Word... words); @Query("delete from word") void deleteAll(); @Query("select * from word") List<Word> findAll(); }
注意:除非在構造器上調用 allowMainThreadQueries(),否則 Room 不支持在主線程上進行數據庫訪問,因為它可能會長時間鎖定 UI。不過異步查詢(返回 LiveData 或 Flowable 實例的查詢)不受此規則約束,因為它們在需要時會在后台線程進行異步查詢。
3.4.2 帶參數的查詢
大多數情況下,需要將參數傳遞到查詢中以執行篩選操作,例如僅需要顯示大於某一年齡的 User。這時,我們可以使用方法參數。
@Dao public interface MyDao { @Query("SELECT * FROM user WHERE age > :minAge") public User[] loadAllUsersOlderThan(int minAge); }
在編譯時,Room 使用 minAge
方法參數匹配 :minAge
綁定參數。如果存在匹配錯誤,將出現編譯錯誤。
還可以在查詢中傳遞多個參數或者多次引用它們。
@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); }
四、Database
在 Room 持久化庫中,通過 @Database
類訪問數據庫。
4.1 定義 Database
下面的代碼片段展示如何定義 Database:
package com.example.jetpackdemo; import androidx.room.Database; import androidx.room.RoomDatabase; @Database(entities = {Word.class},version = 1, exportSchema = false) public abstract class WordDatabase extends RoomDatabase { public abstract WordDao getWordDao(); }
五、測試案例
創建一個RoomActivity
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".RoomActivity"> <ScrollView android:id="@+id/scrollView2" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toTopOf="@+id/guideline2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" /> </ScrollView> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.6" /> <Button android:id="@+id/button5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:text="@string/btn5" app:layout_constraintBottom_toTopOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@+id/guideline4" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline3" /> <Button android:id="@+id/button6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:text="@string/btn6" app:layout_constraintBottom_toTopOf="@+id/guideline3" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline4" app:layout_constraintTop_toTopOf="@+id/guideline3" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.75" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.5" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.875513" /> <Button android:id="@+id/button7" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/btn8" app:layout_constraintBottom_toTopOf="@+id/guideline5" app:layout_constraintEnd_toStartOf="@+id/guideline4" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline5" /> <Button android:id="@+id/button8" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:text="@string/btn9" app:layout_constraintBottom_toTopOf="@+id/guideline5" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline4" app:layout_constraintTop_toTopOf="@+id/guideline5" /> <TextView android:id="@+id/textView" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:text="TextView" app:layout_constraintBottom_toBottomOf="@+id/scrollView2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
package com.example.jetpackdemo; import androidx.appcompat.app.AppCompatActivity; import androidx.room.Insert; import androidx.room.Room; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.util.List; public class RoomActivity extends AppCompatActivity { private WordDatabase wordDatabase; private WordDao wordDao; private Button btn5,btn6,btn7,btn8; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_room); wordDatabase = Room.databaseBuilder(this,WordDatabase.class,"word_database").allowMainThreadQueries().build(); wordDao = wordDatabase.getWordDao(); btn5 = findViewById(R.id.button5); btn6 = findViewById(R.id.button6); btn7 = findViewById(R.id.button7); btn8 = findViewById(R.id.button8); tv = findViewById(R.id.textView); btn5.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Word word1 = new Word("hello","你好"); Word word2 = new Word("world","世界"); wordDao.insert(word1,word2); show(); } }); btn6.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { show(); } }); btn7.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Word word = new Word("hi","你好"); word.setId(5); wordDao.update(word); show(); } }); btn8.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { wordDao.deleteAll(); show(); } }); } private void show() { List<Word> list = wordDao.findAll(); StringBuilder sb =new StringBuilder(); for (Word w : list){ sb.append(w.toString()+"\n"); } tv.setText(sb.toString()); } }
六、效果: