Kotlin Android Room数据库
一、Room 数据库简介
在 Android 开发中,数据持久化是一个至关重要的环节。传统的 SQLiteOpenHelper 虽然提供了基本的 SQLite 数据库操作能力,但使用起来较为繁琐,需要手动编写大量的 SQL 语句来执行数据库的创建、升级以及数据的增删改查操作。而 Room 是 Google 在 Android Jetpack 组件中推出的一个 SQLite 数据库抽象层库,它旨在让开发者更轻松、更高效地在 Android 应用中使用 SQLite 数据库,同时遵循现代 Android 开发的最佳实践。
Room 主要有以下几个优点:
- 强大的编译时检查:通过注解处理器,Room 能在编译期检查 SQL 语句的正确性,大大减少运行时因 SQL 错误导致的崩溃。例如,如果在定义查询方法时写错了表名或者列名,编译器会直接报错,而不是等到运行时才发现问题。
- 简化数据库操作:Room 提供了简洁的 API 来进行数据库操作,开发者无需编写大量重复的 SQLite 代码。比如,对于数据的插入、更新和删除操作,只需定义相应的方法并添加对应的注解,Room 会自动生成实现代码。
- 支持 LiveData 和 RxJava:Room 能够与 LiveData 和 RxJava 很好地集成,方便开发者实现数据的响应式编程。例如,使用 LiveData 可以让 UI 实时感知数据库数据的变化并自动更新,而 RxJava 则提供了强大的异步操作能力,使得数据库操作可以在后台线程进行,避免阻塞主线程。
二、配置项目以使用 Room
要在 Kotlin 项目中使用 Room,首先需要在项目的 build.gradle
文件中添加依赖。在 app/build.gradle
文件中,添加如下依赖:
def room_version = "2.4.3"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// 如果需要使用 Kotlin 扩展
implementation "androidx.room:room-ktx:$room_version"
// 如果需要使用 LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
// 如果需要使用 RxJava
implementation "androidx.room:room-rxjava2:$room_version"
添加完依赖后,同步项目,确保依赖下载成功。
三、创建 Room 数据库
- 定义数据库实体类:
实体类代表数据库中的表,每个实体类的实例对应表中的一行数据。在 Kotlin 中,使用
@Entity
注解来标识一个类为数据库实体。例如,我们创建一个简单的用户表对应的实体类User
:
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val name: String,
val age: Int
)
在这个 User
类中,@Entity
注解指定了表名为 users
。@PrimaryKey
注解标识了 id
字段为主键,并且 autoGenerate = true
表示该主键自增长。
- 创建数据库访问对象(DAO):
DAO 负责定义数据库的操作方法,如插入、查询、更新和删除等。使用
@Dao
注解标识一个接口或抽象类为 DAO。例如:
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User)
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
@Update
suspend fun update(user: User)
@Query("DELETE FROM users WHERE id = :userId")
suspend fun deleteById(userId: Int)
}
在上述代码中,@Insert
注解的方法用于插入数据,@Query
注解的方法用于执行自定义的 SQL 查询,@Update
注解的方法用于更新数据,@Query
注解的另一个方法用于根据 id
删除数据。注意,这里的方法都声明为 suspend
,表示这是挂起函数,需要在协程中调用,以避免阻塞主线程。
- 创建 Room 数据库类:
使用
@Database
注解创建一个继承自RoomDatabase
的抽象类,用于定义数据库的相关信息,如包含哪些实体类和 DAO。例如:
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
在这个 AppDatabase
类中,@Database
注解指定了包含的实体类为 User
,数据库版本为 1
。同时,通过抽象方法 userDao()
暴露了 UserDao
。
- 获取数据库实例:
在应用中,通常需要一个单例的数据库实例。可以使用
Room
类的databaseBuilder
方法来构建数据库实例。例如:
import android.content.Context
import androidx.room.Room
object DatabaseInstance {
private lateinit var instance: AppDatabase
fun getInstance(context: Context): AppDatabase {
if (!::instance.isInitialized) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
)
.build()
}
return instance
}
}
在上述代码中,通过 Room.databaseBuilder
方法创建了 AppDatabase
的实例,数据库名称为 app_database
。这里使用了 Kotlin 的 lateinit
关键字来延迟初始化 instance
,并通过 !::instance.isInitialized
来判断是否已经初始化,以确保单例特性。
四、数据库操作示例
- 插入数据: 在获取到数据库实例和对应的 DAO 后,就可以进行数据插入操作。例如,在一个视图模型(ViewModel)中:
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainViewModel : ViewModel() {
fun insertUser(user: User) {
CoroutineScope(Dispatchers.IO).launch {
val dao = DatabaseInstance.getInstance(App.instance).userDao()
dao.insert(user)
}
}
}
在上述代码中,insertUser
方法通过 CoroutineScope
在 IO 线程中启动一个协程,获取 UserDao
并调用 insert
方法插入用户数据。这里的 App.instance
是一个自定义的用于获取应用上下文的单例对象。
- 查询数据: 查询数据同样在协程中进行。例如:
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.Flow
class MainViewModel : ViewModel() {
fun getAllUsers(): Flow<List<User>> {
return CoroutineScope(Dispatchers.IO).async {
val dao = DatabaseInstance.getInstance(App.instance).userDao()
dao.getAllUsers()
}.flow
}
}
这里通过 CoroutineScope
在 IO 线程中异步查询所有用户数据,并返回一个 Flow
。Flow
是 Kotlin 中用于异步数据流的类型,适合在响应式编程中使用。在 UI 层,可以使用 collect
方法来收集 Flow
中的数据并更新 UI。
- 更新数据: 更新数据的操作与插入和查询类似。例如:
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainViewModel : ViewModel() {
fun updateUser(user: User) {
CoroutineScope(Dispatchers.IO).launch {
val dao = DatabaseInstance.getInstance(App.instance).userDao()
dao.update(user)
}
}
}
在 updateUser
方法中,在 IO 线程中获取 UserDao
并调用 update
方法更新用户数据。
- 删除数据: 删除数据也遵循相同的模式。例如:
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainViewModel : ViewModel() {
fun deleteUserById(userId: Int) {
CoroutineScope(Dispatchers.IO).launch {
val dao = DatabaseInstance.getInstance(App.instance).userDao()
dao.deleteById(userId)
}
}
}
在 deleteUserById
方法中,根据传入的 userId
在 IO 线程中调用 UserDao
的 deleteById
方法删除用户数据。
五、Room 与 LiveData 的集成
- 修改 DAO 以返回 LiveData:
为了使数据变化能够实时反映到 UI 上,可以让 DAO 方法返回
LiveData
。例如,修改UserDao
中的查询方法:
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import androidx.lifecycle.LiveData
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User)
@Query("SELECT * FROM users")
fun getAllUsers(): LiveData<List<User>>
@Update
suspend fun update(user: User)
@Query("DELETE FROM users WHERE id = :userId")
suspend fun deleteById(userId: Int)
}
这里将 getAllUsers
方法的返回类型改为 LiveData<List<User>>
。
- 在视图模型中使用 LiveData:
在视图模型中,可以直接获取
LiveData
并暴露给 UI。例如:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
class MainViewModel : ViewModel() {
val allUsers = liveData {
val dao = DatabaseInstance.getInstance(App.instance).userDao()
emit(dao.getAllUsers().value?: emptyList())
}
}
在上述代码中,使用 liveData
构建器创建了一个 LiveData
,在其中获取 UserDao
并发射查询到的用户数据。如果数据为空,则发射一个空列表。
- 在 UI 中观察 LiveData:
在 Activity 或 Fragment 中,可以观察
LiveData
并根据数据变化更新 UI。例如,在一个Fragment
中:
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import kotlinx.android.synthetic.main.fragment_main.*
import java.util.*
class MainFragment : Fragment() {
private lateinit var viewModel: MainViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
viewModel.allUsers.observe(viewLifecycleOwner, Observer { users ->
val userNames = users.map { it.name }.joinToString(", ")
textView.text = "Users: $userNames"
})
}
}
在 onViewCreated
方法中,获取视图模型并观察 allUsers
LiveData
。当数据发生变化时,更新 TextView
显示用户名字列表。
六、Room 与 RxJava 的集成
- 修改 DAO 以返回 RxJava 类型:
如果要使用 RxJava 进行数据库操作,可以让 DAO 方法返回 RxJava 的类型,如
Single
、Completable
等。例如,修改UserDao
:
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import io.reactivex.Completable
import io.reactivex.Single
@Dao
interface UserDao {
@Insert
fun insert(user: User): Completable
@Query("SELECT * FROM users")
fun getAllUsers(): Single<List<User>>
@Update
fun update(user: User): Completable
@Query("DELETE FROM users WHERE id = :userId")
fun deleteById(userId: Int): Completable
}
这里将插入、更新和删除方法返回 Completable
,表示操作完成但不返回数据;查询方法返回 Single
,表示返回一个单一的数据对象(这里是用户列表)。
- 在视图模型中使用 RxJava: 在视图模型中,可以使用 RxJava 的操作符来处理数据库操作。例如:
import androidx.lifecycle.ViewModel
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
class MainViewModel : ViewModel() {
private val disposable = CompositeDisposable()
fun insertUser(user: User) {
val dao = DatabaseInstance.getInstance(App.instance).userDao()
disposable.add(
dao.insert(user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// 插入成功
}, {
// 插入失败
})
)
}
}
在 insertUser
方法中,使用 subscribeOn(Schedulers.io())
将插入操作放在 IO 线程执行,observeOn(AndroidSchedulers.mainThread())
将结果观察放在主线程,以便更新 UI。
- 管理 RxJava 的订阅:
在视图模型的
onCleared
方法中,需要取消所有的 RxJava 订阅,以避免内存泄漏。例如:
import androidx.lifecycle.ViewModel
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
class MainViewModel : ViewModel() {
private val disposable = CompositeDisposable()
fun insertUser(user: User) {
val dao = DatabaseInstance.getInstance(App.instance).userDao()
disposable.add(
dao.insert(user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
// 插入成功
}, {
// 插入失败
})
)
}
override fun onCleared() {
super.onCleared()
disposable.clear()
}
}
七、数据库升级
- 增加数据库版本:
当需要对数据库进行升级时,首先要在
@Database
注解中增加版本号。例如,将AppDatabase
的版本从1
升级到2
:
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = [User::class], version = 2)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
- 定义数据库迁移:
创建一个
Migration
对象,用于定义从旧版本到新版本的数据库迁移逻辑。例如,假设要在users
表中添加一个新的列email
:
import android.database.sqlite.SQLiteDatabase
import androidx.room.migration.Migration
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SQLiteDatabase) {
database.execSQL("ALTER TABLE users ADD COLUMN email TEXT")
}
}
在上述代码中,Migration
的构造函数接收旧版本号 1
和新版本号 2
。migrate
方法中执行了 SQL 语句来添加新列。
- 应用数据库迁移:
在构建数据库实例时,将
Migration
对象传递给addMigrations
方法。例如:
import android.content.Context
import androidx.room.Room
object DatabaseInstance {
private lateinit var instance: AppDatabase
fun getInstance(context: Context): AppDatabase {
if (!::instance.isInitialized) {
instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
)
.addMigrations(MIGRATION_1_2)
.build()
}
return instance
}
}
这样,当应用升级且数据库版本发生变化时,Room 会自动执行迁移逻辑,确保数据库结构的正确性。
八、高级特性
- 关系型数据处理:
Room 支持处理实体之间的关系,如一对多、多对一和多对多关系。例如,假设有一个
Order
实体和一个Product
实体,一个订单可以包含多个产品,这是一对多关系。
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "orders")
data class Order(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val orderDate: String
)
@Entity(tableName = "products", foreignKeys = [
androidx.room.ForeignKey(
entity = Order::class,
parentColumns = ["id"],
childColumns = ["orderId"],
onDelete = androidx.room.ForeignKey.CASCADE
)
])
data class Product(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val name: String,
val price: Double,
val orderId: Int
)
在 Product
实体中,通过 @ForeignKey
注解定义了与 Order
实体的外键关系,onDelete = androidx.room.ForeignKey.CASCADE
表示当订单被删除时,相关的产品也会被删除。
- 复杂查询:
对于复杂的查询,Room 允许编写复杂的 SQL 查询语句。例如,要查询每个订单及其包含的产品列表,可以在
OrderDao
中定义如下方法:
import androidx.room.Dao
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
interface OrderDao {
@Query("""
SELECT * FROM orders
JOIN products ON orders.id = products.orderId
""")
fun getOrdersWithProducts(): Flow<List<OrderWithProducts>>
}
data class OrderWithProducts(
val order: Order,
val products: List<Product>
)
这里通过 JOIN
操作将 orders
表和 products
表连接起来,并返回一个包含订单及其相关产品的自定义数据类 OrderWithProducts
。
- 事务处理: Room 支持事务处理,确保多个数据库操作要么全部成功,要么全部失败。例如,在一个订单创建和相关产品插入的场景中:
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
@Dao
interface OrderDao {
@Insert
suspend fun insertOrder(order: Order)
@Insert
suspend fun insertProducts(products: List<Product>)
@Transaction
suspend fun createOrderWithProducts(order: Order, products: List<Product>) {
insertOrder(order)
val orderId = order.id
val productsWithOrderId = products.map { it.copy(orderId = orderId) }
insertProducts(productsWithOrderId)
}
}
在 createOrderWithProducts
方法上添加 @Transaction
注解,确保订单插入和产品插入操作在一个事务中执行。
通过以上内容,全面介绍了 Kotlin Android Room 数据库的使用,从基础的配置、创建数据库到高级的特性和集成,希望能帮助开发者在 Android 应用开发中高效地使用 Room 进行数据持久化。