Room 由于原生的 SQLite 需要编写大量的 SQL 语句和处理逻辑,因此涌现出了许多 ORM 框架,比如 GreenDAO、ORMLite 以及 Litepal 等。而 Jetpack 也提供了 Room 组件,并且 Room 融合了 LiveData 等组件。
Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 强大功能的同时,能够流畅地访问数据库。
ORM(Object Relational Mapping)也叫对象关系映射。简单地讲,我们使用的编程语言是面向对象语言,而使用的数据库则是关系型数据库,将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是 ORM 了。
使用 ORM 框架的好处是可以用面向对象的思维来和数据库进行交互,绝大多数情况下不用再和 SQL 语句打交道了,同时也不用担心操作数据库的逻辑会让项目的整体代码变的混乱。
使用 Room 进行增删改查 Room 的基本架构图:
Room 的整体结构。它主要由 Entity、Dao 和 Database 三部分组成:
Entity 表示数据库中对应的表,用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并且表中的列是根据实体类中的字段自动生成的。
Dao 是数据访问对象的意思,提供了访问数据库的方法,通常会在这里对数据库的各项操作进行封装,在实际编程的时候,逻辑层就不需要和底层数据库打交道了,直接和 Dao 层进行交互即可。
Database 是数据库持有者,用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提供 Dao 层的访问实例。
对 App 而言,他要使用 Database 获取对应数据库的访问对象 DAO,然后使用 DAO 对 Entity 进行存储操作。
首先需要添加插件和依赖:
1 2 3 4 5 6 7 8 9 apply plugin: 'kotlin-kapt' dependencies { implementation "androidx.room:room-runtime:2.2.5" kapt "androidx.room:room-compiler:2.2.5" }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Entity data class User (var firstName:String,var lastName:String,var age:Int ){ @PrimaryKey(autoGenerate = true) var id: Long = 0 }
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 @Dao interface UserDao { @Insert fun insertUser (user: User ) :Long @Update fun updateUser (newUser:User ) @Query("select * from User" ) fun loadAllUsers () :List<User> @Query("select * from User where age>:age" ) fun loadUsersOlderThan (age: Int ) :List<User> @Delete fun deleteUser (user:User ) @Query("delete from User where lastName = :lastName" ) fun deleteUserByLastName (lastName:String ) :Int }
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 @Database(version = 1,entities = [User::class]) abstract class AppDatabase :RoomDatabase (){ abstract fun userDao () :UserDao companion object { private var instance:AppDatabase? =null @Synchronized fun getDatabase (context: Context ) :AppDatabase{ instance?.let { return it } return Room.databaseBuilder(context.applicationContext, AppDatabase::class .java,"app_database" ).build().apply{ instance = this } } } }
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 class MainActivity : AppCompatActivity () { override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) val userDao = AppDatabase.getDatabase(this ).userDao() var user1 = User("Tom" ,"Brady" ,40 ) var user2 = User("Tom" ,"Hanks" ,63 ) addDataBtn.setOnClickListener{ thread { user1.id = userDao.insertUser(user1) user2.id = userDao.insertUser(user2) } } updateDataBtn.setOnClickListener{ thread { user1.age = 42 userDao.updateUser(user1) } } deleteDateBtn.setOnClickListener{ thread { userDao.deleteUserByLastName("Hanks" ) } } queryDateBtn.setOnClickListener{ thread { for (user in userDao.loadAllUsers()){ Log.e("MainActivity" ,user.toString()) } } } } }
Room 的数据库升级 测试阶段使用的方法:
1 2 3 4 5 6 Room.databaseBuilder(context.applicationContext, AppDatabase::class .java,"app_database" ) .fallbackToDestructiveMigration() .build()
正规写法:
1 2 3 4 5 6 @Entity data class Book (var name:String,var pages:Int ) { @PrimaryKey(autoGenerate = true) var id : Long = 0 }
1 2 3 4 5 6 7 8 9 @Dao interface BookDao { @Insert fun insertBook (book: Book ) :Long @Query("select * from Book" ) fun loadAllBooks () :List<Book> }
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 @Database(version = 2,entities = [User::class,Book::class]) abstract class AppDatabase :RoomDatabase (){ abstract fun userDao () :UserDao abstract fun bookDao () :BookDao companion object { val MIGRATION_1_2 = object : Migration(1 ,2 ){ override fun migrate (database: SupportSQLiteDatabase ) { database.execSQL("create table Book (id integer primary key autoincrement not null, name text not null,pages integer not null)" ) } } private var instance:AppDatabase? =null @Synchronized fun getDatabase (context: Context ) :AppDatabase{ instance?.let { return it } return Room.databaseBuilder(context.applicationContext, AppDatabase::class .java,"app_database" ) .fallbackToDestructiveMigration() .addMigrations(MIGRATION_1_2) .build() .apply{ instance = this } } } }
如果只是向现有的表中添加新的列,那只需要使用 alert 语句修改表结构就可以。
1 2 3 4 5 6 @Entity data class Book (var name:String,var pages:Int ,var author:String) { @PrimaryKey(autoGenerate = true) var id : Long = 0 }
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 @Database(version = 3,entities = [User::class,Book::class]) abstract class AppDatabase :RoomDatabase (){ ... companion object { ... val MIGRATION_2_3 = object : Migration(2 ,3 ){ override fun migrate (database: SupportSQLiteDatabase ) { database.execSQL("alter table Book add column text not null default 'unknown'" ) } } private var instance:AppDatabase? =null @Synchronized fun getDatabase (context: Context ) :AppDatabase{ ... return Room.databaseBuilder(context.applicationContext, AppDatabase::class .java,"app_database" ) .fallbackToDestructiveMigration() .addMigrations(MIGRATION_1_2, MIGRATION_2_3) .build().apply{ instance = this } } } }
原理解析 从 DataBase 的创建来看,程序通过 Room.databaseBuilder ().build 创建了 database 对象,build 方法:
RoomDatabase.java 1 2 3 4 5 6 7 8 9 10 @SuppressLint("RestrictedApi") @NonNull public T build () { ... DatabaseConfiguration configuration = new DatabaseConfiguration (...); T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX); db.init(configuration); return db; }
先查看 getGeneratedImplementation 方法:
getGeneratedImplementation 方法通过反射方法为 AccountDataBase 创建了实例,命名规则为 类型 +”_Impl”,也就是 AccountDataBase_Impl 类。
Room.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @NonNull @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public static <T, C> T getGeneratedImplementation (@NonNull Class<C> klass, @NonNull String suffix) { final String fullPackage = klass.getPackage().getName(); String name = klass.getCanonicalName(); final String postPackageName = fullPackage.isEmpty() ? name : name.substring(fullPackage.length() + 1 ); final String implName = postPackageName.replace('.' , '_' ) + suffix; try { final String fullClassName = fullPackage.isEmpty() ? implName : fullPackage + "." + implName; @SuppressWarnings("unchecked") final Class<T> aClass = (Class<T>) Class.forName( fullClassName, true , klass.getClassLoader()); return aClass.newInstance(); } ... }
接下来查看 db.init (configuration),他会调用 createOpenHelper 方法,查看 AccountDataBase_Impl 实现类中的 createOpenHelper 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public final class AccountDatabase_Impl extends AccountDatabase { private volatile AccountDao _accountDao; @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 `Account` (`accountId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `_loginAccount` TEXT NOT NULL, `_loginPassword` TEXT NOT NULL, `_loginIpAddress` TEXT NOT NULL)" ); _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, '14a6d25a90b176163fb0e4b68c523836')" ); } ... } ... }
createOpenHelper 方法通过 SupportSQLiteOpenHelper 类实现了创建数据表、删除数据表等方法。数据插入等操作采用的是同样的实现方式。
其他 1、可能会遇到的问题:
build.gradle(:app) 1 2 3 4 5 6 7 8 9 10 11 12 13 android { ... defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments += ["room.schemaLocation" : "$projectDir/schemas" .toString()] } } } ... }
2、修改数据库版本号后报错:
1 java.lang.IllegalStateException: A migration from 1 to 2 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.
解决方法 1:设置 fallbackToDestructiveMigration ()(不推荐)
解决方法 2:使用 Migration 升级策略
3、AndroidStudio 4.1 及更高版本为开发者提供了 DataBase Inspector 工具 ,用来直接查看生成的数据库文件。运行程序后(有线连接)从菜单栏中依次选择 View > Tool Windows > App Inspection -> 选择 Database Inspector 标签页 -> 从下拉菜单中选择正在运行的应用进程就可以看到生成的数据库文件了。
备注 参考资料 :
Room
第一行代码 (第 3 版)
《Android Jetpack 开发 原理解析与应用实战》
相关文章 :
Android 账号登录
欢迎关注微信公众号:非也缘也
预览: