文章目录
1.导入依赖
apply plugin: 'kotlin-kapt'
implementation "androidx.room:room-runtime:2.2.6"
kapt "androidx.room:room-compiler:2.2.6"
//room扩展库 支持通过协程操作数据库
implementation "androidx.room:room-ktx:2.2.6"
//数据库测试
testImplementation "androidx.room:room-testing:2.2.6"
2.使用实体类定义数据
Address类
data class Address (
val street:String?,
val state:String?,
val city:String?
)
User类
@Entity
data class User(
@PrimaryKey(autoGenerate = true)
val uid:Int,
@ColumnInfo(name="first_name")
val firstName:String?,
@ColumnInfo(name = "last_name")
val lastName:String?,
@Embedded
val address: Address?
)
Book基类
open class BaseBook{
val time:Long=0
}
子类Book
@Entity(tableName = "lkBook",primaryKeys = ["lkBookName"],indices = [Index(value = ["author"])],ignoredColumns = ["time"])
data class Book (
val id : Int,
val author :String,
@ColumnInfo(name = "lkBookName")
val name :String,
val country:String?,
):BaseBook()
-
Entity
:用于将该类映射到数据库中的一张表 -
tableName
:不设置则默认使用类名作为表名,否则指定对应的表名 -
PrimaryKey
:指定主键,每个实体必须至少有一个字段定义为主键,如果分配自动ID,设置@PrimaryKey 的autoGenerate
属性,设置多个主键可以通过primaryKeys
-
ColumnInfo
:为实体类中的字段设置自定义的名称 -
indices
:给数据库某些列添加索引
1.索引不是越多越好
2.不要对经常变动数据加索引
3.小数据量的表不需要加索引
4.索引一般加在常用来查询的字段上 -
ignoredColumns
:该字段不会被映射到表中 -
Embedded
:用于将某个实体或数据对象表示为一个紧密的整体 -
Transaction
:确保整个操作以原子方式执行
3.使用DAO访问数据
- 当查询语句中需要传集合或者数组时可以通过如下方式指定
(:字段名)
@Query("select * from user where uid in (:userIds)")
fun loadAllByIds(userIds:IntArray):Flow<List<User>>
- 当查询语句中传入某个字段通过如下方式指定
:字段名
@Query("select * from user where first_name like :first and last_name like :last limit 1")
suspend fun findByName(first:String,last:String):User
-
在插入数据时可以指定冲突策略,默认是OnConflictStrategy.ABORT
1.OnConflictStrategy.REPLACE:冲突策略是取代旧数据同时继续事务
2.OnConflictStrategy.ROLLBACK:冲突策略是回滚事务
3.OnConflictStrategy.ABORT:冲突策略是终止事务
4.OnConflictStrategy.FAIL:冲突策略是事务失败
5.OnConflictStrategy.IGNORE:冲突策略是忽略冲突
@Dao
interface UserDao {
@Query("select * from user")
fun getAll():Flow<List<User>>
@Query("select * from user where uid in (:userIds)")
fun loadAllByIds(userIds:IntArray):Flow<List<User>>
@Query("select * from user where first_name like :first and last_name like :last limit 1")
suspend fun findByName(first:String,last:String):User
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(vararg users:User)
@Delete
suspend fun delete(user:User)
@Update
suspend fun updateUser(user:User)
@Query("delete from user")
suspend fun deleteAll()
}
UserRepository.kt
class UserRepository(private val userDao:UserDao) {
fun loadAllUser(): Flow<List<User>?>{
return userDao.getAll()
}
fun loadAllByIds(userIds:IntArray): Flow<List<User>> {
return userDao.loadAllByIds(userIds)
}
suspend fun insertAll(vararg users:User){
userDao.insertAll(*users)
}
suspend fun deleteAll(){
userDao.deleteAll()
}
}
UserViewModel.kt
class UserViewModel(private val repository: UserRepository) : ViewModel() {
val result by lazy {
MutableLiveData<List<User>?>()
}
fun insert(user: User) = viewModelScope.launch {
repository.insertAll(user)
}
fun updateUser(user: User) = viewModelScope.launch {
repository.update(user)
getAll()
}
fun deleteAll() = viewModelScope.launch {
repository.deleteAll()
getAll()
}
fun loadAllByIds(userIds: IntArray): LiveData<List<User>> =
repository.loadAllByIds(userIds).asLiveData()
fun getAll(){
viewModelScope.launch(Dispatchers.Main) {
repository.loadAllUser().collect {
result.value = it
}
}
}
}
class UserViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return UserViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
AppDatabase.kt
@Database(entities = [User::class,Book::class],version = 1,exportSchema = false)
abstract class AppDataBase : RoomDatabase(){
abstract fun userDao():UserDao
private class UserDatabaseCallback(private val scope:CoroutineScope):RoomDatabase.Callback(){
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch {
val userDao = database.userDao()
userDao.deleteAll()
userDao.insertAll(
User(1,"张","三",null),
User(2,"李","四",null),
User(3,"王","五",Address("祥符街道","拱墅区","杭州市"))
)
}
}
}
}
companion object{
@Volatile
private var INSTANCE: AppDataBase? = null
fun getDatabase(context: Context,scope: CoroutineScope): AppDataBase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDataBase::class.java,
"lkDatabase"
).addCallback(UserDatabaseCallback(scope)).build()
INSTANCE = instance
instance
}
}
}
}
App.kt
class App:Application() {
val applicationScope = CoroutineScope(SupervisorJob())
val database by lazy { AppDataBase.getDatabase(this,applicationScope) }
val respository by lazy { UserRepository(database.userDao()) }
override fun onCreate() {
super.onCreate()
}
}
4.进行数据库迁移
迁移步骤:
1.修改AppDataBase中的version+1
2.定义一个Migration,比如
private val migrate_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE new_Student (
uid INTEGER PRIMARY KEY NOT NULL,
first_name TEXT,
last_name TEXT,
street TEXT,
state TEXT,
city text,
birthday INTEGER NOT NULL DEFAULT 0
)
""".trimIndent()
)
database.execSQL(
"""
INSERT INTO new_Student(uid,first_name,last_name,street,state,city)
SELECT uid,first_name,last_name,street,state,city FROM Student
""".trimIndent()
)
database.execSQL("drop table Student")
database.execSQL("alter table new_Student rename to Student")
}
}
private val migrare_2_3 = object :Migration(2,3){
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE President ADD birthday INTEGER")
}
}
3.增加迁移字段
@Entity
data class Student(
.....
@ColumnInfo(defaultValue = "0")
val birthday:Int,
)
@Entity
data class President (
.....
val birthday:Int?
)
4.通过RoomDatabase的addMigrations添加
.addMigrations(migrate_1_2, migrare_2_3)
5.关于数据库迁移的几个错误
1.给某张表新增字段时,新增字段必须放在最后面,然后通过
val birthday:Int?
database.execSQL("ALTER TABLE President ADD birthday INTEGER")
同时要注意字段是否可空,默认执行的sql语句是可空的, 如果新增的字段没有加
?会报错;或者说如果是非空字段--val birthday:Int, 那么执行的sql语句需要添加 NOT NULL
database.execSQL("ALTER TABLE President ADD birthday INTEGER NOT NULL")
2.如果在表中新增的字段想指定位置,在mysql中是可以做到的,通过after指定位于哪个字段之后,但是在sqlite中无法实现,只能通过创建一张新表,然后把在把数据从新插入,在把老表删除,新的表名在重新命名成老的表名,也就是上诉中的migrate_1_2
database.execSQL("ALTER TABLE President ADD COLUMN gender INTEGER NOT NULL AFTER presidentId")
3.如果碰到执行的SQL语句报错的时候,如果你认为你的写法没有错,一定要把SQL语句单独拿出来测试一下,因为有的时候可能sqlite3确实不支持,在mysql上就可以,就比如上诉的after!!!。