使用 Room 将数据保存到本地数据库学习日志+demo


主要参考来源:

Room理论学习

SQLite是Android系统内置的轻量级关系型数据库,而Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。

概括来说,Room是对SQLite的封装。下面主要以介绍demo来展示学习成果。

前期准备

在Android Studio里新建一个项目MyRoomApplication,并准备好模拟器或真机(这里使用雷电模拟器)

导入库

为正常使用Room,需要在项目的Gradle Scripts下的build.gradle文件里添加依赖:

dependencies {
    def room_version = "1.1.1"

    // Room components
    implementation "android.arch.persistence.room:runtime:$room_version"
    // For Kotlin use kapt instead of annotationProcessor
    annotationProcessor "android.arch.persistence.room:compiler:$room_version"
    //ViewModelProviders依赖
    api "android.arch.lifecycle:extensions:1.1.1"
    ......
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
}

其中,前两个implementation和annotationProcessor的部分是使用Room必须添加的,如果涉及到Room作RXJava的响应式查询等内容还需添加额外的依赖,这里不是本次学习的部分,就不展开了。api "android.arch.lifecycle:extensions:1.1.1"是ViewModelProviders依赖,如果不使用ViewModelProviders该依赖可以不添加。最后一个依赖是RecyclerView的依赖,如果界面不用到列表项也可以不添加。
Room的其他可选添加依赖:

// optional - RxJava support for Room
    implementation "android.arch.persistence.room:rxjava2:$room_version"

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "android.arch.persistence.room:guava:$room_version"

    // Test helpers
    testImplementation "android.arch.persistence.room:testing:$room_version"

Room三大组件之一:Entity

Entity:表示数据库内的表。

由于Room采取的是对象关系映射的数据库框架,即每一张表都可以有一个对应的JavaBean类。
因此我们首先定义一个Student类,并在代码public class Student {前一行加上注释 @Entity(),这就用Room形成了我们要创建的一个表。一般类名会默认成为表名,如果想更改表名可以在@Entity()注释里指定tableName = "自定义的表名"
注意,在该类里的成员变量就是数据表的属性
除此之外,该类里需要提供setter和getter供Room框架调用,以后在别的活动里就可以通过setXXX和getXXX的方法为数据表赋值了。
使用Room定义实体数据,即可以用注解的方式为成员变量(即表的属性、字段、列)设置主键、设置唯一性等。在该实例中只设置了主键,并通过在注解里定义属性autoGenerate = true使Room给主键自动分配值,值将从1开始增加。

Student.java

package com.example.myroomapplication;

import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity(tableName = "student")//Room 使用字段(Filed)名称作为在数据库中的默认列名。可以通过给 Filed 添加 @ColumnInfo 注解设置列名。
public class Student {
    // 设置主键
    @PrimaryKey(autoGenerate = true)//如果需要 Room 自动分配 IDs 给 Entity,可以设置 @PrimaryKey 的 autoGenerate 属性。
    private int Sno;//学号

    private String Sname;//姓名
    private String Ssex;//性别
    private int Sage;//年龄
    private String Sdept;//系别

    // 提供setter和getter供Room框架调用


    public int getSno() {
        return Sno;
    }

    public void setSno(int sno) {
        Sno = sno;
    }

    public String getSname() {
        return Sname;
    }

    public void setSname(String sname) {
        Sname = sname;
    }

    public String getSsex() {
        return Ssex;
    }

    public void setSsex(String ssex) {
        Ssex = ssex;
    }

    public int getSage() {
        return Sage;
    }

    public void setSage(int sage) {
        Sage = sage;
    }

    public String getSdept() {
        return Sdept;
    }

    public void setSdept(String sdept) {
        Sdept = sdept;
    }

}

最后的结果是定义了一个名为student的表,其属性有Sno、Sname、Ssex、Sage、Sdept,其中Sno是主键且自增。

Room三大组件之一:DAO

DAO:包含用于访问数据库的方法。

前面用Entity创建好表之后,就可以为其定义一些增删改查的操作了。这将在DAO里实现。
首先我们定义一个接口StudentDao,并在public interface StudentDao {前添加好注释@Dao。这样这个接口就可以用来实现访问数据库方法了。
在这个接口里定义的方法中

  • 如果是用于插入的方法,需要在方法前添加注释@Insert
    被它注释的方法可以只有一个被Entity注释的类的变量作为参数,表示插入一条数据,也可以有多个参数,表示插入多条数据,当然将多个变量放在一个链表里作为一个参数也是可以的。方法返回值为void即可。
  • 如果是用于更新的方法,需要在方法前添加注释@Update
    被它注释的方法可以有一个或多个被Entity注释的类的变量作为参数,表示将这些变量的相关信息更新。可以将参数写为Student... students形式,这样就可以根据需要传任意数量的变量了。方法返回值为void即可,也可以使此方法返回int型值,以指示数据库中更新的行数。
  • 如果是用于删除的方法,需要在方法前添加注释@Delete
    关于参数和方法返回值的注意事项参照前两条。
  • 如果是用于查询的方法,需要在方法前添加注释@Query(查询的SQL语句),比如加了@Query("SELECT * FROM student")注释的方法将被视为查询表中的所有字段。
    注意,也可以用@Query(SQL语句)注释实现删除等功能,比如被加了@Query("DELETE FROM student")注释的方法也能实现删除全部数据。注意在查询时如果出现了问题,可能会导致警告和直接闪退的情况。
    用于查询的方法如果是查询所有字段,方法可以不用参数。如果要根据某一属性查数据,则需要将该属性类型变量作为参数。方法的返回值可以只是一个被Entity注释的类的变量,表示将返回查出的一条数据;返回值也可以是Student[]这样的数组和List< Student >这样的链表,这样就可以返回多条数据。
    在本实例中查询全部学生用到的返回值是LiveData<List< Student >>,这是可观察的查询,当数据库被更新时,Room生成所有必要的代码来更新LiveData,即能根据查询结果变化实时更新某些内容,这将在后面介绍。

其中@Insert@Update注释里可以指定属性onConflict = OnConflictStrategy.REPLACE,顾名思义,发生冲突时按一定策略替换。

注意:主键要避免为空,如果是字符串做主键,记得加@NonNull

比如:

@NonNull
@PrimaryKey
private String name;

不然会报错 :You must annotate primary keys with @NonNull. "name" is nullable. SQLite considers this a bug and Room does not allow it. See SQLite docs for details:
StudentDao.java

package com.example.myroomapplication;

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;

import java.util.List;

@Dao
public interface StudentDao {
    @Insert
    public void insertOneStudent(Student student); //插入一条学生信息

    @Insert
    public void insertStudents(Student... students); //插入多条学生信息

    @Update(onConflict = OnConflictStrategy.REPLACE)
    public int updateStudents(Student... students); //更新学生信息,当有冲突时则进行替代

    @Delete
    public void deleteStudent(Student student);//删除一个学生

    @Query("DELETE FROM  student")
    public void deleteAllStudent();//删除所有学生

    @Query("SELECT * FROM  student")
    public LiveData<List<Student>> getAllStudents(); //查询所有学生数据,LiveData令App 的 UI 在数据发生变化时自动更新 UI

    @Query("SELECT * FROM student WHERE Sname = :Sname")
    public Student loadStudentByName(String Sname); //根据名字加载学生

}

在DAO里最终定义了7个方法,使用它们将可以实现一些增删改查的操作。

Room三大组件之一:Database

Database:包含数据库持有者,并充当与应用程序持久化的、关系型的数据的底层连接的主要访问点。

@Database注解的类应满足以下条件:

  • 是一个继承RoomDatabase的抽象类。
  • 在注释中包含与数据库相关联的实体列表。
  • 包含一个具有0个参数的抽象方法,并返回用@Dao注释的类。

在运行时,您可以通过调用Room.databaseBuilder()或Room.inMemoryDatabaseBuilder()获取数据库实例

在定义好表和它相关的操作之后,就需要定义数据库来使用它们了。首先新建一个抽象类StudentDatabase,因为该类需要有抽象方法来返回之前定义的被注释为@Dao的类。注意要让该类继承RoomDatabase,和之前两个操作类似地,在public abstract class StudentDatabase extends RoomDatabase {前加上注释@Database(entities = {Student.class}, version = 1, exportSchema = false)。其中,

  • 第一个属性代表要引用的表,这里就是我们刚定义的Student类,这里可以引用多个表。
  • 第二个属性是数据库的版本号,当前版本为1,在进行数据库升级时这里会被改动。
  • 第三个属性是针对项目可以运行但报错的情况,如下:
Error:(22, 17) 警告: Schema export directory is not provided to the annotation processor so we cannot export the schema. 
You can either provide ‘`room.schemaLocation`’ annotation processor argument OR set exportSchema to false.

增加了该属性后就不会出现上述错误了。

接下来是对类体进行编写了。正如前面官方指南所说,需要定义获取Dao的方法,该方法没有方法体,是抽象方法。 这样就可以在别的类或活动里定义一个StudentDatabase类型的变量,再由该变量调用这个抽象方法来获取一个StudentDao类型的变量。有了该变量就可以调用一系列的DAO方法实现对数据表中记录的增删改查了。
一个简单的Database类可以是下面的形式:

AppDatabase.java

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

定义完方法,接下来就是实现数据库的建立了。这需要使用下面的代码:

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

Room.databaseBuilder方法的参数分别指定了上下文要加载的Database类以及数据库的名称。最后再调用build()方法,最终就实现了数据库的创建并且返回了类型自定义的Database类的一个变量。有了该变量,就可以调用其获取DAO的抽象方法了。

类:@Entity 接口:@Dao 抽象类:@Database 定义了表的属性和各 种getter和setter操作. 定义了表的各种增删 改查方法. 定义了获取DAO的 抽象方法,可以在该 类里或其他地方通过 Room.databaseBuilder (...).build()创建数据库 并与该类绑定. 操作针对相应表,定义的方法参数或返回值类型需要该类 数据库指定表,通过添加注释里的属性实现 添加方法,实现用数据库变量调用表的CRUD方法 类:@Entity 接口:@Dao 抽象类:@Database
									三者关系

下面的代码片段示例了包含一个entity和一个DAO的数据库配置:

User.java

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

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

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

// Getters and setters are ignored for brevity,
// but they're required for Room to work. }

UserDao.java

@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.java

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

在创建上面的文件之后,使用以下代码获得创建数据库的实例:

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

在本Demo中定义了StudentDatabase,实现了建立studentDb数据库,并且数据库里也建好了一张表student。表的属性就是Student类里定义的成员变量。下面的代码涉及到一些没有解释的内容,这将在下面介绍。
StudentDatabase.java

package com.example.myroomapplication;

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

// 声明为Database,指定表格、版本
@Database(entities = {Student.class}, version = 1, exportSchema = false)//exportSchema = false防止报错
public abstract class StudentDatabase extends RoomDatabase {
    private static StudentDatabase sInstance;

    // 构造函数必须是public,否则报错
    public StudentDatabase() {
    }

    /*static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
//                创建新的数据表
            database.execSQL("CREATE TABLE IF NOT EXISTS `Course` (`Cno` TEXT PRIMARY KEY NOT NULL, `Cname` TEXT,`Cpno` TEXT,`Ccredit` INTEGER)");
        }
    };
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            //为旧表添加新的字段
            database.execSQL("ALTER TABLE Course " + "ADD COLUMN Cavg INTEGER");
        }
    };
    static final Migration MIGRATION_3_4 = new Migration(3, 4) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("DROP TABLE IF EXISTS Course");
        }
    };*/
    // 单实例模式,节省资源
    public static StudentDatabase getsInstance(Context context) {
        if (sInstance == null) {
            synchronized (StudentDatabase.class) {
                if (sInstance == null) {
                    sInstance = Room.databaseBuilder(context.getApplicationContext(),
                            StudentDatabase.class, "studentDb").fallbackToDestructiveMigration().addMigrations().allowMainThreadQueries().build();allowMainThreadQueries()可以在主线程操作,获取数据库实例
                }
            }
        }
        return sInstance;
    }


    public abstract StudentDao studentDao();// 定义获取Dao的方法
}

注意:阅读上面的代码,你会发现创建数据库的语句

sInstance = Room.databaseBuilder(context.getApplicationContext(),
                            StudentDatabase.class, "studentDb").fallbackToDestructiveMigration().addMigrations().allowMainThreadQueries().build();

中多调用了fallbackToDestructiveMigration()方法addMigrations()方法allowMainThreadQueries()方法。其中第一个和第二个方法将在升级数据库部分提到,第三个方法是因为数据库在后台运行,

默认不许在主线程中连接数据库,不加可能会报下面的错

因此本实例为在主线程中使用数据库而加上此句。在你应用时你可以考虑用多线程的知识完成数据库的相关操作。

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

数据库

到此,Room的基本知识大部分已经列举完了。现在已经完成建立一个数据库和数据库里的表的操作了。由于这些信息在模拟机和真机上不便展示,首先在这里介绍一下查看数据库和数据库表的方法。
Entity(实例中为Student)、DAO(实例中为StudentDao)、Database(实例中为StudentDatabase)直接使用上述代码即可,在主活动里代码:
MainActivity.java(仅作创建数据库测试,以后还会在里面增加内容)

package com.example.myroomapplication;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        StudentDatabase database = StudentDatabase.getsInstance(this);//由于使用StudentDatabase单例模式,因此需要这样写,如果不用单例模式可直接将类似AppDatabase db = Room.databaseBuilder(getApplicationContext(),AppDatabase.class, "database-name").build();的代码写进主活动
        StudentDao studentDao = database.studentDao();
        Student student = new Student();
        student.setSname("李勇");
        student.setSsex("男");
        student.setSage(20);
        studentDao.insertOneStudent(student);//插入一个数据,令数据库被使用起来
    }
}

添加完如上代码,就可以启动程序建立数据库了。

  1. 打开雷电模拟器,启动程序,找到AS左下角的方形标志(有的AS版本可能是一个电脑),选中Device File Explorer,我们可以看到若干文件夹。我们的APP的相关存储都会在相应的文件夹下。而我们新建的数据库在 /data/data/< packageName>/databases 目录下,该项目名叫myroomapplication,包含类名的全称就是com.example.myroomapplication。打开 /data/data/com.example.myroomapplication/databases 目录,我们看到了刚建好的数据库:
    Device File Explorer
  2. 但是用上述方法只能知道已经建好了一个数据库,怎么才能了解它的详细信息呢?可以使用可视化管理工具查看,也可以使用SQLite3 查看数据库,这里介绍后者(注意,一定要在运行完程序之后,模拟机打开的状态下):

在Android sdk的platform-tools目录下有一个adb.exe,这是一个调试工具,使
用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。
(1)打开cmd,在控制台窗口通过“cd C:\Users\“用户名”\AppData\Local\Android\Sdk\platform-tools”跳转到adb.exe所在目录(可以通过设置环境变量,这样就不用跳转到相应目录了。另外注意,adb.exe的所在位置取决于你Android Sdk的安装目录,如果你的sdk是装在默认路径,就可以输入上面的语句)
(2)在命令行中输入“adb shell”,进入adb调试的shell命令行状态。在“#”提示符下(表示超级管理员权限,如果是"$"需要通过su从普通管理员跳转到超级管理员权限)输入“cd /data/data/< packageName >/databases”进入数据库所在目录,(其中< packageName >是包名,如com.example.myroomapplication),进入到项目存放数据库文件的目录中。
(3)再输入“sqlite3 < DatabaseName >”(其中,DatabaseName是数据库名称,例如,sqlite3 studentDb),进入SQL命令状态。这时已经进入到studentDb数据库里了。
(4)在“sqlite>”提示符下输入命令“.table”,显示数据库studentDb中的全部数据表。输入命令“.schema“,可以看到数据库studentDb中的数据表的详细信息
(5)在“sqlite>”提示符下输入SQL查询命令“select * from student;”(其中,student是数据库studentDb中的数据表),则显示数据表student中的 全部记录。
(6)输入“.quit”则退出SQL命令状态,再输入“exit”则退出shell命令行。

在第五步中,由于数据表还没有数据,因此显示为空。数据的添加将在后面介绍。
在.table中可以发现除了我们自己建立的表,还有两个别的表。这是Room数据库自带的两个表,我们可以不用管它们。
cmd截图
这样查看数据库的内容就介绍完了。

建立数据库

建立数据库的内容已经在Room三大组件之一:Database介绍完了。做好三大组件的准备工作后,加入AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build();语句就可以最终实现建立数据库了。这里主要补充一个细节:

在实例化AppDatabase对象时,应遵循单例设计模式,因为每个Roomdatabase实例都相当消耗性能,并且您很少需要访问多个实例。

在前面的StudentDatabase的代码中采用的就是单例模式。并且在单例模式的方法里实现数据库的建立并返回StudentDatabase类型的实例。当需要建立数据库并引用它时,在活动里添加StudentDatabase database = StudentDatabase.getsInstance(this);语句即可。再使用StudentDao studentDao = database.studentDao();语句就可以获得DAO类型的变量对数据库里信息进行增删改查了。具体实现可以参考下面关于记录的内容。

升级数据库

升级数据库,包含对数据库中的数据库表进行改变(如增加一个表,改变表中一个字段,删除一个表)。本博客将会通过用Room规则改变数据表(不用数据库迁移)用SQL语句改变数据表(用到数据库迁移) 这两种方法介绍升级数据库。本标题内容将介绍后者,前者将在数据表这一标题下解释。
它们的共同点都是要修改@Database注释里定义的属性version = 1

数据库迁移:实现新建数据表(版本1——>版本2)

数据库的迁移的概念:

在应用程序中添加和更改特性时,你需要修改实体类以反映这些更改。当用户更新应用程序到最新版本时,不希望它们丢失所有现有数据,尤其是如果无法从远程服务器恢复数据。“Room persistence library“库允许您编写Migration类来保存用户数据。每个迁移类指定起始版本和终结版本。在运行时,Room运行每个迁移类的migrate()方法,使用正确的顺序将数据库迁移到后面的版本。

如果我想在数据库里再建一个表,或者修改现有的表的属性等操作,除了可以用之前介绍的用Room的三大组件,也可以用Android提供的使用SQL操作数据库。比如我要在studentDb数据库里新建一个Course数据表,就可以使用语句

//database是SupportSQLiteDatabase类型的
database.execSQL("CREATE TABLE IF NOT EXISTS `Course` (`Cno` TEXT PRIMARY KEY NOT NULL, `Cname` TEXT,`Cpno` TEXT,`Ccredit` INTEGER)");

这个操作新加了一个数据表,将对数据库做一些调整。如何实现新增这个数据库呢?

Android提供了一个名为Migration的类,来完成Room的升级。

public Migration(int startVersion, int endVersion)

Migration有两个参数,startVersion和endVersion。startVersion表示当前版本(手机上安装的版本),endVersion表示将要升级到的版本。

  1. 还记得之前在@Database注释里定义的属性version = 1吧?版本号为1代表当前应用程序数据库版本为1。由于数据库将遇到更改,我们需要将它升至版本2。我们需要修改StudentDatabase代码,将@Database(entities = {Student.class}, version = 1, exportSchema = false)改为@Database(entities = {Student.class}, version = 2, exportSchema = false)
  2. 接下来我们需要增加一个Migration类型变量MIGRATION_1_2。该变量初始化时构造方法的参数分别传入1和2,表示数据库从1版本升至2版本。初始化时还需要定义一个方法public void migrate(SupportSQLiteDatabase database),这时我们就可以在这个方法里定义我们要做的操作——新建一个Course数据表了。
  3. 还没结束,当初在创建数据库的时候,fallbackToDestructiveMigration()addMigrations() 这两个方法就说要在这个部分讲。
    可以从这两个方法的名字看出,它们都是跟迁移Migration有关的。
  • 第一个方法 fallbackToDestructiveMigration() 用于:

如果从旧版本到新版本的迁移规则无法找到,就会触发错误,如果要避免该错误,可以使用fallbackToDestructiveMigration,这样就可以告诉Room,在找不到迁移规则时,可以破坏性重建数据库,注意这会删除所有数据库表数据。

因此,这个方法有一个对原有数据库表破坏重建的操作,但可以避免一些错误。这个方法不强制使用,你可以根据情况添加它。

  • 第二个方法addMigrations(),顾名思义,就是要用它来添加迁移。原先在这个方法里没有参数,因为我们才初次建立数据库,用不到迁移。这次,我们要将刚刚定义的Migration类型变量MIGRATION_1_2加入进去作为参数了。只有这样传入这个参数,程序知道你要迁移数据库了,这时你所做的改变(建立表Course)才会被执行。

经过了三处修改后(version = 2,MIGRATION_1_2变量的定义,addMigrations(MIGRATION_1_2)),代码如下:
StudentDatabase.java

@Database(entities = {Student.class}, version = 2, exportSchema = false)//exportSchema = false防止报错
public abstract class StudentDatabase extends RoomDatabase {
private static StudentDatabase sInstance;

    // 构造函数必须是public,否则报错
    public StudentDatabase() {
    }
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
//                创建新的数据表
            database.execSQL("CREATE TABLE IF NOT EXISTS `Course` (`Cno` TEXT PRIMARY KEY NOT NULL, `Cname` TEXT,`Cpno` TEXT,`Ccredit` INTEGER)");
        }
    };
    ......
    // 单实例模式,节省资源
    public static StudentDatabase getsInstance(Context context) {
        if (sInstance == null) {
            synchronized (StudentDatabase.class) {
                if (sInstance == null) {
                    sInstance = Room.databaseBuilder(context.getApplicationContext(),
                            StudentDatabase.class, "studentDb").fallbackToDestructiveMigration().addMigrations(MIGRATION_1_2).allowMainThreadQueries().build();allowMainThreadQueries()可以在主线程操作,获取数据库实例
                }
            }
        }
        return sInstance;
    }
    public abstract StudentDao studentDao();// 定义获取Dao的方法
}

再执行起来,打开cmd,我们将看到Course表被成功建立。
Course表创建

数据库迁移:实现更新数据表(版本2——>版本3)

现在我们的数据库版本已经是version=2了。下面的操作将数据库迁移到版本3,实现的是为Course表增加一个字段Cavg。

  1. 修改StudentDatabase代码,将@Database(entities = {Student.class}, version = 2, exportSchema = false)改为@Database(entities = {Student.class}, version = 3, exportSchema = false)
  2. 增加一个Migration类型变量MIGRATION_2_3。该变量初始化时构造方法的参数分别传入2和3,表示数据库从2版本升至3版本。初始化时还需要定义一个方法public void migrate(SupportSQLiteDatabase database),这时我们就可以在这个方法里定义我们要做的操作——为Course数据表增加一个字段了
  3. 最后,将刚刚定义的Migration类型变量MIGRATION_2_3加入到addMigrations() 作为参数。只有这样传入这个参数,程序知道你要迁移数据库了,这时你所做的改变(为Course数据表增加一个字段)才会被执行。

经过了三处修改后(version = 3,MIGRATION_2_3变量的定义,addMigrations(MIGRATION_2_3)),代码如下:
StudentDatabase.java

@Database(entities = {Student.class}, version = 3, exportSchema = false)//exportSchema = false防止报错
public abstract class StudentDatabase extends RoomDatabase {
    private static StudentDatabase sInstance;

    // 构造函数必须是public,否则报错
    public StudentDatabase() {
    }

    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
//                创建新的数据表
            database.execSQL("CREATE TABLE IF NOT EXISTS `Course` (`Cno` TEXT PRIMARY KEY NOT NULL, `Cname` TEXT,`Cpno` TEXT,`Ccredit` INTEGER)");
        }
    };
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            //为旧表添加新的字段
            database.execSQL("ALTER TABLE Course " + "ADD COLUMN Cavg INTEGER");
        }
    };
    ......
    // 单实例模式,节省资源
    public static StudentDatabase getsInstance(Context context) {
        if (sInstance == null) {
            synchronized (StudentDatabase.class) {
                if (sInstance == null) {
                    sInstance = Room.databaseBuilder(context.getApplicationContext(),
                            StudentDatabase.class, "studentDb").fallbackToDestructiveMigration().addMigrations(MIGRATION_2_3).allowMainThreadQueries().build();allowMainThreadQueries()可以在主线程操作,获取数据库实例
                }
            }
        }
        return sInstance;
    }


    public abstract StudentDao studentDao();// 定义获取Dao的方法
}

再执行起来,打开cmd,我们将看到Course表的字段Cavg被成功建立。
Course表的字段Cavg建立
注:你可能在这里会有一个问题,我可不可以不用再创建一个Migration类型变量,直接在MIGRATION_1_2的方法public void migrate(SupportSQLiteDatabase database) 里创建Course表语句后面加入为Course数据表增加一个字段的语句database.execSQL("ALTER TABLE Course " + "ADD COLUMN Cavg INTEGER");可以吗?你可以实验一下,最终应该是不成功的。我的理解是当你将版本修改为2并且已经运行一次后,将不会再执行方法里的内容了。也就是说,你新加在方法public void migrate(SupportSQLiteDatabase database) 的语句程序不会再执行了。因此我们需要再次迁移,在保留版本2新建的表Course的前提下为其新加一个字段Cavg。

数据库迁移:实现删除数据表(版本3——>版本4)

