Android Room 使用案例
本内容主要介绍 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()); } }
六、效果: