引言
在Android开发中,数据库操作是常见的需求。传统的SQLite API虽然功能强大,但直接使用较为繁琐。现代Android开发更推荐使用对象关系映射(ORM)的方式来操作数据库,这不仅提高了开发效率,也使得代码更加清晰易维护。本文将详细介绍如何在Android中以对象方式操作数据库,特别是如何处理复杂对象的转化。
一、Room数据库简介
Room是Google官方推荐的数据库ORM库,它在SQLite之上提供了一个抽象层,允许更流畅的数据库访问,同时保留SQLite的全部功能。
1.1 Room的核心组件
-
Entity: 表示数据库中的表
-
DAO(Data Access Object): 包含用于访问数据库的方法
-
Database: 包含数据库持有者,并作为应用持久化数据的主要访问点
1.2 添加Room依赖
在app模块的build.gradle中添加:
gradle
dependencies {
def room_version = "2.4.0"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// 可选 - Kotlin扩展和协程支持
implementation "androidx.room:room-ktx:$room_version"
// 可选 - RxJava支持
implementation "androidx.room:room-rxjava2:$room_version"
}
二、基本实体定义与操作
2.1 简单实体定义
kotlin
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
@ColumnInfo(name = "user_name") val name: String,
val age: Int,
@ColumnInfo(defaultValue = "false") val isVip: Boolean
)
2.2 基本DAO接口
kotlin
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User): Long
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUserById(id: Int): User?
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
}
三、复杂对象处理
在实际开发中,我们经常需要处理包含复杂数据结构的对象。Room提供了几种方式来处理这种情况。
3.1 使用@Embedded处理嵌套对象
kotlin
data class Address(
val street: String,
val city: String,
val postalCode: String
)
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: Int,
val name: String,
@Embedded val address: Address
)
这种方式会将Address的所有字段平铺到users表中。
3.2 使用TypeConverter处理自定义类型
对于Room不支持的类型(如Date、List、自定义类等),我们可以使用TypeConverter进行转换。
3.2.1 日期类型转换
kotlin
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}
3.2.2 在Database类中注册转换器
kotlin
@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
3.3 处理复杂集合类型
3.3.1 使用JSON序列化存储集合
kotlin
class Converters {
private val gson = Gson()
@TypeConverter
fun fromStringList(value: List<String>?): String? {
return gson.toJson(value)
}
@TypeConverter
fun toStringList(value: String?): List<String>? {
return if (value == null) null
else gson.fromJson(value, object : TypeToken<List<String>>() {}.type)
}
}
3.3.2 存储复杂对象列表
kotlin
data class UserPreference(
val key: String,
val value: Any
)
class Converters {
private val gson = Gson()
@TypeConverter
fun fromUserPreferenceList(preferences: List<UserPreference>?): String? {
return gson.toJson(preferences)
}
@TypeConverter
fun toUserPreferenceList(data: String?): List<UserPreference>? {
return if (data == null) null
else gson.fromJson(data, object : TypeToken<List<UserPreference>>() {}.type)
}
}
3.4 处理一对一、一对多关系
3.4.1 使用@Relation处理一对多关系
kotlin
data class UserWithBooks(
@Embedded val user: User,
@Relation(
parentColumn = "id",
entityColumn = "userId"
)
val books: List<Book>
)
@Dao
interface UserDao {
@Transaction
@Query("SELECT * FROM User")
fun getUsersWithBooks(): List<UserWithBooks>
}
3.4.2 处理多对多关系
需要中间表:
kotlin
@Entity(primaryKeys = ["userId", "bookId"])
data class UserBookCrossRef(
val userId: Int,
val bookId: Int
)
data class UserWithBooks(
@Embedded val user: User,
@Relation(
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(UserBookCrossRef::class)
)
val books: List<Book>
)
四、高级特性与实践
4.1 数据库迁移
当数据库结构发生变化时,需要处理迁移:
kotlin
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE users ADD COLUMN last_login INTEGER")
}
}
Room.databaseBuilder(context, AppDatabase::class.java, "app-db")
.addMigrations(MIGRATION_1_2)
.build()
4.2 预填充数据库
kotlin
Room.databaseBuilder(context, AppDatabase::class.java, "app-db")
.createFromAsset("database/prepopulated.db")
.build()
4.3 数据库测试
kotlin
@RunWith(AndroidJUnit4::class)
class UserDaoTest {
private lateinit var database: AppDatabase
private lateinit var userDao: UserDao
@Before
fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(
context, AppDatabase::class.java
).build()
userDao = database.userDao()
}
@After
fun closeDb() {
database.close()
}
@Test
fun insertAndGetUser() = runBlocking {
val user = User(1, "John", Address("Main St", "NY", "10001"))
userDao.insert(user)
val loaded = userDao.getUserById(1)
assertEquals(user.name, loaded?.name)
}
}
五、性能优化建议
-
避免在主线程操作数据库:Room默认不允许在主线程访问数据库
-
合理使用索引:对频繁查询的字段添加索引
-
批量操作 :使用
@Insert
的onConflict
策略和@Transaction
-
分页查询:使用Paging库处理大量数据
-
观察数据变化 :使用
LiveData
或Flow
自动更新UI
六、完整示例
6.1 复杂实体定义
kotlin
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val name: String,
val age: Int,
@Embedded val address: Address,
@ColumnInfo(name = "preferences") val preferences: List<UserPreference>,
@ColumnInfo(name = "last_login") val lastLogin: Date?
) {
data class Address(
val street: String,
val city: String,
val postalCode: String
)
data class UserPreference(
val key: String,
val value: Any
)
}
6.2 完整转换器
kotlin
class Converters {
private val gson = Gson()
// Date转换
@TypeConverter
fun fromTimestamp(value: Long?): Date? = value?.let { Date(it) }
@TypeConverter
fun dateToTimestamp(date: Date?): Long? = date?.time
// UserPreference列表转换
@TypeConverter
fun fromUserPreferenceList(preferences: List<User.UserPreference>?): String? {
return gson.toJson(preferences)
}
@TypeConverter
fun toUserPreferenceList(data: String?): List<User.UserPreference>? {
return if (data == null) null
else gson.fromJson(data, object : TypeToken<List<User.UserPreference>>() {}.type)
}
}
6.3 完整DAO示例
kotlin
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(user: User): Long
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUserById(id: Int): User?
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
@Transaction
@Query("SELECT * FROM users WHERE address.city = :city")
suspend fun getUsersByCity(city: String): List<User>
}
七、总结
通过Room库,我们可以以面向对象的方式优雅地操作Android数据库。对于复杂对象:
-
使用
@Embedded
处理嵌套对象 -
使用
TypeConverter
处理自定义类型 -
使用JSON序列化处理复杂集合
-
使用
@Relation
处理对象间关系
这种方式不仅提高了开发效率,也使代码更加清晰易维护。在实际项目中,应根据具体需求选择合适的复杂对象处理策略,并注意性能优化和数据库迁移等问题。
希望本文能帮助你在Android开发中更好地以对象方式操作数据库,特别是处理复杂对象的转化问题。