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"
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是主键且自增。
DAO:包含用于访问数据库的方法。
前面用Entity创建好表之后,就可以为其定义一些增删改查的操作了。这将在DAO里实现。
首先我们定义一个接口StudentDao,并在public interface StudentDao {
前添加好注释@Dao
。这样这个接口就可以用来实现访问数据库方法了。
在这个接口里定义的方法中
@Insert
。@Update
。Student... students
形式,这样就可以根据需要传任意数量的变量了。方法返回值为void即可,也可以使此方法返回int型值,以指示数据库中更新的行数。@Delete
。@Query(查询的SQL语句)
,比如加了@Query("SELECT * FROM student")
注释的方法将被视为查询表中的所有字段。@Query(SQL语句)
注释实现删除等功能,比如被加了@Query("DELETE FROM student")
注释的方法也能实现删除全部数据。注意在查询时如果出现了问题,可能会导致警告和直接闪退的情况。其中@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个方法,使用它们将可以实现一些增删改查的操作。
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)
。其中,
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的数据库配置:
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
getAll(); @Query("SELECT * FROM user WHERE uid IN (:userIds)") List 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);//插入一个数据,令数据库被使用起来
}
}
添加完如上代码,就可以启动程序建立数据库了。
在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数据库自带的两个表,我们可以不用管它们。
这样查看数据库的内容就介绍完了。
建立数据库的内容已经在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
数据库的迁移的概念:
在应用程序中添加和更改特性时,你需要修改实体类以反映这些更改。当用户更新应用程序到最新版本时,不希望它们丢失所有现有数据,尤其是如果无法从远程服务器恢复数据。“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表示将要升级到的版本。
@Database(entities = {Student.class}, version = 1, exportSchema = false)
改为@Database(entities = {Student.class}, version = 2, exportSchema = false)
如果从旧版本到新版本的迁移规则无法找到,就会触发错误,如果要避免该错误,可以使用fallbackToDestructiveMigration,这样就可以告诉Room,在找不到迁移规则时,可以破坏性重建数据库,注意这会删除所有数据库表数据。
因此,这个方法有一个对原有数据库表破坏重建的操作,但可以避免一些错误。这个方法不强制使用,你可以根据情况添加它。
经过了三处修改后(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表被成功建立。
现在我们的数据库版本已经是version=2了。下面的操作将数据库迁移到版本3,实现的是为Course表增加一个字段Cavg。
@Database(entities = {Student.class}, version = 2, exportSchema = false)
改为@Database(entities = {Student.class}, version = 3, exportSchema = false)
经过了三处修改后(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被成功建立。
注:你可能在这里会有一个问题,我可不可以不用再创建一个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。
现在我们的数据库版本已经是version=3了。下面的操作将数据库迁移到版本4,实现的是删除Course表。
@Database(entities = {Student.class}, version = 3, exportSchema = false)
改为@Database(entities = {Student.class}, version = 4, exportSchema = false)
经过了三处修改后(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的方法
}
经过了之前的讲解,关于数据表的建立我想你已经掌握了吧?这里总结一下,用ROOM的三大组件(Database、Entity、DAO) 就可以将一个数据表与数据库绑定,其中Entity定义了数据表的表名和属性,DAO定义了数据表会用到的操作。再Room.databaseBuilder(…).build();时建立数据库,与它绑定的数据表也将随之建立。在上一节讲升级数据库时涉及到用迁移数据库的方法结合SQL语句改变数据表,接下来的内容就要描述Room在改变数据表方面的优势了。内容也将涉及到升级数据库。
注:由于之前用数据库迁移使数据库版本升到了4,现在我们让它还原成版本1时的代码,再运行一次,以防止在接下来通过升级数据库在数据库里增加表时遇到问题。
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;
}
}
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语言,使操作非常简单。
升级数据表也同样地简单。
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的方法
}
删除操作可能没有那么顺利,如果改了StudentDatabase的注释 ,在entities里删除属性值SC.class,version=3改为version = 4,删除了SC.java类,发现该表仍存在的话,还可以选择上面的借助SQL语句用数据库迁移的方法删除表。(见数据库迁移:实现删除数据表(版本3——>版本4))
至此,Room的相关基础知识已经全部涉及到了。由于之前我们一直在研究建库建表的问题,都没有提及活动的事情。还记得StudentDao吗?接下来我们要做的就是在主活动里使用它来为数据表添加数据,然后再在APP界面里显示出数据来啦。
讲了那么多Room的内容,终于要提到开头中的LiveData+ViewModel+RecyclerView了。由于这里的重点是Room,这三者不做重点描述,未学到这些知识的朋友可以去查一查它们的用法。
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>
布局文件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是可观察的查询
当执行查询时,您经常希望应用程序的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类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据,ViewModel中数据会一直存活即使 activity configuration发生变化,比如横竖屏切换的时候。
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>的用法,相信你阅读下面的代码就可以猜到。获取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;
......
}
}
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界面
使用数据库迁移建立的表
使用数据库迁移对已经建立的表添加字段
使用数据库迁移删除数据表
使用新建类并且改版本号建立的表
使用直接修改类成员变量并且改版本号更新的字段
主活动运行截图:
增加一条记录截图:
增加多条记录截图:
滑动列表项截图:
删除一条记录截图:
更新一条记录截图:
查询一条记录截图: