Android 中以对象方式操作数据库:复杂对象转化实践

引言

在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)
    }
}

五、性能优化建议

  1. 避免在主线程操作数据库:Room默认不允许在主线程访问数据库

  2. 合理使用索引:对频繁查询的字段添加索引

  3. 批量操作 :使用@InsertonConflict策略和@Transaction

  4. 分页查询:使用Paging库处理大量数据

  5. 观察数据变化 :使用LiveDataFlow自动更新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数据库。对于复杂对象:

  1. 使用@Embedded处理嵌套对象

  2. 使用TypeConverter处理自定义类型

  3. 使用JSON序列化处理复杂集合

  4. 使用@Relation处理对象间关系

这种方式不仅提高了开发效率,也使代码更加清晰易维护。在实际项目中,应根据具体需求选择合适的复杂对象处理策略,并注意性能优化和数据库迁移等问题。

希望本文能帮助你在Android开发中更好地以对象方式操作数据库,特别是处理复杂对象的转化问题。