Android架构组件之Room数据库

Room数据库

room数据库是基于SQlite的一个抽象层的数据库操作库
room的使用

首先需要支持Kapt

在app gradle中加入

1
2
3
4
5
6
apply plugin: 'kotlin-kapt'

//使用生成文件
kapt {
generateStubs = true
}

然后导入room的依赖

Romm的主要组件

数据库注解

数据库的三组件

  • @Database
    • 必须是抽象类
    • DataBase注解中包含entity类
    • 包含一个没有行参数的抽象方法对应相应的@Dao
  • @Entity 对应数据库的表
  • @Dao 与数据库交互的具体方法

    DataBase

1
2
3
4
5
6
7
@Database(entities = arrayOf(CommonBean::class,UserLoginBean::class), version = 1,exportSchema = true)
abstract class CrushDatabase : RoomDatabase() {

abstract fun commonDao(): CommonDao
abstract fun userInfoDao():UserInfoDao

}

字段必须定义为public或者提供get或者set方法

Entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity(tableName = "commonbean")
class CommonBean {

@PrimaryKey(autoGenerate = true)
var status: Int = 0

@ColumnInfo(name = "common_data")
var data: String? = null

@ColumnInfo(name = "common_message")
var message: String? = null

@Ignore
var message: String? = null


}
组合主键
1
2
3
4
5
6
7
8
9
10
11
@Entity(primaryKeys = {"status", "data"})
class CommonBean {
var status: Int = 0
var data: String? = null
@ColumnInfo(name = "common_message")
var message: String? = null
@Ignore
var message: String? = null


}
索引
1
2
3
4
5
6
7
8
@Entity(indices = arrayOf(Index(value = "status", unique = true))))
class CommonBean {
var status: Int = 0
var data: String? = null
var message: String? = null
@Ignore
var message: String? = null
}
1
2
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();

定义对象关系

Sql数据库是关系型数据库,但是room数据库不允许指定对象关系,但是可以指定外键

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity(foreignKeys = arrayOf(ForeignKey(entity = ImageTokenBean::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("user_id"),
onDelete = CASCADE)))
public class Book {
@PrimaryKey
public int bookId;

public String title;

@ColumnInfo(name = "user_id")
public int userId;
}

Dao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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);
}
  • Dao既可以是interface也可以是abstrace类,如果是abstrace类,可以指定构造函数为唯一参数

  • 需要注意线程问题,如果没有指定可以在主线程中执行数据库操作的话那么必须在子线程中执行数据库操作

  • convert:一些类型在数据库中是不可识别的,这个会在编译器指出,比如data
    但是通过指定convert类则可以做相应的转换

  • 如果是insert方法的话,那么此方法会返回一个long类型,表明此数据在数据库的位置,如果是插入多个数据的话,那么会返回long[]或者List类型

  • update会根据主键来更新数据update操作可以返回一个int类型,表明在数据库的位置

  • delete操作也可以返回一个int类型的数据,表明从表中删除的位置

查询详细信息

@Query
会在编译时验证

1
2
3
4
5
@Dao
interface MyDao {
@Query("SELECT * FROM user")
var loadAllUsers():User[] ;
}

有些时候你只需要一小部分的数据,那么可以这么查询

1
2
3
4
5
@Dao
interface MyDao {
@Query("SELECT first_name, last_name FROM user")
loadFullName():List<NameTuple>;
}

查询可变的数量

1
2
3
4
5
@Dao
interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
loadUsersFromRegions(List<String> regions):List<NameTuple> ;
}

Room可以返回LiveData和Rxjava的实现

Publisher Flowable LiveData 同时也可以返回Cursor
但是要注意返回Cursor的时候是不知道里面有没有数据的

1
2
3
4
5
@Dao
interface MyDao {
@Query("SELECT * from user where id = :id LIMIT 1")
loadUserById(int id) Flowable<User> ;
}

多表联查

内关联查询

1
2
3
4
5
6
7
8
@Dao
public interface MyDao {
@Query("SELECT * FROM book "
+ "INNER JOIN loan ON loan.book_id = book.id "
+ "INNER JOIN user ON user.id = loan.user_id "
+ "WHERE user.name LIKE :userName")
public List<Book> findBooksBorrowedByNameSync(String userName);
}

别名重新定义

1
2
3
4
5
6
7
8
9
10
11
12
13
@Dao
public interface MyDao {
@Query("SELECT user.name AS userName, pet.name AS petName "
+ "FROM user, pet "
+ "WHERE user.id = pet.user_id")
public LiveData<List<UserPet>> loadUserAndPetNames();


class UserPet {
var userName:String;
var petName:String;
}
}

数据库迁移

room数据库可以写入一个Migration类保留用户信息,它指定了startVersion和endVersion,在运行的时候回执行migrate()方法去迁移数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
+ "`name` TEXT, PRIMARY KEY(`id`))");
}
};

static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Book "
+ " ADD COLUMN pub_year INTEGER");
}
};

进行room迁移测试co

导入room的测试依赖,然后在asset中添加schema作为目录在gradle中加入sourceSet

1
2
3
4
5
6
android {
...
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
}

@TypeConvert

1
2
3
4
5
6
7
8
9
10
11
public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}

@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}

你可以在数据库中使用@TypeConvert注解来做转换
那么就可以在Entity和Dao中使用转换类型的type

1
2
3
4
5
@Entity
public class User {
...
private Date birthday;
}

Room数据库会自动更新Observable

APT是编译时处理注解是javac提供的一个工具,那么Room在运行时候会根据注解来生成相应的类。其实就是Java提供的插件,在编译时候生成代码,然后随和你的代码一起被javac编译。

在有返回LiveData生成的类中自动生成了返回ComputableLiveData方法

其中在compute()方法中有这么一段

1
2
3
4
5
6
7
8
9
if (_observer == null) {
_observer = new Observer("commonbean") {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
invalidate();
}
};
__db.getInvalidationTracker().addWeakObserver(_observer);
}

那么调用invalidate()的时候在主线程中执行了

1
2
3
public void invalidate() {
ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
}

通过层层调用可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (mComputing.compareAndSet(false, true)) {
// as long as it is invalid, keep computing.
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
value = compute();
}
if (computed) {
mLiveData.postValue(value);
}
} finally {
// release compute lock
mComputing.set(false);
}
}

在这里有mLiveData.postValue(value);方法,此方法是通知LiveData的方法

那么room是怎么在插入数据的时候通知其他数据更新的呢?
Room数据库在修改的时候回执行一个方法_de.endTransaction();这个方法

1
__db.endTransaction();

这个方法会处理执行到RoomDatabase里面

1
private final InvalidationTracker mInvalidationTracker;

在Room数据库中有个InvalidationTracker
这个方法存放了所有在之前所有通过查询而获取的实体的Observer
那么如果数据库有更新,
将会把RoomDataBase中的InvalidationTracker都做一遍通知。通知的方法就是onInvalidated()

谢谢您的鼓励~