☰
Android SQLite 增删改查实战:用 TaoToken 统一 Key 打通 AI 辅助建表与 CRUD 调试
2026/9/26 13:24:13 网站建设 项目流程

1. Android SQLite 建表与 CRUD 到底难在哪

Android 本地 SQLite 是每个 App 都绕不开的持久化方案,它轻量、无需网络、随 App 一起打包,适合存联系人、订单草稿、离线缓存这类结构化数据。但真到动手写的时候,很多人会卡在三个地方:一是SQLiteOpenHelper的onCreate只在数据库第一次创建时执行,改表结构忘了升版本号,结果新字段死活不生效;二是Cursor用完不关、SQLiteDatabase频繁开关,跑久了就报SQLiteDatabaseLockedException;三是 DAO 里insert、update、delete、rawQuery的写法各不一样,参数占位符一多就容易写错。

这篇面向需要快速生成 DAO 骨架、又想顺手调试 SQL 的 Android 开发者。我会先给出一套可直接复制的PersonSQLiteOpenHelper+PersonDao完整代码,覆盖建表、插入、查询、更新、删除五个动作,再讲怎么用 TaoToken 的统一 Key 把 AI 辅助工具接进来,让它在写 DAO 骨架、排查 SQL 报错、生成测试用例时省掉反复配 Key 的麻烦。适合刚接触 Android 数据存储的新手,也适合想把 AI 编码助手接进日常开发流的老手。

2. 用 TaoToken 统一 Key 接入 AI 辅助工具

写 SQLite 代码时,我经常需要 AI 帮我做几件事:根据表结构生成 DAO 方法骨架、解释Cursor越界报错、把execSQL改写成ContentValues写法。问题是不同 AI 编码工具各要各的 Key,配一次换一个地方,很烦。TaoToken 的思路是提供一个统一的 API 入口,你申请一个 Key,就能在多个支持自定义 Base URL 的工具里复用。

它的 API 地址是https://taotoken.net/api,兼容常见的 OpenAI 风格调用格式。你需要在官网注册后到控制台创建 API Key,然后把它填进工具的配置里。对于 Android 开发场景,最实用的两个入口是:模型对话(用来问 SQL 语法、贴报错让它分析)和 Coding Plan(用来做长期的代码补全和 Agent 式改代码)。如果你用的是 Claude Code 这类工具,TaoToken 也提供了对应的接入文档。

注意:TaoToken 是 API 接入服务,不是编辑器替代品,你的 Android Studio、Gradle、SQLite 调试流程都不变,只是把 AI 工具的请求地址指向它。

具体操作路径:先访问官网https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=注册账号,进控制台创建 Key,然后按你要用的工具去接入文档里找对应的settings.json或环境变量配置方式。下面第三节我会给出一个可直接复制的配置片段。

3. 可复制的 SQLiteOpenHelper 与 DAO 配置

3.1 建表:PersonSQLiteOpenHelper

先看数据库创建类。核心是构造函数里传数据库名和版本号,onCreate里写建表语句。这里用person.db、版本 1,表结构是id自增主键、name、number两个文本字段。

