Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库
Room 包含 3 个主要组件:
(1)数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点
使用 @Database 注释的类应满足以下条件:
1.是扩展 RoomDatabase 的抽象类
2.在注释中添加与数据库关联的实体列表
3.包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法
在运行时,您可以通过调用 Room.databaseBuilder() 或Room.inMemoryDatabaseBuilder() 获取 Database 的实例
(2)Entity:表示数据库中的表
(3)DAO:包含用于访问数据库的方法
(1)导入依赖:
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
除此之外,不要忘记加上下面这个
apply plugin: 'kotlin-kapt'
(2)定义实体
对于每个实体,系统会在关联的 Database 对象中创建一个表,以存储这些项
//@Fts4:使用此注解支持全文搜索
@Entity(tableName = "user") //如不指定,默认是类名(SQLite中的表名称不区分大小写)
data class User(
@PrimaryKey(autoGenerate = true) var id: Int, //定义主键,并自动分配
var name: String?,
@ColumnInfo(name = "company_id") var companyId: String?, //指定列名称,默认是字段名
@ColumnInfo(name = "company_name") var companyName: String?,
@Ignore var overlook: String? //忽略字段
)
如果是复合主键,可以如此添加:
@Entity(primaryKeys = ["id", "companyId"])
如果想要将特定列编入索引,需要列出要在索引或复合索引中包含的列的名称,还可以将unique 属性设为 true,保证数据库中的某些字段或字段组必须是唯一的
@Entity(indices = [Index(value = ["company_name", "name"], unique = true)])
嵌套对象:可以使用 @Embedded 注释表示要分解为表格中的子字段的对象
@Entity
data class User(
@PrimaryKey(autoGenerate = true) var id: Int,
var name: String?,
var companyId: String?,
var companyName: String?,
@Embedded var address: Address?
)
@Entity
data class Address(
val country: String?,
val province: String?,
val city: String?
)
(3)DAO 访问数据
@Dao
interface UserDao {
@Query("select * from user")
fun getAllMsg(): List<User>
@Query("select * from user where id in (:userId)")
fun getAllById(userId: IntArray): List<User>
@Query("select * from user where name like :userName")
fun getAllByName(userName: String): List<User>
@Query("select * from user where age < :youngAge")
fun getYoungPeople(youngAge: Int): List<User>
@Query("select * from user where age between :minAge and :maxAge")
fun getPeopleBetweenAges(minAge: Int, maxAge: Int): List<User>
@Query("select name, companyName from user")
fun getColumn(): List<Name>
//传递参数的集合
@Query("select name, companyName from user where age in (:ages)")
fun getPeopleByAge(ages: List<String>): List<Name>
//流式响应式查询:只要表中的任何数据发生变化,返回的Flow对象就会再次触发查询并重新发出整个结果集
@Query("select * from user")
fun getAllUsers(): kotlinx.coroutines.flow.Flow<List<User>>
//使用Flow响应式查询,只要对表中的任何行进行更新,Flow对象就会重新运行查询
//通过将distinctUntilChanged()应用于返回的Flow对象,可以确保仅在实际查询结果发生更改时通知界面
fun getUserDistinctUntilChanged() = getAllUsers().distinctUntilChanged()
@Insert
fun insertAll(vararg user: User)
@Delete
fun delete(user: User)
//updateUser与每个实体的主键匹配的查询,也可以让此方法返回一个int值,以指示数据库中更新/删除的行数
@Update
fun updateUser(vararg user: User)
//也可将suspend添加到DAO方法中,让其成为异步方法,这样可确保不会在主线程上执行这些方法
@Query("select * from user")
suspend fun getAllFromAsyn(): List<User>
//使用LiveData进行可观察查询
@Query("select * from user where id in (:userId)")
fun getAllByIdFromLiveData(userId: IntArray): LiveData<List<User>>
}
(4)创建数据库
如果要增加work表,要进行数据库迁移,修改version
@Entity
data class Work(
@PrimaryKey(autoGenerate = true) var id: Int,
var name: String?
)
@Dao
interface WorkDao {
@Query("select * from work")
fun getAllMsg(): List<Work>
@Insert
fun insert(vararg work: Work)
}
@Database(entities = [User::class, Work::class], version = 2)
abstract class MyDatabase : RoomDatabase() {
abstract val userDao: UserDao
abstract val workDao: WorkDao
companion object {
@Volatile
private var INSTANCE: MyDatabase? = null
val MIGRATION = object : Migration(1, 2) {
//增加表,迁移数据库
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE `work` (`id` INTEGER NOT NULL, `name` TEXT ,PRIMARY KEY(`id`))")
}
}
fun getInstance(): MyDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
MyApplication.getAppContext(),
MyDatabase::class.java,
"database"
).allowMainThreadQueries().addMigrations(MIGRATION).build()
}
return instance
}
}
}
}
(5)操作数据库
class MainActivity : AppCompatActivity() {
private var user1: User? = null
private var user2: User? = null
private var user3: User? = null
private var user4: User? = null
private var userList: MutableList<User>? = null
private var workList: MutableList<Work>? = null
private var job: Work? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initAuthority()
initData()
operateDatabase()
}
private fun initData() {
user1 = User(1, "一号嘉宾", "100", "百度", 10)
user2 = User(2, "二号嘉宾", "200", "阿里", 20)
user3 = User(3, "三号嘉宾", "300", "腾讯", 30)
user4 = User(1, "行走的猪蹄", "900", "字节跳动", 50)
job = Work(1, "程序员")
}
private fun operateDatabase() {
insert.setOnClickListener {
//插入数值
MyDatabase.getInstance().userDao.insertAll(user1!!, user2!!, user3!!)
}
query.setOnClickListener {
//查询年龄0-50
userList = ArrayList()
userList?.addAll(MyDatabase.getInstance().userDao.getPeopleBetweenAges(0, 50))
txt.text = userList.toString()
}
update.setOnClickListener {
//修改数据
MyDatabase.getInstance().userDao.updateUser(user4!!)
}
delete.setOnClickListener {
//删除数据
MyDatabase.getInstance().userDao.delete(user2!!)
}
test.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
//异步查询的情况
userList = ArrayList()
userList!!.addAll(MyDatabase.getInstance().userDao.getAllFromAsyn())
txt.text = userList.toString()
}
}
work.setOnClickListener {
// MyDatabase.getInstance().workDao.insert(job!!)//插入数据后进行显示
workList = ArrayList()
workList!!.addAll(MyDatabase.getInstance().workDao.getAllMsg())
txt.text = workList.toString()
}
}
private fun initAuthority() {
PermissionX.init(this)
.permissions(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
.request {
allGranted, grantedList, deniedList ->
if (allGranted) {
Toast.makeText(this, "All permissions are granted", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(
this,
"These permissions are denied: $deniedList",
Toast.LENGTH_LONG
).show()
}
}
}
}