介绍
安卓开发个各位小伙伴,或多或少的都会用到数据库框架。为了帮助支持各位开发者,google推出了自己的数据库框架Room。
定义
官方介绍:The Room persistence library provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
翻译过来就是,Room持久型类库在SQLite的基础上提供了一个抽象层,方便大家流利的访问数据库。并且,利用了SQlite的全部强力功能。
好吧,说的很流弊。其实,也真的是很流弊。使用起来特别nice。
Demo效果(CURD还是很快的)
主要动作有:
插入一条、
插入多条、
更新记录通过主键index匹配、
查找一条记录通过主键index匹配、
查找所有的记录、
删除一条记录通过主键index匹配、
删除所有记录。
引用
首先我们需要在项目的build.gradlle文件添加google的maven仓库
allprojects {
repositories {
jcenter()
//添加google
google()
}
}
接着在主module的build.gradle文件添加依赖
implementation 'android.arch.persistence.room:runtime:1.0.0'
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
//添加测试支持,我们可以对数据库进行androidTest(后面会介绍)
implementation 'android.arch.persistence.room:testing:1.0.0'
概念
Room设计到的概念有以下几个:
Entity:具体的bean实体,会与数据库表column进行映射
Dao:数据库访问对象,实现具体的增删改查。
RoomDatabase:提供直接访问底层数据库实现,我们应该从里面拿到具体的Dao,进行数据库操作。
了解上面的具体概念,我们就可以进行开发了,下面是具体细节处理。
配置过程
首先我们要创建具体的实体Entity
//entity声明定义,并且指定了映射数据表明
@Entity(tableName = "user")
public class User {
//设置主键,并且定义自增增
@PrimaryKey(autoGenerate = true)
//字段映射具体的数据表字段名
@ColumnInfo(name = "uid")
private int uid;
//字段映射具体的数据表字段名
@ColumnInfo(name = "first_name")
private String firstName;
//字段映射具体的数据表字段名
@ColumnInfo(name = "last_name")
private String lastName;
//必须指定一个构造方法,room框架需要。并且只能指定一个
//,如果有其他构造方法,则其他的构造方法必须添加@Ignore注解
public User() {
}
//其他构造方法要添加@Ignore注解
@Ignore
public User(int uid) {
this.uid = uid;
}
//Setter、Getter方法是需要添加的,为了支持room工作
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
接着我们要创建具体的Dao
//注解配置sql语句
@Dao
public interface UserDao {
//所有的CURD根据primary key进行匹配
//------------------------query------------------------
//简单sql语句,查询user表所有的column
@Query("SELECT * FROM user")
List getAll();
//根据条件查询,方法参数和注解的sql语句参数一一对应
@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);
//同上
@Query("SELECT * FROM user WHERE uid = :uid")
User findByUid(int uid);
//-----------------------insert----------------------
// OnConflictStrategy.REPLACE表示如果已经有数据,那么就覆盖掉
//数据的判断通过主键进行匹配,也就是uid,非整个user对象
//返回Long数据表示,插入条目的主键值(uid)
@Insert(onConflict = OnConflictStrategy.REPLACE)
Long insert(User user);
//返回List数据表示被插入数据的主键uid列表
@Insert(onConflict = OnConflictStrategy.REPLACE)
List insertAll(User... users);
//返回List数据表示被插入数据的主键uid列表
@Insert(onConflict = OnConflictStrategy.REPLACE)
List insertAll(List users);
//---------------------update------------------------
//更新已有数据,根据主键(uid)匹配,而非整个user对象
//返回类型int代表更新的条目数目,而非主键uid的值。
//表示更新了多少条目
@Update()
int update(User user);
//同上
@Update()
int updateAll(User... user);
//同上
@Update()
int updateAll(List user);
//-------------------delete-------------------
//删除user数据,数据的匹配通过主键uid实现。
//返回int数据表示删除了多少条。非主键uid值。
@Delete
int delete(User user);
//同上
@Delete
int deleteAll(List users);
//同上
@Delete
int deleteAll(User... users);
}
然后自定义类继承RoomDatabase
//注解指定了database的表映射实体数据以及版本等信息
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
//RoomDatabase提供直接访问底层数据库实现,我们通过定义抽象方法返回具体Dao
//然后进行数据库增删该查的实现。
public abstract UserDao userDao();
}
最后创建对象并访问数据库
//得到AppDatabase 对象
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "roomDemo-database")
//下面注释表示允许主线程进行数据库操作,但是不推荐这样做。
//他可能造成主线程lock以及anr
//所以我们的操作都是在新线程完成的
// .allowMainThreadQueries()
.build();
//得到userDao对象
mUserDao = db.userDao();
//具体操作,非重点代码,不再展示细节(只展示 insertOne()),下载demo了解整个体系
public void onClick(View view) {
switch (view.getId()) {
case R.id.insert_one:
insertOne();
break;
case R.id.insert_some:
insertSome();
break;
case R.id.update_one:
updataOne();
break;
case R.id.delete_one:
deleteOne();
break;
case R.id.find_one:
findOne();
break;
case R.id.find_all:
findAll();
break;
case R.id.delete_all:
deleteAll();
break;
}
}
//非核心代码
private void insertOne() {
//防止UI线程阻塞以及ANR,所有的数据库操作,推荐开启新的线程访问。
new Thread(new Runnable() {
@Override
public void run() {
//返回的是插入元素的primary key index
Long aLong = mUserDao.insert(new User("t" + System.currentTimeMillis() / 1000, "allen"));
if (aLong > 0) {
String msg = "insert one success, index is " + aLong.toString();
mBuffer.append(msg + "\n");
Log.i(TAG, msg);
} else {
String msg = "insert one fail ";
mBuffer.append(msg + "\n");
Log.i(TAG, msg);
}
MainActivity.this.setMsg();
}
}).start();
}
-------------------------------你已经掌握基本的增删改查--------------------------------
那么,我们还需要掌握数据库的更新。毕竟,我们的数据库表以及结构不是一成不变的。
更新数据库
首先,我们需要提升数据库版本号并定义Migration对象,指明数据库的变动迁移。
//指定version = 2(之前为1)
@Database(entities = {User.class}, version = 2)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
//数据库变动添加Migration
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
//数据库的具体变动,我是在之前的user表中添加了新的column,名字是age。
//类型是integer,不为空,默认值是0
database.execSQL("ALTER TABLE user "
+ " ADD COLUMN age INTEGER NOT NULL DEFAULT 0");
}
};
}
接着,在数据库对象创建时候添加该Migration对象。
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "roomDemo-database")
//添加数据库变动迁移
.addMigrations(AppDatabase.MIGRATION_1_2)
//下面注释表示允许主线程进行数据库操作,但是不推荐这样做。
//他可能造成主线程lock以及anr
// .allowMainThreadQueries()
.build();
-----------------------------你已经完成了数据库的迁移-----------------------------
任何版本的数据库sql以及迁移,我们都需要记录下来。这样,我们数据库就有了一个完整的历史体系。后续也容易修改和维护。怎么记录呢?电子笔记?Out啦!
记录数据库sql
在主module的build.gradle里面添加如下代码
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
//room的数据库概要、记录
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
sourceSets {
//数据库概要、记录存放位置
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
配置完毕之后,我们在构建项目之后会在对应路径生成schemas文件夹。
1.json和2.json分别是数据库版本1、2对应的概要、记录(json文件格式存储)
1.json文件数据如下:
{
"formatVersion": 1,
"database": {
"version": 1,
//身份哈希
"identityHash": "7fcc959eb0e5d9c2cd52cf58f7a05392",
"entities": [
{
"tableName": "user",
//建表语句
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_name` TEXT, `last_name` TEXT)",
"fields": [
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "firstName",
"columnName": "first_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "lastName",
"columnName": "last_name",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"uid"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7fcc959eb0e5d9c2cd52cf58f7a05392\")"
]
}
}
----------------你已经成功拿到了数据库各个版本的概要、记录数据------------------
接下来,我们应该进行数据库的测试,保证我们程序的稳健。写入如下:
@RunWith(AndroidJUnit4.class)
public class MigrationTest {
private static final String TEST_DB = "migration-test";
@Rule
public MigrationTestHelper helper;
public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
AppDatabase.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}
@Test
public void migrate1To2() throws IOException {
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
//你可以通过sql语句写入一些测试数据(旧版本数据库)
// db.execSQL(...);
// 关闭数据库,为了打开新的数据库
// db.close();
//打开新的数据库测试迁移等
db = helper.runMigrationsAndValidate(TEST_DB, 2, true, AppDatabase.MIGRATION_1_2);
}
}
调试migrate1To2()方法,即可测试数据库操作了。
----------------------------------完成数据库测试--------------------------------------
后续,我会抽时间加上数据表的多表查询、RxJAVA支持等。。。
总结
Room是google官方推出的一个好用的数据库框架。文章带领大家了解思路以及快速上手。
下面是Demo地址,方便大家测试学习~
地址:https://github.com/HoldMyOwn/RoomDemo.git
注:github留下了两个commit(分别对应数据库版本1和2)。大家可以通过先跑数据库版本1代码,拿到版本1数据库。然后,跑数据库版本2代码,体验数据库版本迁移。