现在我们的数据库版本已经是version=3了。下面的操作将数据库迁移到版本4,实现的是删除Course表。

  1. 修改StudentDatabase代码,将@Database(entities = {Student.class}, version = 3, exportSchema = false)改为@Database(entities = {Student.class}, version = 4, exportSchema = false)
  2. 增加一个Migration类型变量MIGRATION_3_4。该变量初始化时构造方法的参数分别传入3和4,表示数据库从3版本升至4版本。初始化时还需要定义一个方法public void migrate(SupportSQLiteDatabase database),这时我们就可以在这个方法里定义我们要做的操作——删除Course数据表了
  3. 最后,将刚刚定义的Migration类型变量MIGRATION_3_4加入到addMigrations() 作为参数。只有这样传入这个参数,程序知道你要迁移数据库了,这时你所做的改变(删除Course数据表)才会被执行。

经过了三处修改后(version = 4,MIGRATION_3_4变量的定义,addMigrations(MIGRATION_3_4)),代码如下:
StudentDatabase.java

@Database(entities = {Student.class}, version = 4, exportSchema = false)//exportSchema = false防止报错
public abstract class StudentDatabase extends RoomDatabase {
    private static StudentDatabase sInstance;

    // 构造函数必须是public,否则报错
    public StudentDatabase() {
    }

    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
//                创建新的数据表
            database.execSQL("CREATE TABLE IF NOT EXISTS `Course` (`Cno` TEXT PRIMARY KEY NOT NULL, `Cname` TEXT,`Cpno` TEXT,`Ccredit` INTEGER)");
        }
    };
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            //为旧表添加新的字段
            database.execSQL("ALTER TABLE Course " + "ADD COLUMN Cavg INTEGER");
        }
    };
    static final Migration MIGRATION_3_4 = new Migration(3, 4) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("DROP TABLE IF EXISTS Course");
        }
    };/**/
    // 单实例模式,节省资源
    public static StudentDatabase getsInstance(Context context) {
        if (sInstance == null) {
            synchronized (StudentDatabase.class) {
                if (sInstance == null) {
                    sInstance = Room.databaseBuilder(context.getApplicationContext(),
                            StudentDatabase.class, "studentDb").fallbackToDestructiveMigration().addMigrations(MIGRATION_3_4).allowMainThreadQueries().build();allowMainThreadQueries()可以在主线程操作,获取数据库实例
                }
            }
        }
        return sInstance;
    }


    public abstract StudentDao studentDao();// 定义获取Dao的方法
}

再执行起来,打开cmd,我们将看到Course表被删除了。
Course表被删除

数据表

经过了之前的讲解,关于数据表的建立我想你已经掌握了吧?这里总结一下,用ROOM的三大组件(Database、Entity、DAO) 就可以将一个数据表与数据库绑定,其中Entity定义了数据表的表名和属性,DAO定义了数据表会用到的操作。再Room.databaseBuilder(…).build();时建立数据库,与它绑定的数据表也将随之建立。在上一节讲升级数据库时涉及到用迁移数据库的方法结合SQL语句改变数据表,接下来的内容就要描述Room在改变数据表方面的优势了。内容也将涉及到升级数据库

建立数据库中的数据表

注:由于之前用数据库迁移使数据库版本升到了4,现在我们让它还原成版本1时的代码,再运行一次,以防止在接下来通过升级数据库在数据库里增加表时遇到问题。

  1. 新建一个SC类,表示一个SC表,并让它被 @Entity 注释。注意这次我们用联合主键将SC表的Sno字段和Cno字段都设置为了主键,实现这一目的只需在 @Entity 注释里添加属性primaryKeys = {"Sno", "Cno"}即可。

SC.java

package com.example.myroomapplication;

import androidx.room.Entity;

@Entity(primaryKeys = {"Sno", "Cno"})
public class SC {
    private int Sno;//学号
    private int Cno;//课程号
    private int Grade;//成绩

    public int getSno() {
        return Sno;
    }

    public void setSno(int sno) {
        Sno = sno;
    }

    public int getCno() {
        return Cno;
    }

    public void setCno(int cno) {
        Cno = cno;
    }

    public int getGrade() {
        return Grade;
    }

    public void setGrade(int grade) {
        Grade = grade;
    }
}
  1. 接下来我们就要把这张表建立在数据库studentDb里了。也用到升级数据库,操作很简单,只需改StudentDatabase的注释即可 ,在entities里添加属性值SC.class,再将version=1改为version = 2就可以了。
    StudentDatabase.java
package com.example.myroomapplication;

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

// 声明为Database,指定表格、版本
@Database(entities = {Student.class,SC.class}, version = 2, exportSchema = false)//exportSchema = false防止报错
public abstract class StudentDatabase extends RoomDatabase {
    private static StudentDatabase sInstance;

    // 构造函数必须是public,否则报错
    public StudentDatabase() {
    }

    /*static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
//                创建新的数据表
            database.execSQL("CREATE TABLE IF NOT EXISTS `Course` (`Cno` TEXT PRIMARY KEY NOT NULL, `Cname` TEXT,`Cpno` TEXT,`Ccredit` INTEGER)");
        }
    };
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            //为旧表添加新的字段
            database.execSQL("ALTER TABLE Course " + "ADD COLUMN Cavg INTEGER");
        }
    };
    static final Migration MIGRATION_3_4 = new Migration(3, 4) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("DROP TABLE IF EXISTS Course");
        }
    };*/
    // 单实例模式,节省资源
    public static StudentDatabase getsInstance(Context context) {
        if (sInstance == null) {
            synchronized (StudentDatabase.class) {
                if (sInstance == null) {
                    sInstance = Room.databaseBuilder(context.getApplicationContext(),
                            StudentDatabase.class, "studentDb").fallbackToDestructiveMigration().addMigrations().allowMainThreadQueries().build();allowMainThreadQueries()可以在主线程操作,获取数据库实例
                }
            }
        }
        return sInstance;
    }


    public abstract StudentDao studentDao();// 定义获取Dao的方法
}

打开cmd查看建表结果,发现SC表已经成功建立。这就是使用Room创建数据表的便利之处了,只需要新建一个被@Entity注释的类作为表,它的成员变量就是属性。在数据库类里也只需改动一点注释,表就成功建好了。这省去了大量的SQL语言,使操作非常简单。
建立SC数据表

升级数据库中的数据表

升级数据表也同样地简单。

  • 如果你要给SC表添加一个字段,只需要在SC类新建一个成员变量并创建它的setter和getter方法即可。
  • 如果要修改一个字段,也只需要改它对应的成员变量的名字或类型以及setter和getter方法。
  • 如果要删除一个字段,则把那个成员变量和相应的setter和getter方法删除。
  • 最后别忘了在StudentDatabase里修改一下version = 3

下面展示了将Grade字段修改为浮点型,并创建一个Rank字段。
SC.java

package com.example.myroomapplication;

import androidx.room.Entity;

@Entity(primaryKeys = {"Sno", "Cno"})
public class SC {
    private int Sno;//学号
    private int Cno;//课程号
    private float Grade;//成绩
    private String Rank;//排名

    public String getRank() {
        return Rank;
    }

    public void setRank(String rank) {
        Rank = rank;
    }

    public int getSno() {
        return Sno;
    }

    public void setSno(int sno) {
        Sno = sno;
    }

    public int getCno() {
        return Cno;
    }

    public void setCno(int cno) {
        Cno = cno;
    }

    public float getGrade() {
        return Grade;
    }

    public void setGrade(float grade) {
        Grade = grade;
    }
}

StudentDatabase.java

package com.example.myroomapplication;

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

// 声明为Database,指定表格、版本
@Database(entities = {Student.class,SC.class}, version = 3, exportSchema = false)//exportSchema = false防止报错
public abstract class StudentDatabase extends RoomDatabase {
    private static StudentDatabase sInstance;

    // 构造函数必须是public,否则报错
    public StudentDatabase() {
    }

    /*static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
//                创建新的数据表
            database.execSQL("CREATE TABLE IF NOT EXISTS `Course` (`Cno` TEXT PRIMARY KEY NOT NULL, `Cname` TEXT,`Cpno` TEXT,`Ccredit` INTEGER)");
        }
    };
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            //为旧表添加新的字段
            database.execSQL("ALTER TABLE Course " + "ADD COLUMN Cavg INTEGER");
        }
    };
    static final Migration MIGRATION_3_4 = new Migration(3, 4) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("DROP TABLE IF EXISTS Course");
        }
    };*/
    // 单实例模式,节省资源
    public static StudentDatabase getsInstance(Context context) {
        if (sInstance == null) {
            synchronized (StudentDatabase.class) {
                if (sInstance == null) {
                    sInstance = Room.databaseBuilder(context.getApplicationContext(),
                            StudentDatabase.class, "studentDb").fallbackToDestructiveMigration().addMigrations().allowMainThreadQueries().build();allowMainThreadQueries()可以在主线程操作,获取数据库实例
                }
            }
        }
        return sInstance;
    }


    public abstract StudentDao studentDao();// 定义获取Dao的方法
}

对SC表添加字段,修改字段类型

删除数据库中的数据表

删除操作可能没有那么顺利,如果改了StudentDatabase的注释 ,在entities里删除属性值SC.classversion=3改为version = 4,删除了SC.java类,发现该表仍存在的话,还可以选择上面的借助SQL语句用数据库迁移的方法删除表。(见数据库迁移:实现删除数据表(版本3——>版本4))

记录

至此,Room的相关基础知识已经全部涉及到了。由于之前我们一直在研究建库建表的问题,都没有提及活动的事情。还记得StudentDao吗?接下来我们要做的就是在主活动里使用它来为数据表添加数据,然后再在APP界面里显示出数据来啦。

前期准备

讲了那么多Room的内容,终于要提到开头中的LiveData+ViewModel+RecyclerView了。由于这里的重点是Room,这三者不做重点描述,未学到这些知识的朋友可以去查一查它们的用法。

RecyclerView

RecyclerView,一个强大的滚动控件。注意在使用它之前要导入库,这在一开头导入Room库时就提到了。RecyclerView可以显示列表项,我们就让它来显示我们插入的学生数据。
布局文件activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="增加一条记录"
        android:layout_gravity="center" />
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">
        <EditText
            android:id="@+id/et2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="请输入删除的学生姓名"
            android:singleLine="true"/>
        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="删除一条记录" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">
        <EditText
            android:id="@+id/et3_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="请输入更新的学生姓名"
            android:singleLine="true"/>
        <EditText
            android:id="@+id/et3_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="更新后的系别"
            android:singleLine="true"/>
        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="更新一条记录" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">
    <EditText
        android:id="@+id/et4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="请输入查询的学生姓名"
        android:singleLine="true"/>
    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查询一条记录" />
    </LinearLayout>

    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="增加多条记录"
        android:layout_gravity="center"/>

    <!--<Button
        android:id="@+id/button6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查询所有记录"
        android:layout_gravity="center"/>-->

    <Button
        android:id="@+id/button7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="删除所有记录"
        android:layout_gravity="center"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="20sp"
        android:text="查询一名学生"
        android:textColor="#000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="20sp"
        android:text="学号 姓名 性别 年龄 系别"
        android:textColor="#f00"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/studentoneinfo"
        android:layout_gravity="center"
        android:textSize="20sp"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="20sp"
        android:text="查询全部学生"
        android:textColor="#000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="20sp"
        android:text="学号 姓名 性别 年龄 系别"
        android:textColor="#f00"/>
    <!--<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/studentinfo"
        android:layout_gravity="center"
        android:textSize="20sp" />
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#000"/>-->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerStudent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
</LinearLayout>
  • 这个布局文件的内容包含六个按钮,用于六个事件增加一条记录删除一条记录更新一条记录查询一条记录增加多条记录删除所有记录的触发,MainActivity会为它们注册监听器。
  • 其中删除一条记录更新一条记录查询一条记录前有输入框,通过输入学生姓名就可以执行相应的操作,其中更新一条记录根据DAO的定义是更新系别,因此更新后的系别也是需要输入的。
  • 下方的View只用来显示一条横线作为美观,无其他用途。
  • 接下来的文本TextView用来展示"学号 姓名 性别 年龄 系别"这些标题,方便阅读。在它下面的TextView并没有指定内容,因为它将用来显示只查询一个学生的学生信息,其内容会不断变化。
  • 最后一个组件就是列表项RecyclerView了。此外,如果不用列表项,而要用TextView显示全部学生信息的话也可以。
  • 所有的组件对于对齐方式、字体大小等进行了一系列调整,界面美观方面将由你自己决定怎么操作。

布局文件listview_item_bt.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/sno"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/sname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/ssex"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/sage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/sdept"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20sp"/>

</LinearLayout>

RecyclerView的每个列表项由5个TextView组成,分别显示学号、姓名、性别、年龄、系别。
StudentAdapter.java

package com.example.myroomapplication;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.StudentHolder> {

    private static final String TAG = "StudentAdapter";
    private List<Student> mStudents = new ArrayList<>();
    private final LayoutInflater mInflater;
    static class StudentHolder extends RecyclerView.ViewHolder {

        public final TextView text1;
        public final TextView text2;
        public final TextView text3;
        public final TextView text4;
        public final TextView text5;
        //public final TextView mText;

        public StudentHolder(View itemView) {
            super(itemView);
            text1 = (TextView)itemView.findViewById(R.id.sno);
            text2 = (TextView)itemView.findViewById(R.id.sname);
            text3 = (TextView)itemView.findViewById(R.id.ssex);
            text4 = (TextView)itemView.findViewById(R.id.sage);
            text5 = (TextView)itemView.findViewById(R.id.sdept);
            //mText = itemView.findViewById(android.R.id.text1);
        }
    }

    public void setList(List<Student> Students) {
        mStudents = Students;
        notifyDataSetChanged();
    }
    public StudentAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
    }

    @NonNull
    @Override
    public StudentHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.listview_item_bt,parent,false);//false表示不加载到父布局上
        final StudentHolder holder = new StudentHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull StudentHolder holder, int position) {
        if (mStudents != null) {
            holder.text1.setText(""+mStudents.get(position).getSno());
            holder.text2.setText(mStudents.get(position).getSname());
            holder.text3.setText(mStudents.get(position).getSsex());
            holder.text4.setText(""+mStudents.get(position).getSage());
            holder.text5.setText(mStudents.get(position).getSdept());
            //holder.mText.setText(mStudents.get(position).toString());
        }
        else {
            //holder.mText.setText("No Info");
            holder.text1.setText("No Sno");
            holder.text2.setText("No Sname");
            holder.text3.setText("No Ssex");
            holder.text4.setText("No Sage");
            holder.text5.setText("No Sdept");
        }
    }

    @Override
    public int getItemCount() {
        if (mStudents != null)
            return mStudents.size();
        else return 0;
    }
}

RecyclerView的适配器,内含ViewHolder,都是基本的列表项所需代码。
至此,用于显示的界面就准备好了。

LiveData

LiveData是可观察的查询
当执行查询时,您经常希望应用程序的UI在数据更改时自动更新。要实现这一点,请在查询方法描述中使用类型LiveData的返回值。当数据库被更新时,Room生成所有必要的代码来更新LiveData。
LiveData是一个数据持有类。它能够保证数据和UI统一。这个和LiveData采用了观察者模式有关,LiveData是被观察者,当数据有变化时会通知观察者(UI)。

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


概括来说,就是它能够被UI观察其所存储数据的变化,如果有变化能更新其存储的数据,UI(用户看到的手机APP页面)上的信息也随之更新。它和ViewModel经常配合使用。
我们这里把它用在查询全部学生的返回结果上,由于对数据进行动态增删改,查询的全部学生信息是不断变化的,因此用LiveData来存储全部学生数据。

ViewModel

ViewModel类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据,ViewModel中数据会一直存活即使 activity configuration发生变化,比如横竖屏切换的时候。

  • ViewModel是架构组件。注意使用它也需要导入库,在导入Room的时候已经提到。要怎么使用它呢?需要定义一个MainViewModel类,再让它继承AndroidViewModel,这就是你自己的一个ViewModel。
  • ViewModel存储了StudentDao类型变量和LiveData<List< Student >> 类型变量作为成员变量,并在构造函数中对这两个变量进行初始化,也有getStudentDao()getStudents() 这两个方法分别返回那两个成员变量。因为DAO操作和LiveData<List< Student >>里存储的数据是密不可分的,ViewModel将这两者连接在一起,以后在主活动使用StudentDao和LiveData<List< Student >>时就可以通过ViewModel来代替了。
  • 注意,我们将在ViewModel里实现数据库的创建语句,即调用StudentDatabase.getsInstance(application) 这样ViewModel就可以间接地获得StudentDao了。
    MainViewModel.java
package com.example.myroomapplication;

import android.app.Application;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

import java.util.List;

public class MainViewModel extends AndroidViewModel {//然后ViewModel就可以从Dao中获取LiveData了

    private final StudentDao mStudentDao;
    //private static final String TAG = "MainViewModel";
    private LiveData<List<Student>> mStudents;

    public MainViewModel(@NonNull Application application) {
        super(application);
        mStudentDao = StudentDatabase.getsInstance(application).studentDao();
        mStudents = mStudentDao.getAllStudents();
    }


    public StudentDao getStudentDao(){
        return mStudentDao;
    }

    public LiveData<List<Student>> getStudents() {
        return mStudents;
    }
}

关于主活动中ViewModel变量的获取以及通过ViewModel间接使用LiveData<List< Student >>的用法,相信你阅读下面的代码就可以猜到。获取ViewModel需要ViewModelProviders.of(this).get(MainViewModel.class) 这样初始化,不要通过new。
ViewModel+LiveData的配合也在下面的代码得到了体现。通过ViewModel获得LiveData的变量,并为其添加观察者observe,观察其中的链表List< Student >。注册观察者要实现onChanged的方法,这里就是将查询得到的链表结果发送给列表项RecyclerView,列表项再通知数据更新,这样每次我们对数据进行增删查的时候,所看到的列表项也将实时变化了,
MainActivity.java 的部分代码

// 获取ViewModel
        mViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
        // 观察ViewModel中的LiveData的变化
        mViewModel.getStudents().observe(this, new Observer<List<Student>>() {
            @Override
            public void onChanged(@Nullable List<Student> Students) {
                mStudentAdapter.setList(Students);
                mStudentAdapter.notifyDataSetChanged();
                }

            }
        });

MainActivity.java 的全部代码

package com.example.myroomapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private Button button1;
    private Button button2;
    private Button button3;
    private Button button4;
    private Button button5;
    //private Button button6;
    private Button button7;
    private EditText et2;
    private EditText et3_1;
    private EditText et3_2;
    private EditText et4;
    //private TextView textView1;
    private TextView textView;
    private RecyclerView mRecyclerView;
    private StudentAdapter mStudentAdapter;
    private MainViewModel mViewModel;
    private Student student;
    private String sname;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button1 = findViewById(R.id.button1);
        button2 = findViewById(R.id.button2);
        button3 = findViewById(R.id.button3);
        button4 = findViewById(R.id.button4);
        button5 = findViewById(R.id.button5);
        //button6 = findViewById(R.id.button6);
        button7 = findViewById(R.id.button7);
        et2 = findViewById(R.id.et2);
        et3_1 = findViewById(R.id.et3_1);
        et3_2 = findViewById(R.id.et3_2);
        et4 = findViewById(R.id.et4);
        //textView1 = findViewById(R.id.studentinfo);
        textView = findViewById(R.id.studentoneinfo);
        button1.setOnClickListener(this);
        button2.setOnClickListener(this);
        button3.setOnClickListener(this);
        button4.setOnClickListener(this);
        button5.setOnClickListener(this);
        //button6.setOnClickListener(this);
        button7.setOnClickListener(this);
        mRecyclerView = (RecyclerView)findViewById(R.id.recyclerStudent);
        mStudentAdapter = new StudentAdapter(this);
        //桥接
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        //设置RecyclerView的布局方向为线性布局
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.setAdapter(mStudentAdapter);

        // 获取ViewModel
        mViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
        // 观察ViewModel中的LiveData的变化
        mViewModel.getStudents().observe(this, new Observer<List<Student>>() {
            @Override
            public void onChanged(@Nullable List<Student> Students) {
                mStudentAdapter.setList(Students);
                mStudentAdapter.notifyDataSetChanged();
                //mRecyclerView.smoothScrollToPosition(mStudentAdapter.getItemCount() - 1);
                    /*String s = "";
                    for (int i=0;i<Students.size();i++){
                        s = s + Students.get(i).getSno()+" "
                                +Students.get(i).getSname()+" "
                                +Students.get(i).getSsex()+" "
                                +Students.get(i).getSage()+" "
                                +Students.get(i).getSdept();
                        s = s + "\n";*/
                    /*Log.e("MainActivity",Students.get(i).getSno()+" "
                            +Students.get(i).getSname()+" "
                            +Students.get(i).getSsex()+" "
                            +Students.get(i).getSage()+" "
                            +Students.get(i).getSdept());*/
                    //}
                    //textView1.setText(s);

            }
        });
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button1:
                //增加一条记录
                mViewModel.getStudentDao().insertOneStudent(createOneStudent());
                textView.setText("");
                break;
            case R.id.button2:
                //删除一条记录
                sname = et2.getText().toString();
                student = mViewModel.getStudentDao().loadStudentByName(sname);
                mViewModel.getStudentDao().deleteStudent(student);
                textView.setText("");
                break;
            case R.id.button3:
                //更新一条记录
                updateRecord();
                textView.setText("");
                break;
            case R.id.button4:
                //查询一条记录
                sname = et4.getText().toString();
                student = mViewModel.getStudentDao().loadStudentByName(sname);
                String as = student.getSno()+" "
                        +student.getSname()+" "
                        +student.getSsex()+" "
                        +student.getSage()+" "
                        +student.getSdept();
                as = as + "\n";
                textView.setText(as);
                break;
            case R.id.button5:
                //增加多条记录
                mViewModel.getStudentDao().insertStudents(createStudents());
                textView.setText("");
                break;
            /*case R.id.button6:
                //查询所有记录
                break;*/
            case R.id.button7:
                //删除所有记录
                mViewModel.getStudentDao().deleteAllStudent();
                textView.setText("");
                break;
        }
    }


    private Student createOneStudent(){
        Student student = new Student();
        student.setSname("李勇");
        student.setSsex("男");
        student.setSage(20);
        student.setSdept("CS");
        return student;
    }
    private Student[] createStudents(){
        Student[] students = new Student[10];
        String[] names = new String[]{"刘晨","王敏","张立","赵菁菁","张衡","张向东","张向丽","王芳","王民生","马翔阳"};
        String[] sexs = new String[]{"女","女","男","女","男","男","女","女","男","男"};
        int[] ages = new int[]{19,18,19,23,18,20,20,20,25,21};
        String[] depts = new String[]{"IS","MA","IS","CS","IS","IS","IS","CS","MA",""};
        for (int i=0;i<students.length;i++){
            students[i] = new Student();
            students[i].setSname(names[i]);
            students[i].setSsex(sexs[i]);
            students[i].setSage(ages[i]);
            students[i].setSdept(depts[i]);
        }
        return students;

    }
    private void updateRecord() {
        sname = et3_1.getText().toString();
        String sdept = et3_2.getText().toString();
        student = mViewModel.getStudentDao().loadStudentByName(sname);
        student.setSdept(sdept);
        mViewModel.getStudentDao().updateStudents(student);
    }


}

主活动运行截图:
主活动刚启动

对数据表中的数据记录进行添加操作

现在我们只差主活动的代码完善了。关于数据库的获取,由于已经在MainViewModel.java里实现,我们只需要MainViewModel类型变量就可以了。同样地,我们要通过MainViewModel类型变量获得DAO,再调用DAO的insertOneStudent或insertStudents方法,就可以为记录添加数据了。
这里的数据由Student类型变量的setXXX方法设置,本实例是在代码中敲,在以后的应用中可以通过用户自己输入或者别的渠道获得。
当然真正对记录进行添加操作的实现,还是

@Insert
    public void insertOneStudent(Student student); //插入一条学生信息

这个带注释的方法。
MainActivity.java 的部分代码

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button1:
                //增加一条记录
                mViewModel.getStudentDao().insertOneStudent(createOneStudent());
                textView.setText("");
                break;
            ......
            case R.id.button5:
                //增加多条记录
                mViewModel.getStudentDao().insertStudents(createStudents());
                textView.setText("");
                break;
                ......
        }
    }


    private Student createOneStudent(){
        Student student = new Student();
        student.setSname("李勇");
        student.setSsex("男");
        student.setSage(20);
        student.setSdept("CS");
        return student;
    }
    private Student[] createStudents(){
        Student[] students = new Student[10];
        String[] names = new String[]{"刘晨","王敏","张立","赵菁菁","张衡","张向东","张向丽","王芳","王民生","马翔阳"};
        String[] sexs = new String[]{"女","女","男","女","男","男","女","女","男","男"};
        int[] ages = new int[]{19,18,19,23,18,20,20,20,25,21};
        String[] depts = new String[]{"IS","MA","IS","CS","IS","IS","IS","CS","MA",""};
        for (int i=0;i<students.length;i++){
            students[i] = new Student();
            students[i].setSname(names[i]);
            students[i].setSsex(sexs[i]);
            students[i].setSage(ages[i]);
            students[i].setSdept(depts[i]);
        }
        return students;

    }