package com.examp.use_SQLite; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class PersonSQLiteOpenHelper extends SQLiteOpenHelper { public PersonSQLiteOpenHelper(Context context) { super(context, "person.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table person (id integer primary key autoincrement," + "name varchar(20), number varchar(20))"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 版本升级时在这里做表结构迁移 } }

这里有个坑要提前说:onCreate只在数据库文件不存在时调用一次。你如果改了建表语句但版本号还是 1,App 重装前新表结构不会生效。调试阶段最省事的做法是卸载 App 或清应用数据,让数据库重建。

3.2 实体类 Person

实体类就是三个字段加 getter/setter,toString方便测试时打印。

package com.examp.use_SQLite.domain; public class Person { private int id; private String name; private String number; public Person() {} public Person(int id, String name, String number) { this.id = id; this.name = name; this.number = number; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } @Override public String toString() { return "Person [id=" + id + ", name=" + name + ", number=" + number + "]"; } }

3.3 DAO 骨架:增删改查五个方法

DAO 是重点。插入用ContentValues是官方推荐写法,查询用rawQuery拿Cursor,更新和删除用execSQL带占位符。注意每个方法里db.close()的位置,以及Cursor的关闭。

package com.examp.use_SQLite.dao; import java.util.ArrayList; import java.util.List; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.examp.use_SQLite.PersonSQLiteOpenHelper; import com.examp.use_SQLite.domain.Person; public class PersonDao { private PersonSQLiteOpenHelper helper; public PersonDao(Context context) { helper = new PersonSQLiteOpenHelper(context); } // 增 public void add(String name, String number) { SQLiteDatabase db = helper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("name", name); values.put("number", number); db.insert("person", null, values); db.close(); } // 查:按姓名 public Person findByName(String name) { SQLiteDatabase db = helper.getReadableDatabase(); Cursor cursor = db.rawQuery("select * from person where name=?", new String[]{name}); Person person = null; if (cursor.moveToNext()) { int id = cursor.getInt(cursor.getColumnIndex("id")); String named = cursor.getString(cursor.getColumnIndex("name")); String number = cursor.getString(cursor.getColumnIndex("number")); person = new Person(id, named, number); } cursor.close(); db.close(); return person; } // 改 public void update(String name, String newnumber) { SQLiteDatabase db = helper.getWritableDatabase(); db.execSQL("update person set number=? where name=?", new Object[]{newnumber, name}); db.close(); } // 删 public void delete(String name) { SQLiteDatabase db = helper.getWritableDatabase(); db.execSQL("delete from person where name=?", new Object[]{name}); db.close(); } // 查全部 public List<Person> findAll() { SQLiteDatabase db = helper.getReadableDatabase(); Cursor cursor = db.rawQuery("select * from person", null); List<Person> persons = new ArrayList<Person>(); while (cursor.moveToNext()) { int id = cursor.getInt(cursor.getColumnIndex("id")); String named = cursor.getString(cursor.getColumnIndex("name")); String number = cursor.getString(cursor.getColumnIndex("number")); persons.add(new Person(id, named, number)); } cursor.close(); db.close(); return persons; } }

3.4 测试类与 Manifest 配置

用AndroidTestCase跑单元测试,每个方法对应一个 CRUD 动作。Manifest 里要加instrumentation和uses-library,否则测试跑不起来。

package com.examp.use_SQLite.test; import java.util.List; import com.examp.use_SQLite.PersonSQLiteOpenHelper; import com.examp.use_SQLite.dao.PersonDao; import com.examp.use_SQLite.domain.Person; import android.test.AndroidTestCase; public class TestPerson extends AndroidTestCase { public void testCreateDB() throws Exception { PersonSQLiteOpenHelper helper = new PersonSQLiteOpenHelper(getContext()); helper.getWritableDatabase(); } public void testAdd() { PersonDao dao = new PersonDao(getContext()); dao.add("donghongyu", "1231231"); } public void testFindByname() { PersonDao dao = new PersonDao(getContext()); Person person = dao.findByName("lisi"); System.out.println(person.toString()); } public void testUpdate() { PersonDao dao = new PersonDao(getContext()); dao.update("lisi", "11111111111"); } public void testDelete() { PersonDao dao = new PersonDao(getContext()); dao.delete("lisi"); } public void testFindAll() { PersonDao dao = new PersonDao(getContext()); List<Person> persons = dao.findAll(); for (Person person : persons) { System.out.println(person); } } }

Manifest 关键片段:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.examp.use_SQLite" android:versionCode="1" android:versionName="1.0"> <instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="com.examp.use_SQLite" /> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> <uses-library android:name="android.test.runner" /> <activity android:name="com.examp.use_SQLite.MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

3.5 TaoToken 的 settings.json 配置片段

如果你用支持自定义 Base URL 的 AI 编码工具,配置大致长这样。把api_key换成你在控制台创建的那个 Key,base_url指向 TaoToken 的 API 地址。

{ "ai_provider": { "base_url": "https://taotoken.net/api", "api_key": "sk-你的TaoToken密钥", "model": "claude-sonnet" } }

配好之后,你在工具里问「帮我给 person 表加一个 age 字段并生成迁移代码」,或者把CursorIndexOutOfBoundsException贴进去让它分析,请求都会走 TaoToken 这个统一入口。需要长期做代码补全和 Agent 改代码的话,可以看 Coding Plan 的接入方式;只是临时问 SQL 语法,用模型对话就够了。

4. 验证请求与预期结果

代码写完要验证。最直接的方式是跑TestPerson里的测试方法,看 Logcat 输出。按下面顺序执行,每一步都有明确的预期结果。

第一步,跑testCreateDB。预期:数据库person.db在/data/data/com.examp.use_SQLite/databases/下生成,Logcat 无异常。如果报no such table,说明onCreate没执行,检查数据库名和版本号。

第二步,跑testAdd,插入donghongyu / 1231231。预期:无异常,数据库里多一行。可以紧接着跑testFindAll,预期打印出Person [id=1, name=donghongyu, number=1231231]。

第三步,跑testFindByname查lisi。如果之前没插过 lisi,预期person为 null,打印时会抛NullPointerException,这是正常的,说明查询逻辑走到了「无结果」分支。想看到结果,先testAdd插一条 lisi 再查。

第四步,跑testUpdate,把 lisi 的 number 改成11111111111。预期:无异常,再跑testFindByname("lisi")时 number 变成新值。

第五步,跑testDelete删 lisi。预期:无异常,再查 lisi 返回 null,testFindAll里不再有 lisi。

如果你用 AI 工具辅助,可以把每一步的 Logcat 输出贴给模型对话,让它判断是否符合预期。比如贴Person [id=1, name=donghongyu, number=1231231]问「这个输出说明插入和查询都成功了吗」,它会帮你确认。

5. 本篇常见错误排查

5.1 no such table: person

最常见。原因通常是onCreate没被调用,或者数据库版本号没变但表结构改了。排查:确认PersonSQLiteOpenHelper构造函数里的数据库名和onCreate里的表名一致;调试阶段直接卸载 App 或清应用数据,强制重建数据库。如果是在onUpgrade里改表,记得把版本号从 1 改成 2。

5.2 CursorIndexOutOfBoundsException

Cursor的游标位置不对。moveToNext()返回 false 时说明没有下一行,此时调getInt、getString就会越界。正确写法是先判断if (cursor.moveToNext())或while (cursor.moveToNext()),再取值。另外getColumnIndex返回 -1 时也会出问题,说明字段名拼错了,检查 SQL 里的列名和getColumnIndex的参数是否一致。

5.3 SQLiteDatabaseLockedException

数据库被占用。常见于多线程同时读写,或者上一个SQLiteDatabase没关就开新的。排查:确认每个方法里db.close()都执行到了;如果多线程访问,考虑用单例持有SQLiteOpenHelper,而不是每次 new 一个。getWritableDatabase和getReadableDatabase返回的可能是同一个连接,不要重复关闭。

5.4 测试类跑不起来

AndroidTestCase需要在 Manifest 里声明instrumentation和uses-library,缺一个都会报InstrumentationTestRunner not found。另外targetPackage要和manifest的package一致。如果用的是新版 Android Studio,android.test相关 API 已废弃,建议迁移到androidx.test,但本文的代码在旧项目里仍可直接用。

5.5 AI 工具请求失败

配了 TaoToken 之后如果请求报 401,先检查 Key 是否复制完整、有没有多余空格。报 404 通常是base_url写错了,确认是https://taotoken.net/api而不是带其他路径。模型名写错会报 400,去接入文档里核对当前支持的模型标识。这些报错都可以直接贴给模型对话让它帮你定位。

6. 把 AI 接进 Android 数据层开发流

SQLite 的 CRUD 代码本身不复杂,难的是表结构一变、字段一多,DAO 里几十个方法要同步改,手写容易漏。我的做法是把 TaoToken 的 Key 配进 AI 编码工具,让它根据PersonSQLiteOpenHelper里的建表语句自动生成 DAO 骨架,改表时再让它生成对应的迁移代码和测试用例。这样建表和 CRUD 调试这两块最耗时的活就交出去了。

具体入口按你的需求选:只是临时问 SQL 语法、贴报错分析,用模型对话;要长期做代码补全、Agent 式改代码,看 Coding Plan;Key 的管理和创建在控制台;接入细节在接入文档。如果你用 Claude Code,也有对应的接入说明。把这些配好,Android 本地数据库这块的开发节奏会顺很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询