Android 入门教程
SQLite 数据库
本教程共 100 篇 · 第 67 篇 · 更新于 2026-07-28 · 约 9 分钟阅读
AndroidAndroid 入门教程SQLiteSQLiteOpenHelper数据库CRUD事务
67. SQLite 数据库
本节目标:学会用 SQLiteOpenHelper 创建和管理数据库,掌握增删改查(CRUD)操作和事务处理,为下一章学习 Room 打好基础。
SQLite 是什么
SQLite 是 Android 内置的轻量级关系型数据库。它不需要独立的数据库服务进程,整个数据库就是一个文件,适合移动设备存储结构化数据。
打个比方:如果 SharedPreferences 是便签纸,那 SQLite 就是 Excel 表格。你能建多张表、设主键、做查询、排序过滤,适合存大量有条理的数据。
Note
- 官方现在推荐用 Room 持久化库替代直接使用 SQLite API(第 68 章会讲)。Room 是 SQLite 的封装,减少了样板代码、提供了编译期 SQL 检查和协程支持。但理解原生 SQLite 是学习 Room 的前提。
SQLiteOpenHelper
SQLiteOpenHelper 是管理数据库创建和升级的帮手类。继承它需要实现两个方法:
onCreate():数据库首次创建时调用,建表。onUpgrade():数据库版本号升高时调用,做表结构迁移。
class AppDatabase(context: Context) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
companion object {
const val DB_NAME = "app.db"
const val DB_VERSION = 1
}
override fun onCreate(db: SQLiteDatabase) {
// 建表
db.execSQL("""
CREATE TABLE users (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER,
email TEXT
)
""")
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
// 版本升级时执行(简单做法:删表重建,生产环境要写迁移逻辑)
db.execSQL("DROP TABLE IF EXISTS users")
onCreate(db)
}
}
使用
val dbHelper = AppDatabase(context)
val db = dbHelper.writableDatabase // 可读写的数据库对象
// 或
val db = dbHelper.readableDatabase // 只读
Warning
onUpgrade里直接DROP TABLE再重建会丢失数据!这只是演示。生产环境要写增量迁移,比如ALTER TABLE users ADD COLUMN phone TEXT。每次升级只加改动部分。
增(Insert)
fun addUser(db: SQLiteDatabase, name: String, age: Int, email: String): Long {
val values = ContentValues().apply {
put("name", name)
put("age", age)
put("email", email)
}
// 返回新行的 id
return db.insert("users", null, values)
}
// 使用
val dbHelper = AppDatabase(context)
val db = dbHelper.writableDatabase
val newId = addUser(db, "张三", 25, "zhangsan@example.com")
Note
ContentValues是键值对容器,存放列名和值。insert()第二个参数nullColumnHack:当 values 为空时,插入一个 null 值的列名,防止INSERT INTO users () VALUES ()这种非法 SQL。传null通常即可。
查(Query)
用 query 方法
fun queryUsers(db: SQLiteDatabase): List<User> {
val cursor = db.query(
"users", // 表名
arrayOf("_id", "name", "age"), // 查询列
"age > ?", // WHERE 条件
arrayOf("20"), // 条件参数
null, // GROUP BY
null, // HAVING
"age DESC" // ORDER BY
)
val users = mutableListOf<User>()
cursor.use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow("_id"))
val name = it.getString(it.getColumnIndexOrThrow("name"))
val age = it.getInt(it.getColumnIndexOrThrow("age"))
users.add(User(id, name, age))
}
}
return users
}
用 rawQuery 写 SQL
val cursor = db.rawQuery(
"SELECT * FROM users WHERE age > ? ORDER BY age DESC",
arrayOf("20")
)
Tip
- 无论用
query还是rawQuery,条件值都用?占位符配合参数数组,不要拼接字符串。拼接字符串有 SQL 注入风险,和第 38 章 ContentProvider 讲的一样。?会被安全替换。
Cursor 的使用
cursor.use { // use 自动关闭 cursor
// 移动到第一行
if (it.moveToFirst()) {
do {
val name = it.getString(it.getColumnIndexOrThrow("name"))
// 处理每一行
} while (it.moveToNext())
}
}
// 便捷方法
val count = cursor.count // 总行数
val position = cursor.position // 当前行位置
Warning
- Cursor 用完必须关闭!用
cursor.use { }自动关闭,或手动cursor.close()。不关闭会导致内存泄漏和数据库锁定。getColumnIndexOrThrow在列名不存在时抛异常,比getColumnIndex返回 -1 更容易发现问题。
改(Update)
fun updateUser(db: SQLiteDatabase, id: Long, newName: String): Int {
val values = ContentValues().apply {
put("name", newName)
}
return db.update(
"users",
values,
"_id = ?",
arrayOf(id.toString())
)
// 返回受影响的行数
}
删(Delete)
fun deleteUser(db: SQLiteDatabase, id: Long): Int {
return db.delete("users", "_id = ?", arrayOf(id.toString()))
// 返回删除的行数
}
事务
多个操作要么全成功要么全回滚,用事务:
fun transferData(db: SQLiteDatabase) {
db.beginTransaction()
try {
db.execSQL("UPDATE users SET age = age + 1 WHERE _id = 1")
db.execSQL("UPDATE users SET age = age - 1 WHERE _id = 2")
db.setTransactionSuccessful() // 标记成功
} finally {
db.endTransaction() // 结束事务(没标记成功则回滚)
}
}
Note
setTransactionSuccessful()必须在endTransaction()之前调用,否则事务回滚。放在try块最后一行,endTransaction()放finally里保证一定执行。事务的好处:一是原子性(全成功或全回滚),二是性能(批量操作在一个事务里比逐条执行快很多)。
数据库升级
版本号升高时做增量迁移:
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
// 从 1 升到 2:加一列
if (oldVersion < 2) {
db.execSQL("ALTER TABLE users ADD COLUMN phone TEXT")
}
// 从 2 升到 3:再加一列
if (oldVersion < 3) {
db.execSQL("ALTER TABLE users ADD COLUMN avatar TEXT")
}
// 用 if 判断而非 switch,因为用户可能跨版本升级(从 1 直接升到 3)
}
Tip
- 用
if (oldVersion < N)而非switch,因为用户可能跳过中间版本(从 1 直接升到 3)。每个 if 独立执行,保证所有迁移都跑一遍。
在协程中使用
数据库操作必须在 IO 线程:
class UserRepository(private val dbHelper: AppDatabase) {
suspend fun addUser(name: String, age: Int): Long = withContext(Dispatchers.IO) {
val db = dbHelper.writableDatabase
val values = ContentValues().apply {
put("name", name)
put("age", age)
}
db.insert("users", null, values)
}
suspend fun getAllUsers(): List<User> = withContext(Dispatchers.IO) {
val db = dbHelper.readableDatabase
val cursor = db.query("users", null, null, null, null, null, "name ASC")
val users = mutableListOf<User>()
cursor.use {
while (it.moveToNext()) {
users.add(User(
it.getLong(it.getColumnIndexOrThrow("_id")),
it.getString(it.getColumnIndexOrThrow("name")),
it.getInt(it.getColumnIndexOrThrow("age"))
))
}
}
users
}
}
Warning
- 数据库读写不要在主线程执行,会触发
StrictMode异常或卡 UI。用withContext(Dispatchers.IO)切到 IO 线程。SQLiteOpenHelper 本身不是线程安全的,多线程同时写要靠事务保证安全。
SQLite 的局限
原生 SQLite API 有不少痛点:
- 样板代码多:每次查询都要写 ContentValues、Cursor 遍历、列名转换。
- 没有编译期检查:SQL 写错了运行时才报。
- 类型不安全:列名是字符串,拼错不报错。
- 手动管理 Cursor:忘了关就泄漏。
这些问题正是 Room 要解决的。Room 在 SQLite 之上提供了:
- 用注解定义表和查询
- 编译期检查 SQL
- 自动生成样板代码
- 协程和 Flow 支持
小结
SQLiteOpenHelper 管理数据库的创建和升级。CRUD 用 insert/query/update/delete 方法或 execSQL/rawQuery 写 SQL。条件值用 ? 占位符防注入。事务用 beginTransaction + setTransactionSuccessful + endTransaction 保证原子性。数据库操作放 IO 线程。理解了原生 SQLite,下一章学 Room 就水到渠成了。