拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SeventhDay+EighthDay-SQLite数据库操作:用 TaoToken 统一 Key 打通 AI 辅助建库流程

SeventhDay+EighthDay-SQLite数据库操作:用 TaoToken 统一 Key 打通 AI 辅助建库流程 1. 从一次真实的建库翻车说起Android 原生 SQLite 开发里SQLiteOpenHelper、SQLiteDatabase、ContentValues、Cursor这四个类几乎绕不开。它们能做什么简单说SQLiteOpenHelper负责建库建表和版本管理SQLiteDatabase负责执行增删改查ContentValues负责把键值对安全地塞进表里Cursor负责把查询结果一行行读出来。适合谁适合正在做 Android 课程设计、准备面试数据库八股、或者想给本地缓存加一层持久化的同学。我试过在没配好 AI 辅助环境的情况下让模型帮我补全onUpgrade逻辑结果它把oldVersion和newVersion的判断顺序写反了编译能过运行直接崩在启动页。问题不在模型本身而在于我每次对话都要重新贴一遍项目结构、包名、依赖版本上下文一断它就开始自由发挥。后来我把 TaoToken 的统一 Key 接进编码工具链把项目约定写进配置文件模型每次拿到的都是同一份上下文建库和 CRUD 的骨架才稳定下来。这篇就按「建库 → 增删改查 → 验证 → 排障」的顺序把一套可复制的 SQLite 操作骨架拆开讲同时给出config.toml和settings.json的接入片段让你把 AI 辅助编码稳定地用在数据库练习上。2. TaoToken 前置统一 Key 与两个配置文件TaoToken 在这里扮演的角色是「统一入口」你不需要在多个模型供应商之间来回切换 Key也不用把不同平台的地址散落在各个工具的配置里。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 这个不加 UTM。拿到 Key 之后控制台在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。为什么要在写 SQLite 之前先配这个因为数据库代码的「约定」特别多表名用单数还是复数、主键叫_id还是id、时间字段存INTEGER还是TEXT。这些约定如果每次对话都靠嘴说模型很容易漂移。把它们固化进配置文件模型每次读到的都是同一套规则生成的CREATE TABLE和ContentValues的 key 才不会对不上。先看config.toml的片段适合放在项目根目录或者工具约定的配置路径下# config.toml —— 项目级 AI 辅助配置 [provider] name taotoken base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY # 从环境变量读取别硬编码 [project] language kotlin min_sdk 24 package com.example.seventhday [database] helper_class MyDbHelper db_name seventh_day.db db_version 1 table_cars car column_id _id column_brand brand column_price price再看settings.json适合那些用 JSON 配置的编辑器插件或 CLI 工具{ taotoken: { baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, defaultModel: claude-sonnet, timeoutMs: 60000 }, sqliteConvention: { tablePrefix: , primaryKey: _id, useContentValues: true, cursorLoopStyle: while_moveToNext } }注意api_key_env和apiKeyEnv都指向环境变量不要把真实 Key 写进版本库。设置环境变量的方式在 macOS/Linux 下是export TAOTOKEN_API_KEY你的KeyWindows PowerShell 下是$env:TAOTOKEN_API_KEY你的Key。配好之后你在对话里只需要说「按 config.toml 里的 database 约定帮我补全 onUpgrade」模型就能拿到表名、列名、版本号不用你反复贴。这一步做完后面的建库代码才有稳定的生成基础。3. 可复制配置SQLiteOpenHelper 建库骨架3.1 继承 SQLiteOpenHelper 并重写生命周期SQLiteOpenHelper的用法就四步继承、调父类构造、重写onCreate、重写onUpgrade。onCreate只在数据库第一次创建时调用onUpgrade只在版本号变大时调用。下面这份 Kotlin 骨架可以直接抄进项目package com.example.seventhday import android.content.Context import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper class MyDbHelper(context: Context) : SQLiteOpenHelper( context, DB_NAME, null, DB_VERSION ) { companion object { const val DB_NAME seventh_day.db const val DB_VERSION 1 const val TABLE_CAR car const val COL_ID _id const val COL_BRAND brand const val COL_PRICE price private const val SQL_CREATE_CAR CREATE TABLE $TABLE_CAR ( $COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_BRAND TEXT NOT NULL, $COL_PRICE REAL NOT NULL DEFAULT 0 ) private const val SQL_DROP_CAR DROP TABLE IF EXISTS $TABLE_CAR } override fun onCreate(db: SQLiteDatabase) { db.execSQL(SQL_CREATE_CAR) // 初始数据可选这里插一条占位记录方便验证 db.execSQL( INSERT INTO $TABLE_CAR ($COL_BRAND, $COL_PRICE) VALUES (?, ?), arrayOf(demo, 0.0) ) } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { // 练习阶段直接重建生产环境请写增量迁移 if (oldVersion newVersion) { db.execSQL(SQL_DROP_CAR) onCreate(db) } } }这里有个容易踩的点onCreate里用execSQL带占位符插入时第二个参数必须是ArrayAnyKotlin 里写arrayOf(demo, 0.0)没问题但如果你写成arrayOf(demo, 0)价格会被当成整数虽然 SQLite 会做类型亲和转换但语义上不严谨。3.2 获取 SQLiteDatabase 并封装 CRUD拿到SQLiteDatabase的方式是helper.writableDatabase或helper.readableDatabase。写操作走前者纯查询走后者。下面把增删改查封装成一个 DAO方便复用package com.example.seventhday import android.content.ContentValues import android.content.Context import android.database.Cursor class CarDao(context: Context) { private val helper MyDbHelper(context) fun insert(brand: String, price: Double): Long { val db helper.writableDatabase val values ContentValues().apply { put(MyDbHelper.COL_BRAND, brand) put(MyDbHelper.COL_PRICE, price) } return db.insert(MyDbHelper.TABLE_CAR, null, values) } fun deleteById(id: Long): Int { val db helper.writableDatabase return db.delete( MyDbHelper.TABLE_CAR, ${MyDbHelper.COL_ID} ?, arrayOf(id.toString()) ) } fun updatePrice(id: Long, newPrice: Double): Int { val db helper.writableDatabase val values ContentValues().apply { put(MyDbHelper.COL_PRICE, newPrice) } return db.update( MyDbHelper.TABLE_CAR, values, ${MyDbHelper.COL_ID} ?, arrayOf(id.toString()) ) } fun queryAll(): ListCar { val db helper.readableDatabase val cursor: Cursor db.rawQuery( SELECT * FROM ${MyDbHelper.TABLE_CAR} ORDER BY ${MyDbHelper.COL_ID} ASC, null ) val result mutableListOfCar() while (cursor.moveToNext()) { val id cursor.getLong(cursor.getColumnIndexOrThrow(MyDbHelper.COL_ID)) val brand cursor.getString(cursor.getColumnIndexOrThrow(MyDbHelper.COL_BRAND)) val price cursor.getDouble(cursor.getColumnIndexOrThrow(MyDbHelper.COL_PRICE)) result.add(Car(id, brand, price)) } cursor.close() return result } } data class Car(val id: Long, val brand: String, val price: Double)ContentValues的用法就是new一个对象然后put(列名, 值)注意 key 必须和建表时的列名完全一致大小写敏感。Cursor的循环固定是while (cursor.moveToNext())取值用getColumnIndexOrThrow比getColumnIndex更安全列名写错会直接抛异常而不是返回 -1。3.3 用 AI 辅助补全时的提示词模板配好 TaoToken 之后你可以用这样的提示词让模型补全代码而不是从零生成参考 config.toml 中的 database 约定为 MyDbHelper 增加一个 onDowngrade 方法 要求版本回退时删除 car 表并重建日志用 android.util.Log 输出TAG 为 MyDbHelper。模型拿到表名、列名、版本号这些固定上下文生成的代码就能直接编译不用你手动改列名。4. 验证请求插入与查询跑通配置和代码都就位后验证动作要足够小小到一眼能看出对错。推荐在MainActivity的onCreate里加一段临时验证val dao CarDao(this) val newId dao.insert(byd, 129800.0) android.util.Log.d(SQLiteCheck, insert id $newId) val updated dao.updatePrice(newId, 119800.0) android.util.Log.d(SQLiteCheck, update rows $updated) val list dao.queryAll() list.forEach { android.util.Log.d(SQLiteCheck, car: id${it.id}, brand${it.brand}, price${it.price}) }跑起来之后在 Logcat 里过滤SQLiteCheck你应该看到类似这样的输出insert id 2 update rows 1 car: id1, branddemo, price0.0 car: id2, brandbyd, price119800.0insert返回的是新行的_idupdate返回的是受影响行数queryAll返回的列表里第一条是onCreate时插入的占位记录第二条是你刚插的。如果insert id返回 -1说明插入失败通常是ContentValues的 key 和列名对不上或者表还没创建。如果你想让模型帮你解释这段输出可以直接在对话里贴日志让它对照config.toml里的列定义分析。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 适合这种「贴日志问原因」的轻量场景。5. 本篇常见错排查5.1 表不存在no such table: car这个报错几乎都是onCreate没被调用或者数据库文件是旧版本残留。onCreate只在数据库文件第一次创建时执行如果你之前跑过一次、数据库已经存在改完建表语句再跑是不会重新执行的。解决办法有两个一是卸载应用重装二是把DB_VERSION加一让onUpgrade触发重建。练习阶段推荐后者顺便验证onUpgrade逻辑。5.2 列名对不上no such column: brandContentValues.put的 key、Cursor.getColumnIndexOrThrow的参数、建表语句里的列名这三处必须完全一致。常见错误是建表用car_brandput的时候写成brand。把列名抽成companion object里的常量三处引用同一个常量就能从根上避免。这也是为什么第 2 节的config.toml里要把列名写进去——让模型也引用同一份定义。5.3 Cursor 忘记 close 导致泄漏Cursor用完必须close()否则会泄漏游标资源查询多了之后应用会变卡甚至崩。上面的queryAll里用了cursor.close()但如果你在while循环中间returnclose就执行不到了。更稳的写法是用use扩展helper.readableDatabase.rawQuery(sql, null).use { cursor - while (cursor.moveToNext()) { // 读取逻辑 } }use会在代码块结束时自动关闭不管有没有异常。5.4 onUpgrade 里旧版本判断写反onUpgrade(db, oldVersion, newVersion)里oldVersion是设备上已有的版本newVersion是代码里声明的版本。判断条件应该是if (oldVersion newVersion)而不是反过来。写反了会导致升级时什么都不做或者降级时反而重建表。这个错误模型很容易犯因为它在生成代码时倾向于「对称写法」。把版本判断逻辑写进config.toml的注释里让模型每次都能看到正确方向。5.5 主键自增但插入时手动指定 _idINTEGER PRIMARY KEY AUTOINCREMENT的列插入时不要手动put(_id, 值)否则自增序列会被打乱。如果你确实需要指定 id用insertWithOnConflict并处理冲突策略。练习阶段直接让数据库自己分配 id 最省事。6. 把 AI 辅助稳定接进数据库练习数据库操作的练习有个特点代码量不大但约定特别密。表名、列名、版本号、主键策略、Cursor 循环方式任何一处不一致都会报错而报错信息往往只告诉你「列不存在」不告诉你哪一处写错了。把 TaoToken 的统一 Key 接进工具链再把项目约定固化进config.toml和settings.json模型每次拿到的上下文就是同一份生成的建表语句和 CRUD 代码才能对得上。如果你后面要长期做 Android 编码练习或者想让 Agent 帮你批量生成 DAO可以看看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言 SDK 的调用示例。Claude Code 相关的配置参考 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-code-anthropicutm_campaignrewrite 。最后留一个实用技巧每次改完建表语句先把DB_VERSION加一再跑验证代码看 Logcat 里的insert id和queryAll输出。如果输出里出现了旧数据和新数据混在一起说明onUpgrade的删除逻辑没生效回去检查DROP TABLE的语句有没有拼错表名。这个检查动作花不了两分钟但能帮你省掉半小时的「为什么列不存在」排查。
返回列表