最终实现的效果就是点击第一个按钮出现一条记录,再点击“增加多条记录”按钮出现多条记录。
增加一条记录截图:
增加一条记录
增加多条记录截图:
增加多条记录
滑动列表项截图:
滑动列表项

对数据表中的数据记录进行删除操作

与增加类似,只要通过输入框里的学生姓名可以实现删除一条记录。点击“删除所有记录”实现删除所有学生记录。
当然真正对记录进行删除操作的实现,还是

@Delete
    public void deleteStudent(Student student);//删除一个学生

这个带注释的方法。
MainActivity.java 的部分代码

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            ......
            case R.id.button2:
                //删除一条记录
                sname = et2.getText().toString();
                student = mViewModel.getStudentDao().loadStudentByName(sname);
                mViewModel.getStudentDao().deleteStudent(student);
                textView.setText("");
                break;
            ......
            case R.id.button7:
                //删除所有记录
                mViewModel.getStudentDao().deleteAllStudent();
                textView.setText("");
                break;
        }
    }

删除一条记录截图:
删除一条记录

对数据表中的数据记录进行修改操作

与增加类似,只要通过输入框里的学生姓名可以实现更新一条记录。
当然真正对记录进行更新操作的实现,还是

@Update(onConflict = OnConflictStrategy.REPLACE)
    public int updateStudents(Student... students); //更新学生信息,当有冲突时则进行替代

这个带注释的方法。
MainActivity.java 的部分代码

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            ......
            case R.id.button3:
                //更新一条记录
                updateRecord();
                textView.setText("");
                break;
            ......
          
        }
    }
    private void updateRecord() {
        sname = et3_1.getText().toString();
        String sdept = et3_2.getText().toString();
        student = mViewModel.getStudentDao().loadStudentByName(sname);
        student.setSdept(sdept);
        mViewModel.getStudentDao().updateStudents(student);
    }

更新一条记录截图:
更新一条记录

对数据表中的数据记录进行查询操作

与增加类似,只要通过输入框里的学生姓名可以实现查询一条记录。查询出来的结果将由setText打印在界面上。
由于查询全部学生信息已经由可观察的查询LiveData的观察者实现,所以每次列表项显示的都是当前的全部学生。
当然真正对记录进行查询操作的实现,还是

@Query("SELECT * FROM  student")
    public LiveData<List<Student>> getAllStudents(); //查询所有学生数据,LiveData令App 的 UI 在数据发生变化时自动更新 UI

    @Query("SELECT * FROM student WHERE Sname = :Sname")
    public Student loadStudentByName(String Sname); //根据名字加载学生

这个带注释的方法。
MainActivity.java 的部分代码

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            ......
            case R.id.button4:
                //查询一条记录
                sname = et4.getText().toString();
                student = mViewModel.getStudentDao().loadStudentByName(sname);
                String as = student.getSno()+" "
                        +student.getSname()+" "
                        +student.getSsex()+" "
                        +student.getSage()+" "
                        +student.getSdept();
                as = as + "\n";
                textView.setText(as);
                break;
            ......
          
        }
    }

查询一条记录截图:
查询一条记录

Demo全部代码

MainActivity.java

package com.example.myroomapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private Button button1;
    private Button button2;
    private Button button3;
    private Button button4;
    private Button button5;
    //private Button button6;
    private Button button7;
    private EditText et2;
    private EditText et3_1;
    private EditText et3_2;
    private EditText et4;
    //private TextView textView1;
    private TextView textView;
    private RecyclerView mRecyclerView;
    private StudentAdapter mStudentAdapter;
    private MainViewModel mViewModel;
    private Student student;
    private String sname;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button1 = findViewById(R.id.button1);
        button2 = findViewById(R.id.button2);
        button3 = findViewById(R.id.button3);
        button4 = findViewById(R.id.button4);
        button5 = findViewById(R.id.button5);
        //button6 = findViewById(R.id.button6);
        button7 = findViewById(R.id.button7);
        et2 = findViewById(R.id.et2);
        et3_1 = findViewById(R.id.et3_1);
        et3_2 = findViewById(R.id.et3_2);
        et4 = findViewById(R.id.et4);
        //textView1 = findViewById(R.id.studentinfo);
        textView = findViewById(R.id.studentoneinfo);
        button1.setOnClickListener(this);
        button2.setOnClickListener(this);
        button3.setOnClickListener(this);
        button4.setOnClickListener(this);
        button5.setOnClickListener(this);
        //button6.setOnClickListener(this);
        button7.setOnClickListener(this);
        mRecyclerView = (RecyclerView)findViewById(R.id.recyclerStudent);
        mStudentAdapter = new StudentAdapter(this);
        //桥接
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        //设置RecyclerView的布局方向为线性布局
        mRecyclerView.setLayoutManager(layoutManager);
        mRecyclerView.setAdapter(mStudentAdapter);

        // 获取ViewModel
        mViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
        // 观察ViewModel中的LiveData的变化
        mViewModel.getStudents().observe(this, new Observer<List<Student>>() {
            @Override
            public void onChanged(@Nullable List<Student> Students) {
                mStudentAdapter.setList(Students);
                mStudentAdapter.notifyDataSetChanged();
                //mRecyclerView.smoothScrollToPosition(mStudentAdapter.getItemCount() - 1);
                    /*String s = "";
                    for (int i=0;i<Students.size();i++){
                        s = s + Students.get(i).getSno()+" "
                                +Students.get(i).getSname()+" "
                                +Students.get(i).getSsex()+" "
                                +Students.get(i).getSage()+" "
                                +Students.get(i).getSdept();
                        s = s + "\n";*/
                    /*Log.e("MainActivity",Students.get(i).getSno()+" "
                            +Students.get(i).getSname()+" "
                            +Students.get(i).getSsex()+" "
                            +Students.get(i).getSage()+" "
                            +Students.get(i).getSdept());*/
                    //}
                    //textView1.setText(s);

            }
        });
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button1:
                //增加一条记录
                mViewModel.getStudentDao().insertOneStudent(createOneStudent());
                textView.setText("");
                break;
            case R.id.button2:
                //删除一条记录
                sname = et2.getText().toString();
                student = mViewModel.getStudentDao().loadStudentByName(sname);
                mViewModel.getStudentDao().deleteStudent(student);
                textView.setText("");
                break;
            case R.id.button3:
                //更新一条记录
                updateRecord();
                textView.setText("");
                break;
            case R.id.button4:
                //查询一条记录
                sname = et4.getText().toString();
                student = mViewModel.getStudentDao().loadStudentByName(sname);
                String as = student.getSno()+" "
                        +student.getSname()+" "
                        +student.getSsex()+" "
                        +student.getSage()+" "
                        +student.getSdept();
                as = as + "\n";
                textView.setText(as);
                break;
            case R.id.button5:
                //增加多条记录
                mViewModel.getStudentDao().insertStudents(createStudents());
                textView.setText("");
                break;
            /*case R.id.button6:
                //查询所有记录
                break;*/
            case R.id.button7:
                //删除所有记录
                mViewModel.getStudentDao().deleteAllStudent();
                textView.setText("");
                break;
        }
    }


    private Student createOneStudent(){
        Student student = new Student();
        student.setSname("李勇");
        student.setSsex("男");
        student.setSage(20);
        student.setSdept("CS");
        return student;
    }
    private Student[] createStudents(){
        Student[] students = new Student[10];
        String[] names = new String[]{"刘晨","王敏","张立","赵菁菁","张衡","张向东","张向丽","王芳","王民生","马翔阳"};
        String[] sexs = new String[]{"女","女","男","女","男","男","女","女","男","男"};
        int[] ages = new int[]{19,18,19,23,18,20,20,20,25,21};
        String[] depts = new String[]{"IS","MA","IS","CS","IS","IS","IS","CS","MA",""};
        for (int i=0;i<students.length;i++){
            students[i] = new Student();
            students[i].setSname(names[i]);
            students[i].setSsex(sexs[i]);
            students[i].setSage(ages[i]);
            students[i].setSdept(depts[i]);
        }
        return students;

    }
    private void updateRecord() {
        sname = et3_1.getText().toString();
        String sdept = et3_2.getText().toString();
        student = mViewModel.getStudentDao().loadStudentByName(sname);
        student.setSdept(sdept);
        mViewModel.getStudentDao().updateStudents(student);
    }


}

MainViewModel.java

package com.example.myroomapplication;

import android.app.Application;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

import java.util.List;

public class MainViewModel extends AndroidViewModel {//然后ViewModel就可以从Dao中获取LiveData了

