一个轻量、无注解的 Android SQLite ORM,适合学习、原型和数据模型较简单的应用。
sqliteorz 基于 SQLiteOpenHelper 和反射提供建表、对象映射、CRUD、事务批量写入、
多数据库和版本迁移能力。
sqliteorz 的目标是保持简单。如果项目需要关系映射、复杂查询、响应式数据流或编译期 SQL 校验,建议使用 Room。
- 模型继承
DBBaseModel即可使用,无需注解或代码生成。 - 自动创建数据表,内置
id、createTime和updateTime字段。 - 支持单条及批量写入、条件查询、排序、分页、更新、删除和计数。
- 查询条件通过
whereArgs绑定,更新值通过ContentValues绑定。 - 批量写入自动分批并使用事务,支持
ABORT、IGNORE、REPLACE冲突策略。 - 支持延迟获取
Context、账号和数据库配置,首次 CRUD 时自动初始化并可按账号切库。 - 提供
DBResult非抛异常调用方式,明确区分未就绪、成功和执行失败。 - 支持多个数据库、非破坏性迁移及显式破坏性重建。
- 最低支持 Android 5.0(API 21),库本身没有第三方运行时依赖。
在项目的 settings.gradle 中加入 JitPack:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url "https://jitpack.io" }
}
}在应用模块中添加依赖:
dependencies {
implementation "com.github.lawnvi:sqliteorz:2.0.1"
}模型必须继承 DBBaseModel,并提供可访问的无参构造函数。Kotlin 构造参数都有默认值时,
编译器会生成可用的无参构造函数。
data class User(
var name: String = "",
var age: Int = 0,
var enabled: Boolean = true,
) : DBBaseModel() {
override fun getTableName() = "users"
}建议显式覆写 getTableName(),避免包名或类名变化影响已有表名。
支持以下字段类型及对应的可空类型:
| Kotlin 类型 | SQLite 类型 |
|---|---|
Int、Short、Long、Boolean |
INTEGER |
Float、Double |
REAL |
Char、String |
TEXT |
ByteArray |
BLOB |
建议在 Application.onCreate() 中注册模型并初始化:
val driver = DBDriver(
name = "app.db",
version = 1,
).apply {
registerModel(User::class.java)
}
OrzHelper.initialize(
applicationContext,
drivers = arrayOf(driver),
)数据库默认保存在应用内部数据库目录,不需要存储权限。
在 Xposed 等早于宿主 Application 创建的场景,可以先注册配置提供者:
OrzHelper.prepare(
keyProvider = {
currentAccountIdOrNull()?.let { "$it:schema-v2" }
},
configurationFactory = factory@{ capturedKey ->
val context = currentApplicationOrNull() ?: return@factory null
val accountId = capturedKey.removeSuffix(":schema-v2")
val driver = DBDriver(name = "$accountId.db", version = 2).apply {
registerModel(User::class.java)
}
OrzConfiguration(
context = context,
drivers = arrayOf(driver),
)
},
)keyProvider 会在 CRUD 前执行,应保持轻量且线程安全;只有 key 变化时才会调用
configurationFactory,并将已捕获的 key 传给它创建 Driver。任一提供者返回 null 时,本次访问抛出
DatabaseNotReadyException,已打开的旧库会保留但不会用于本次操作,后续访问仍可重试。
新配置成功创建后才会关闭旧库并切换。key 应同时包含账号标识和数据库配置版本;
配置未变化时必须保持同一个 key。账号注销时显式调用 OrzHelper.invalidate() 关闭旧库,
它会保留延迟配置,后续登录仍可自动初始化。
切换前会真正打开新数据库并完成建表或迁移;打开失败时旧数据库保持可恢复状态, 当前操作返回失败,不会退回旧账号继续执行。
也可以主动调用 OrzHelper.ensureInitialized() 获取是否已就绪,或用
OrzHelper.hasRetainedState() 检查是否保留了数据库实例。直接调用 initialize() 会改用立即初始化配置。
val user = User(name = "张三", age = 20)
check(user.save())
val adults = DBOrz.findModels<User>(
where = "age >= ? AND enabled = ?",
whereArgs = arrayOf("18", "1"),
orderMap = linkedMapOf("age" to -1),
)
val userById = DBOrz.findOne<User>(id = user.id)
val count = DBOrz.count<User>(
where = "age >= ?",
whereArgs = arrayOf("18"),
)为兼容旧版,单条插入发生 SQLite 错误时 save() 返回 false。需要明确异常时使用
saveOrThrow();需要结果对象时使用 runDbCatching { user.saveOrThrow() }。
orderMap 的值大于 0 表示升序,其他值表示降序。查询全部数据时显式使用
where = "1=1"。
分页查询:
val page = DBOrz.findModels<User>(
where = "1=1",
orderMap = linkedMapOf("id" to -1),
limit = 20,
offset = 40,
)所有外部输入都应通过 ? 和 whereArgs 绑定,不要直接拼接到 SQL 中。
已保存的模型可以直接操作当前记录:
user.update(mapOf("name" to "李四", "enabled" to false))
user.delete()也可以按条件批量操作:
DBOrz.update<User>(
setMaps = mapOf("enabled" to false),
where = "age < ?",
whereArgs = arrayOf("18"),
)
DBOrz.delete<User>(
where = "enabled = ?",
whereArgs = arrayOf("0"),
)为避免误操作,更新和删除全部数据时必须显式传入 where = "1=1"。
val users = List(1_000) { index ->
User(name = "user-$index", age = 18 + index % 50)
}
DBOrz.saveModels(users)批量数据会先校验全部元素,再按每 500 条一组执行事务。同一批数据必须是相同 Model、
相同数据表并属于同一数据库。默认使用 ABORT,冲突时回滚当前事务并抛出 DBException。
需要跳过冲突数据或替换旧数据时,显式指定策略:
val result = DBOrz.saveModels(users, ConflictStrategy.IGNORE)
println("写入 ${result.successCount} 条,忽略 ${result.ignoredCount} 条")
DBOrz.saveModels(users, ConflictStrategy.REPLACE)IGNORE 会跳过发生约束冲突的记录并继续写入;REPLACE 使用 SQLite 的替换语义,
可能删除冲突行后插入新行,因此主键可能变化。
除兼容旧行为的 save() 外,严格 CRUD 会在数据库错误时抛出 DBException。
不希望 ORM 异常进入宿主进程时,使用 runDbCatching 包裹数据库操作:
when (val result = runDbCatching { DBOrz.findModels<User>() }) {
is DBResult.Success -> showUsers(result.value)
DBResult.DatabaseNotReady -> waitForAccountOrApplication()
is DBResult.Failure -> reportDatabaseError(result.error)
}查询不到数据属于 Success(emptyList()),只有数据库未就绪才返回
DatabaseNotReady,其他初始化或 SQLite 错误返回 Failure。业务异常、线程中断和取消
不会被包装,应由调用方按自身逻辑处理。
每个模型只能注册到一个数据库。配置多个数据库时,需要显式指定默认数据库:
val mainDriver = DBDriver("main.db").apply {
registerModel(User::class.java)
}
val cacheDriver = DBDriver("cache.db").apply {
registerModel(CacheEntry::class.java)
}
OrzHelper.initialize(
applicationContext,
default = "main.db",
drivers = arrayOf(mainDriver, cacheDriver),
)sqliteorz 会根据模型注册关系自动选择对应数据库。
需要直接调用 SQLite API 时,把完整操作放在受保护的代码块内,避免账号切换关闭正在使用的库:
OrzHelper.withDatabase("main.db") { helper ->
helper.writableDatabase.execSQL("CREATE INDEX IF NOT EXISTS user_age ON users(age)")
}use() 和 model() 仅为兼容旧代码保留,不应长期持有其返回的 DBHelper。
数据库版本增加时应注册迁移;没有迁移策略会直接抛出异常,避免静默丢失数据:
val driver = DBDriver(name = "app.db", version = 2)
.migrateWith { db, oldVersion, _ ->
if (oldVersion < 2) {
db.execSQL("ALTER TABLE users ADD COLUMN email TEXT")
}
}
driver.registerModel(User::class.java)仅对缓存等允许丢失数据的数据库启用破坏性重建:
driver.fallbackToDestructiveMigration()字段重命名、删除、类型变化及索引变更都需要在迁移中自行处理。
需要加密、压缩等自定义写入逻辑时,可以覆写 toContentValues():
override fun toContentValues() = super.toContentValues().apply {
put("content", encrypt(content))
}如果同时需要自定义读取逻辑,可覆写 cursor2Model(),并确保读写格式一致。
- 原来的
fieldStr()、valueStr()扩展点应迁移到toContentValues()。 - 模型主键
id从Int调整为Long,使用主键变量的业务代码需要同步修改类型。 ByteArray新数据使用原生 SQLiteBLOB,读取时兼容旧版 Base64 文本。- 默认数据库改用应用内部数据库目录;检测到旧的应用专属外部目录数据库时会继续使用旧路径。
- 升级前请备份真实业务数据库,并为每个数据库版本变化提供迁移。
- 主键使用 SQLite
INTEGER PRIMARY KEY,在模型中映射为Long。 - 表结构和对象映射依赖运行时反射,没有编译期字段或 SQL 校验。
- 不提供表关联、对象关系、LiveData、Flow 或跨进程访问能力。
- 查询条件使用 SQLite selection 字符串,复杂查询可直接使用原生 SQLite 或改用 Room。