关注我的公众号 “安安安安卓” 免费学知识
git 地址:
https://github.com/ananananzhuo-blog/RoomDemo.git
Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。
@Database 注解的类必须是扩展 RoomDatabase 的抽象类;
包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法;
可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 的实例;
@Entity
表示数据库中的表。
@Dao
包含用于访问数据库的方法
实际上两者注释中已经写得很不错了
inMemoryDatabaseBuilder 这种方式创建的数据库只能存在于内存中,一旦应用关闭数据就会被清空
我给你证明一下:
分别使用inMemoryDatabaseBuilder 和 databaseBuilder两种方式获取数据库db对象,然后分别在两个操作页面插入数据并查看数据插入成功与否
然后关闭应用再次打开,使用 databaseBuilder方式获取的数据库对象依然可以查询到数据,使用inMemoryDatabaseBuilder方式获取到的数据库对象不可以获取到数据,这证明了注释中说的是对的,也与我们前面说的结论呼应
本例我们定义一个 Goods 的商品,商品包含自增 id 和商品名称,
然后实现对商品的增加、查询、删除等功能。主要展示商品增加和查询
dependencies {
def room_version = "2.3.0"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor "androidx.room:room-compiler:$room_version"
// To use Kotlin annotation processing tool (kapt)
kapt("androidx.room:room-compiler:$room_version")
// To use Kotlin Symbolic Processing (KSP)
ksp("androidx.room:room-compiler:$room_version")
// optional - Kotlin Extensions and Coroutines support for Room
implementation("androidx.room:room-ktx:$room_version")
// optional - RxJava2 support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - RxJava3 support for Room
implementation "androidx.room:room-rxjava3:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// optional - Test helpers
testImplementation("androidx.room:room-testing:$room_version")
}
@Database(entities = arrayOf(Goods::class),version = 1)
abstract class GoodsDatabase: RoomDatabase() {
abstract fun goodsDao():GoodsDao
}
@Dao
interface GoodsDao {
@Query("SELECT * FROM goods")
fun getAll(): List
@Query("SELECT * FROM goods WHERE id IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List
@Insert
fun insertAll(vararg users: Goods)
@Delete
fun delete(user: Goods)
}
@Entity
data class Goods(
@ColumnInfo(name = "goodsName") val name: String
) {
@PrimaryKey(autoGenerate = true)
var id: Long? =null
}
因为商品的主键自动累加的,所有不应该在构造方法中传入,所以我们给他初始设置为 null,
注:必须设置为 null 才能实现自增,如果设置为具体值那么就无法自增了
GlobalScope.launch {
db.goodsDao().insertAll(Goods("阿娜卡列尼亚"))
}
GlobalScope.launch {
val all = db.goodsDao().getAll()
val sb = StringBuilder()
for (good in all){
sb.append("商品id:${good.id} 商品名:${good.name} \n")
}
withContext(Dispatchers.Main){
tv_simpleuse.text=sb.toString()
}
}
@Ignore val time:Long=0
open class User {
var picture: Bitmap? = null
}
@Entity(ignoredColumns = arrayOf("picture"))
data class RemoteUser(
@PrimaryKey val id: Int,
val hasVpn: Boolean
) : User()
本例中 RemoteUser 继承了 User,User 中有一个 picture 字段,我们不想数据库的表生成一个 picture 列,所以在 RemoteUser 的 Entity 注解中添加 ignoredColumns 忽略。
定义实体类的时候注解中加入如下代码:
@Entity(indices = arrayOf(Index(value = ["last_name", "address"])))
构建 db 对象的时候调用 allowMainThreadQueries 方法
虽说可以设置,但是建议你 100%不要考虑这么做
val db =
Room.databaseBuilder(applicationContext, GoodsDatabase::class.java, "goods")
.allowMainThreadQueries()
.build()
插入数据的方法可以有一个或多个参数,如果是一个参数可以返回主键,如果是多个参数会返回主键的数组
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(vararg users: User)
@Dao
interface MyDao {
@Update
fun updateUsers(vararg users: User)
}
@Dao
interface MyDao {
@Delete
fun deleteUsers(vararg users: User)
}
@Dao
interface MyDao {
@Query("SELECT * FROM user")
fun loadAllUsers(): Array
}
@Dao
interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge")
fun loadAllUsersOlderThan(minAge: Int): Array
}
:minAge 绑定参数与 minAge 方法参数进行匹配
下面代码查询结果会自动赋值到 NameTuple 中
@Dao
interface MyDao {
@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List
}
大多数情况下,您只需获取实体的几个字段。例如,您的界面可能仅显示用户的名字和姓氏,而不是用户的每一条详细信息。
部分查询可能要求您传入数量不定的参数,参数的确切数量要到运行时才知道。例如,您可能希望从部分区域中检索所有用户的相关信息。Room 知道参数何时表示集合,并根据提供的参数数量在运行时自动将其展开。
@Dao
interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List): List
}
使用 suspend 修饰方法,通过协程使这些方法变为异步,避免我们在主线程中进行操作
@Update
suspend fun updateUsers(vararg users: User)
@Dao
interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
fun loadUsersFromRegionsSync(regions: List): LiveData>
}
暂时先不深入这里,回头单开文章讲:
官网参考:
https://developer.android.google.cn/training/data-storage/room/relationships
如需从位于应用 assets/ 目录中的任意位置的预封装数据库文件预填充 Room 数据库,请先从 RoomDatabase.Builder 对象调用 createFromAsset() 方法,然后再调用 build()
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromAsset("database/myapp.db")
.build()
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromFile(File("mypath"))
.build()
Room 持久性库支持通过 Migration 类进行增量迁移以满足此需求。每个 Migration 子类通过替换 Migration.migrate() 方法定义 startVersion 和 endVersion 之间的迁移路径。当应用更新需要升级数据库版本时,Room 会从一个或多个 Migration 子类运行 migrate() 方法,以在运行时将数据库迁移到最新版本:
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, " +
"PRIMARY KEY(`id`))")
}
}
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER")
}
}
Room.databaseBuilder(applicationContext, MyDb::class.java, "database-name")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build()
官网提供了单元测试的方式测试数据迁移,可以尽量少错误
https://developer.android.google.cn/training/data-storage/room/migrating-db-versions