    private final StudentDao mStudentDao;
    //private static final String TAG = "MainViewModel";
    private LiveData<List<Student>> mStudents;

    public MainViewModel(@NonNull Application application) {
        super(application);
        mStudentDao = StudentDatabase.getsInstance(application).studentDao();
        mStudents = mStudentDao.getAllStudents();
    }


    public StudentDao getStudentDao(){
        return mStudentDao;
    }

    public LiveData<List<Student>> getStudents() {
        return mStudents;
    }
}

Student.java

package com.example.myroomapplication;

import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity(tableName = "student")//Room 使用字段(Filed)名称作为在数据库中的默认列名。可以通过给 Filed 添加 @ColumnInfo 注解设置列名。
public class Student {
    // 设置主键
    @PrimaryKey(autoGenerate = true)//如果需要 Room 自动分配 IDs 给 Entity,可以设置 @PrimaryKey 的 autoGenerate 属性。
    private int Sno;//学号

    private String Sname;//姓名
    private String Ssex;//性别
    private int Sage;//年龄
    private String Sdept;//系别

    // 提供setter和getter供Room框架调用


    public int getSno() {
        return Sno;
    }

    public void setSno(int sno) {
        Sno = sno;
    }

    public String getSname() {
        return Sname;
    }

    public void setSname(String sname) {
        Sname = sname;
    }

    public String getSsex() {
        return Ssex;
    }

    public void setSsex(String ssex) {
        Ssex = ssex;
    }

    public int getSage() {
        return Sage;
    }

    public void setSage(int sage) {
        Sage = sage;
    }

    public String getSdept() {
        return Sdept;
    }

    public void setSdept(String sdept) {
        Sdept = sdept;
    }

}

StudentAdapter.java

package com.example.myroomapplication;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.StudentHolder> {

    private static final String TAG = "StudentAdapter";
    private List<Student> mStudents = new ArrayList<>();
    private final LayoutInflater mInflater;
    static class StudentHolder extends RecyclerView.ViewHolder {

        public final TextView text1;
        public final TextView text2;
        public final TextView text3;
        public final TextView text4;
        public final TextView text5;
        //public final TextView mText;

        public StudentHolder(View itemView) {
            super(itemView);
            text1 = (TextView)itemView.findViewById(R.id.sno);
            text2 = (TextView)itemView.findViewById(R.id.sname);
            text3 = (TextView)itemView.findViewById(R.id.ssex);
            text4 = (TextView)itemView.findViewById(R.id.sage);
            text5 = (TextView)itemView.findViewById(R.id.sdept);
            //mText = itemView.findViewById(android.R.id.text1);
        }
    }

    public void setList(List<Student> Students) {
        mStudents = Students;
        notifyDataSetChanged();
    }
    public StudentAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
    }

    @NonNull
    @Override
    public StudentHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.listview_item_bt,parent,false);//false表示不加载到父布局上
        final StudentHolder holder = new StudentHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull StudentHolder holder, int position) {
        if (mStudents != null) {
            holder.text1.setText(""+mStudents.get(position).getSno());
            holder.text2.setText(mStudents.get(position).getSname());
            holder.text3.setText(mStudents.get(position).getSsex());
            holder.text4.setText(""+mStudents.get(position).getSage());
            holder.text5.setText(mStudents.get(position).getSdept());
            //holder.mText.setText(mStudents.get(position).toString());
        }
        else {
            //holder.mText.setText("No Info");
            holder.text1.setText("No Sno");
            holder.text2.setText("No Sname");
            holder.text3.setText("No Ssex");
            holder.text4.setText("No Sage");
            holder.text5.setText("No Sdept");
        }
    }

    @Override
    public int getItemCount() {
        if (mStudents != null)
            return mStudents.size();
        else return 0;
    }
}

StudentDao.java

package com.example.myroomapplication;

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;

import java.util.List;

@Dao
public interface StudentDao {
    @Insert
    public void insertOneStudent(Student student); //插入一条学生信息

    @Insert
    public void insertStudents(Student... students); //插入多条学生信息

    @Update(onConflict = OnConflictStrategy.REPLACE)
    public int updateStudents(Student... students); //更新学生信息,当有冲突时则进行替代

    @Delete
    public void deleteStudent(Student student);//删除一个学生

    @Query("DELETE FROM  student")
    public void deleteAllStudent();//删除所有学生

    @Query("SELECT * FROM  student")
    public LiveData<List<Student>> getAllStudents(); //查询所有学生数据,LiveData令App 的 UI 在数据发生变化时自动更新 UI

    @Query("SELECT * FROM student WHERE Sname = :Sname")
    public Student loadStudentByName(String Sname); //根据名字加载学生

}

StudentDatabase.java

package com.example.myroomapplication;

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

// 声明为Database,指定表格、版本
@Database(entities = {Student.class}, version = 1, exportSchema = false)//exportSchema = false防止报错
public abstract class StudentDatabase extends RoomDatabase {
    private static StudentDatabase sInstance;

    // 构造函数必须是public,否则报错
    public StudentDatabase() {
    }
    /*static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
//                创建新的数据表
            database.execSQL("CREATE TABLE IF NOT EXISTS `Course` (`Cno` TEXT PRIMARY KEY NOT NULL, `Cname` TEXT,`Cpno` TEXT,`Ccredit` INTEGER)");
        }
    };
    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            //为旧表添加新的字段
            database.execSQL("ALTER TABLE Course " + "ADD COLUMN Cavg INTEGER");
        }
    };
    static final Migration MIGRATION_3_4 = new Migration(3, 4) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("DROP TABLE IF EXISTS Course");
        }
    };*/
    // 单实例模式,节省资源
    public static StudentDatabase getsInstance(Context context) {
        if (sInstance == null) {
            synchronized (StudentDatabase.class) {
                if (sInstance == null) {
                    sInstance = Room.databaseBuilder(context.getApplicationContext(),
                            StudentDatabase.class, "studentDb").fallbackToDestructiveMigration().addMigrations().allowMainThreadQueries().build();allowMainThreadQueries()可以在主线程操作,获取数据库实例
                }
            }
        }
        return sInstance;
    }


    public abstract StudentDao studentDao();// 定义获取Dao的方法
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="增加一条记录"
        android:layout_gravity="center" />
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">
        <EditText
            android:id="@+id/et2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="请输入删除的学生姓名"
            android:singleLine="true"/>
        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="删除一条记录" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">
        <EditText
            android:id="@+id/et3_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="请输入更新的学生姓名"
            android:singleLine="true"/>
        <EditText
            android:id="@+id/et3_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="更新后的系别"
            android:singleLine="true"/>
        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="更新一条记录" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center">
    <EditText
        android:id="@+id/et4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="请输入查询的学生姓名"
        android:singleLine="true"/>
    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查询一条记录" />
    </LinearLayout>

    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="增加多条记录"
        android:layout_gravity="center"/>

    <!--<Button
        android:id="@+id/button6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查询所有记录"
        android:layout_gravity="center"/>-->

    <Button
        android:id="@+id/button7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="删除所有记录"
        android:layout_gravity="center"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="20sp"
        android:text="查询一名学生"
        android:textColor="#000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="20sp"
        android:text="学号 姓名 性别 年龄 系别"
        android:textColor="#f00"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/studentoneinfo"
        android:layout_gravity="center"
        android:textSize="20sp"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="20sp"
        android:text="查询全部学生"
        android:textColor="#000"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="20sp"
        android:text="学号 姓名 性别 年龄 系别"
        android:textColor="#f00"/>
    <!--<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/studentinfo"
        android:layout_gravity="center"
        android:textSize="20sp" />
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#000"/>-->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerStudent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
</LinearLayout>

listview_item_bt.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/sno"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/sname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/ssex"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/sage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20sp"/>

    <TextView
        android:id="@+id/sdept"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:textSize="20sp"/>

</LinearLayout>

运行截图汇总

建立一个数据库带一个表的cmd界面
cmd截图
使用数据库迁移建立的表
Course表创建
使用数据库迁移对已经建立的表添加字段
Course表的字段Cavg建立
使用数据库迁移删除数据表
Course表被删除
使用新建类并且改版本号建立的表
建立SC数据表
使用直接修改类成员变量并且改版本号更新的字段
对SC表添加字段,修改字段类型
主活动运行截图:
主活动刚启动
增加一条记录截图:
增加一条记录
增加多条记录截图:
增加多条记录
滑动列表项截图:
滑动列表项
删除一条记录截图:
删除一条记录
更新一条记录截图:
更新一条记录
查询一条记录截图:
查询一条记录

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值