Room是在Sqlite数据的一个抽象层,拥有更强大的数据访问能力。
导入依赖:1
2
3
4
5
6
7
8
9
10def room_version = "2.1.0-alpha06"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// kotlin扩展和协程支持
implementation "androidx.room:room-ktx:$room_version"
//RxJava 支持库
implementation "androidx.room:room-rxjava2:$room_version"
// 可选 - Guava 的支持库
implementation "androidx.room:room-guava:$room_version"
下面开始使用
第一步创建实体类
假如我们有一个用户表,每个用户的实体就是表中的一列
1 | @Entity |
- @Entity: 代表一个表中的实体,默认类名就是表名,如果不想使用类名作为表名,可以给注解添加表名字段
@Entity(tableName = "user_table")
- @PrimaryKey: 每个实体都需要自己的主键
- @NonNull 表示字段,方法,参数返回值不能为空
- @ColumnInfo(name = “lastname”) 如果希望表中字段名跟类中的成员变量名不同,添加此字段指明
第二步创建DAO
- DAO是数据访问对象,指定SQL查询,并让他与方法调用相关联。
- DAO必须是一个接口或者抽象类。
- 默认情况下,所有的查询都必须在单独的线程中执行
1 | @Dao |
- 创建一个接口UserDao
- 给它添加注解@Dao,表名它是Room的一个查询类
- 声明一个插入用户的方法insert,并给它添加注解@Insert,不用提供任何SQL语句
- 声明一个删除全部的方法,deleteAll(),删除方法没有便捷方法,需要使用@Query注解,并且提供相应的SQL语句
delete from user
- 声明一个getUserList方法来查询所有的用户,这个也没有便捷方法,,需要使用@Query注解,并且提供相应的SQL语句
select * from user
第三步添加Database
Room是SQLite数据库之上的数据库层,可以让我们轻松的使用系统原始API:SQLiteOpenHelper1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@Database(entities = {User.class},version = 1)
public abstract class UserRoomDatabase extends RoomDatabase {
public abstract UserDao userDao();
public static UserRoomDatabase instance;
public static UserRoomDatabase getInstance(Context context){
if(instance == null){
synchronized (UserRoomDatabase.class){
if(instance == null){
instance = Room.databaseBuilder(context.getApplicationContext(),UserRoomDatabase.class
,"user_database").build();
}
}
}
return instance;
}
}
- 创建一个抽象类继承自RoomDatabase
- 给他添加一个注解
@Database
表名它是一个数据库,注解有两个参数第一个是数据库的实体,它是一个数组,可以传多个,当数据库创建的时候,会默认给创建好对应的表,第二个参数是数据库的版本号 - 定义跟数据库一起使用的相关的DAO类
- 创建一个UserRoomDatabase的单例,防止同时打开多个数据库的实例
- 使用Room提供的数据库构建器来创建该实例,第一个参数application,第二个参数当前数据库的实体类,第三个参数数据库的名字
第四步开始使用
前面三步主要步骤写完了,现在就可以开始使用了,使用的时候,为了让Activity中代码简洁,创建一个UserRepository类来管理这个数据库1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87public class UserRepository {
private UserDao mUserDao;
private List<User> allUser;
public UserRepository(Application application) {
//UserRoomDatabase db = UserRoomDatabase.getInstance(application);
//mUserDao = db.userDao();
//allUser = mUserDao.getUserList();
//使用ViweModel可以直接使用上面注释了的
new InitThread(application).start();
}
public List<User> getAllUser() {
return allUser;
}
public void deleteAll(){
new DeleteAsyncTask(mUserDao).execute();
}
public void update(User user){
new UpdateAsyncTask(mUserDao).execute(user);
}
public void insert(User user){
new InsertAsyncTask(mUserDao).execute(user);
}
private class InitThread extends Thread{
Application application;
InitThread(Application application){
this.application = application;
}
@Override
public void run() {
UserRoomDatabase db = UserRoomDatabase.getInstance(application);
mUserDao = db.userDao();
allUser = mUserDao.getUserList();
}
}
//更新
private static class UpdateAsyncTask extends AsyncTask<User, Void, Void> {
private UserDao mAsyncTaskDao;
UpdateAsyncTask(UserDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(final User... params) {
mAsyncTaskDao.updateUsers(params[0]);
return null;
}
}
//插入
private static class InsertAsyncTask extends AsyncTask<User, Void, Void> {
private UserDao mAsyncTaskDao;
InsertAsyncTask(UserDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(final User... params) {
mAsyncTaskDao.insert(params[0]);
return null;
}
}
//删除
private static class DeleteAsyncTask extends AsyncTask<Void, Void, Void> {
private UserDao mAsyncTaskDao;
DeleteAsyncTask(UserDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(Void... voids) {
mAsyncTaskDao.deleteAll();
return null;
}
}
}
这个类的作用就是初始化数据库和响应的DAO类,对外提供插入、查询等方法。
注意:数据库的创建,表的插入和删除操作,Room会强制要求在非UI线程中使用,否则会崩溃。
在Activity中初始化UserRepository之后,就可以进行相关的操作了
使用LiveData和ViewModel
当数据变化的时候,LiveData可以观察到数据的变化,可以让我们实时更新UI
ViewModel可以更好的保存Activity中的数据,比如屏幕旋转的时候数据不会丢失
ViewModel与Room和LiveData一起工作可以替换以前的loader。ViewModel确保数据在设备配置更改后仍然存在。当数据库发生更改时,Room会通LiveData,而LiveData反过来又用修改后的数据更的UI。
将DAO中的查询更改为下面
1 | @Query("select * from user") |
然后创建ViewModel1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class UserViewModel extends AndroidViewModel {
private LiveData<List<User>> mUsers;
private UserRepository mRepository;
public UserViewModel(Application application) {
super(application);
mRepository = new UserRepository(application);
mUsers = mRepository.getAllUser();
}
public LiveData<List<User>> getUsers(){
return mUsers;
}
public void insertUser(User user){
mRepository.insert(user);
}
public void deleteAll(){
mRepository.deleteAll();
}
public void update(User user){
mRepository.update(user);
}
}
我们单独使用LiveData的时候,都是使用MutableLiveData,当与Room一块使用的时候只能使用LiveData。
Activity中使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61public class RoomActivity extends AppCompatActivity {
List<User> mUsers = new ArrayList<>();
UserRepository mRepository;
MyAdapter mAdapter;
int index = 0;
private UserViewModel mViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_room);
// mRepository = new UserRepository(getApplication());
// mUsers = new ArrayList<>();
RecyclerView recyclerView = findViewById(R.id.recycleview);
LinearLayoutManager manager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(manager);
mAdapter = new MyAdapter(mUsers,this);
recyclerView.setAdapter(mAdapter);
mViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
mViewModel.getUsers().observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
mUsers.clear();
mUsers.addAll(users);
mAdapter.notifyDataSetChanged();
}
});
}
public void insert(View view) {
User user = new User();
user.id =index;
user.name = "张三"+ Math.random()*100;
user.school = "北大"+ Math.random()*100;
user.password = "123"+ Math.random()*100;
// mRepository.insert(user);
mViewModel.insertUser(user);
index++;
}
public void query(View view) {
// List<User> allUser = mRepository.getAllUser();
// mUsers.addAll(allUser);
// mAdapter.notifyDataSetChanged();
}
public void deleteAll(View view) {
mViewModel.deleteAll();
}
public void update(View view) {
User user = new User();
user.id =0;
user.name = "张三"+ Math.random()*100;
user.school = "北大"+ Math.random()*100;
user.password = "123"+ Math.random()*100;
mViewModel.update(user);
}
}
数据库升级
当数据库中的表或者表中的字段有变化的时候,我们需要升级数据的版本,这个时候,我们不希望现在数据库中的数据丢失
Room提供了相应的类(Migration)来完成数据库的迁移,需要传入一个旧版本和新版本,比如现在在user表中新加一个age字段
- 在User类中新加一个字段
1 | public int age; |
- 编写Migration类,编写sql更改数据库
1 | private static final Migration MIGRATION_1_2 = new Migration(1, 2) { |
- 更改数据库的版本由1变成2
1 | @Database(entities = {User.class},version = 2,exportSchema = false) |
- 更改数据库的创建方法
1 | instance = Room.databaseBuilder(context.getApplicationContext(),UserRoomDatabase.class |
然后重新运行程序就可以看到age字段已经加到表里了。
addMigrations方法,里面可以接收多个参数,比如现在只是编写了版本1-2的升级方法MIGRATION_1_2,假如我们还有2-3版本的还可以编写一个MIGRATION_2_3,添加到后面。
OK,到这里Room的简单使用就完成啦。下面来看看它的源码吧
前面我们知道数据库的创建是从Room.databaseBuilder(...).build();
方法开始,很明显看出来这是通过建造者模式创建出来的。传入一些参数到RoomDatabase.Builder中,最终的创建方法肯定就是在build中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 public T build() {
......
if (mFactory == null) {
mFactory = new FrameworkSQLiteOpenHelperFactory();
}
//数据库配置
DatabaseConfiguration configuration =
new DatabaseConfiguration(
mContext,
mName,
mFactory,
mMigrationContainer,
mCallbacks,
mAllowMainThreadQueries,
mJournalMode.resolve(mContext),
mQueryExecutor,
mTransactionExecutor,
mMultiInstanceInvalidation,
mRequireMigration,
mAllowDestructiveMigrationOnDowngrade,
mMigrationsNotRequiredFrom);
//前面创建的UserRoomDatabase是个抽象类,编译期间会生成对应的实现类UserRoomDatabase_Impl,获取UserRoomDatabase的实现类
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
//初始化数据库
db.init(configuration);
return db;
}
}
创建了一个SQLiteOpenHelper的工厂类FrameworkSQLiteOpenHelperFactory1
2
3
4
5
6
7public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
@Override
public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
return new FrameworkSQLiteOpenHelper(
configuration.context, configuration.name, configuration.callback);
}
}
这个工厂方法可以创建一个FrameworkSQLiteOpenHelper,它是SupportSQLiteOpenHelper接口的实现类。
1 | class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper { |
可以看到在其构造方法中创建了一个代理类OpenHelper1
static class OpenHelper extends SQLiteOpenHelper {......}
OpenHelper继承自系统的SQLiteOpenHelper,它用来监听数据库的创建(onCreate)升级(onUpgrade)等操作。然后回调给RoomOpenHelper来处理。
回到build()方法中,将数据库的配置封装到DatabaseConfiguration中,然后获取我们之前写的抽象类UserRoomDatabase的一个实现类,这个实现类是注解器在编译期间自动创建的。
位置在:build->generated->source->apt->debug->你的包名中找到。最后初始化数据库。怎么创建的可以去查一下编译时注解的原理。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
//包名
final String fullPackage = klass.getPackage().getName();
//全名
String name = klass.getCanonicalName();
final String postPackageName = fullPackage.isEmpty()
? name
: (name.substring(fullPackage.length() + 1));
//拼成UserRoomDatabase_Impl
final String implName = postPackageName.replace('.', '_') + suffix;
//noinspection TryWithIdenticalCatches
try {
//通过反射找到生成的类,然后实例化
@SuppressWarnings("unchecked")
final Class<T> aClass = (Class<T>) Class.forName(
fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
return aClass.newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException("cannot find implementation for "
+ klass.getCanonicalName() + ". " + implName + " does not exist");
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access the constructor"
+ klass.getCanonicalName());
} catch (InstantiationException e) {
throw new RuntimeException("Failed to create an instance of "
+ klass.getCanonicalName());
}
}
上面的方法就是根据我们传入的数据库的类名,拼接出Room编译器给自动生成的实现类的名字,然后通过反射找到这个类并实例化返回。
下面看一下这个自动生成的类UserRoomDatabase_Impl1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112public final class UserRoomDatabase_Impl extends UserRoomDatabase {
private volatile UserDao _userDao;
@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(2) {
@Override
public void createAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`id_` INTEGER NOT NULL, `name` TEXT, `password` TEXT, `school` TEXT, `age` INTEGER NOT NULL, PRIMARY KEY(`id_`))");
_db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
_db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"c59ead1e532b11ea2062c3f5e814a66b\")");
}
@Override
public void dropAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("DROP TABLE IF EXISTS `User`");
}
@Override
protected void onCreate(SupportSQLiteDatabase _db) {
if (mCallbacks != null) {
for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
mCallbacks.get(_i).onCreate(_db);
}
}
}
@Override
public void onOpen(SupportSQLiteDatabase _db) {
mDatabase = _db;
internalInitInvalidationTracker(_db);
if (mCallbacks != null) {
for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
mCallbacks.get(_i).onOpen(_db);
}
}
}
@Override
public void onPreMigrate(SupportSQLiteDatabase _db) {
DBUtil.dropFtsSyncTriggers(_db);
}
@Override
public void onPostMigrate(SupportSQLiteDatabase _db) {
}
@Override
protected void validateMigration(SupportSQLiteDatabase _db) {
final HashMap<String, TableInfo.Column> _columnsUser = new HashMap<String, TableInfo.Column>(5);
_columnsUser.put("id_", new TableInfo.Column("id_", "INTEGER", true, 1));
_columnsUser.put("name", new TableInfo.Column("name", "TEXT", false, 0));
_columnsUser.put("password", new TableInfo.Column("password", "TEXT", false, 0));
_columnsUser.put("school", new TableInfo.Column("school", "TEXT", false, 0));
_columnsUser.put("age", new TableInfo.Column("age", "INTEGER", true, 0));
final HashSet<TableInfo.ForeignKey> _foreignKeysUser = new HashSet<TableInfo.ForeignKey>(0);
final HashSet<TableInfo.Index> _indicesUser = new HashSet<TableInfo.Index>(0);
final TableInfo _infoUser = new TableInfo("User", _columnsUser, _foreignKeysUser, _indicesUser);
final TableInfo _existingUser = TableInfo.read(_db, "User");
if (! _infoUser.equals(_existingUser)) {
throw new IllegalStateException("Migration didn't properly handle User(com.chs.androiddailytext.jetpack.User).\n"
+ " Expected:\n" + _infoUser + "\n"
+ " Found:\n" + _existingUser);
}
}
}, "c59ead1e532b11ea2062c3f5e814a66b", "c0809d515e95fc9eec52ad8880ec6aae");
final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
.name(configuration.name)
.callback(_openCallback)
.build();
final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
return _helper;
}
@Override
protected InvalidationTracker createInvalidationTracker() {
final HashMap<String, String> _shadowTablesMap = new HashMap<String, String>(0);
HashMap<String, Set<String>> _viewTables = new HashMap<String, Set<String>>(0);
return new InvalidationTracker(this, _shadowTablesMap, _viewTables, "User");
}
@Override
public void clearAllTables() {
super.assertNotMainThread();
final SupportSQLiteDatabase _db = super.getOpenHelper().getWritableDatabase();
try {
super.beginTransaction();
_db.execSQL("DELETE FROM `User`");
super.setTransactionSuccessful();
} finally {
super.endTransaction();
_db.query("PRAGMA wal_checkpoint(FULL)").close();
if (!_db.inTransaction()) {
_db.execSQL("VACUUM");
}
}
}
@Override
public UserDao userDao() {
if (_userDao != null) {
return _userDao;
} else {
synchronized(this) {
if(_userDao == null) {
_userDao = new UserDao_Impl(this);
}
return _userDao;
}
}
}
}
- createOpenHelper方法,创建SupportSQLiteOpenHelper的实现类FrameworkSQLiteOpenHelper,前面我们知道他的构造方法中创建了一个代理类OpenHelper它继承自系统的SQLiteOpenHelper,这个就是我们如果不使用Room,而是自己使用系统提供的类操作数据库的时候需要创建的类,这里Room帮我们创建好了,这个方法是在前面build()方法中的 db.init(configuration)中调用的
- createOpenHelper方法中创建了SupportSQLiteOpenHelper.Callback这个回调,并实现它的回调方法从代码中看到它是一个RoomOpenHelper。
- OpenHelper监听系统回调,监听到之后会回调RoomOpenHelper的相关方法,RoomOpenHelper又会回调UserRoomDatabase_Impl中的相关方法。
- onCreate()中Sql语句创建user表和room_master_table表,dropAllTables方法删除数据库,validateMigration方法实现数据库升级
- userDao()方法创建UserDao_Impl的实例,这个UserDao_Impl也是注解处理器给我们自动生成的,这里面就是我们定义的UserDao中增删改查的真正实现的地方。
然后在看build()方法中的init方法
1 | public void init(@NonNull DatabaseConfiguration configuration) { |
- createOpenHelper(configuration)就是调用了UserRoomDatabase_Impl中的createOpenHelper方法。
- mQueryExecutor 和 mTransactionExecutor 是两个线程池,查询和事物的线程池。这就是为啥前面例子中我们的查询方法不用自己放到非UI线程中执行,而插入和更新方法却需要自己创建子线程执行了。
下面来看看升级的方法,前面我们知道OpenHelper这个代理类继承了系统的SQLiteOpenHelper,会监听系统的数据库相关事件,我们找到它中的升级方法1
2
3
4public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
mMigrated = true;
mCallback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
}
mCallback就是我们在UserRoomDatabase_Impl中创建的RoomOpenHelper一路传进来的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
boolean migrated = false;
if (mConfiguration != null) {
//根据版本号查找对应的migrations
List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
oldVersion, newVersion);
if (migrations != null) {
//迁移之前的初始化工作
mDelegate.onPreMigrate(db);
//循环执行我们写的sql
for (Migration migration : migrations) {
migration.migrate(db);
}
//验证升级结果
mDelegate.validateMigration(db);
mDelegate.onPostMigrate(db);
updateIdentity(db);
migrated = true;
}
}
//如果没有执行我们的sql
if (!migrated) {
if (mConfiguration != null
&& !mConfiguration.isMigrationRequired(oldVersion, newVersion)) {
//删除清空表
mDelegate.dropAllTables(db);
mDelegate.createAllTables(db);
} else {
throw new IllegalStateException("A migration from " + oldVersion + " to "
+ newVersion + " was required but not found. Please provide the "
+ "necessary Migration path via "
+ "RoomDatabase.Builder.addMigration(Migration ...) or allow for "
+ "destructive migrations via one of the "
+ "RoomDatabase.Builder.fallbackToDestructiveMigration* methods.");
}
}
}
这里面的mDelegate就是我们在UserRoomDatabase_Impl中new出来的RoomOpenHelper.Delegate,并实现了它里面的方法,所以上面代码中调用Delegate最终都会到达UserRoomDatabase_Impl中的相关方法执行。
findMigrationPath方法根据版本号找到相应的migrations,前面使用中我们知道migrations中封装了我们升级数据库的sql语句。
循环执行我们的migrations,执行我们写的升级的sql语句,执行完之后验证是否升级成功。
如果没有执行找到需要执行的migrations ,并且mConfiguration.isMigrationRequired(oldVersion, newVersion)为false,就会清空数据库中所有的表。1
2
3
4
5
6
7public boolean isMigrationRequired(int fromVersion, int toVersion) {
final boolean isDowngrade = fromVersion > toVersion;
if (isDowngrade && allowDestructiveMigrationOnDowngrade) {
return false;
}
......
}
可以看到当旧版本大于新版本的时候或者allowDestructiveMigrationOnDowngrade为true的时候返回false。
allowDestructiveMigrationOnDowngrade这个标志位可以在数据库创建的时候指定1
2
3
4Room.databaseBuilder(context.getApplicationContext(),UserRoomDatabase.class
,"user_database")
.fallbackToDestructiveMigration()
.build();
加上它之后升级就会清空数据库中以前的数据。一般情况下我们都是希望保留数据的,所以需要些我们自己的Migration类,定义升级的sql。
OK结束