☰
SQLite数据库笔记:用TaoToken统一Key打通SQL查询与ListView数据展示
2026/9/28 11:32:38 网站建设 项目流程

1. 从一次真实的 Android 本地数据翻车说起

SQLite 是 Android 系统内置的轻量级关系型数据库,不需要单独安装服务端,一个.db文件就能存下整张表的数据。它能做增删改查、事务、分页,适合谁?适合所有需要在手机本地持久化结构化数据的场景,比如通讯录、记账本、离线缓存列表。而 ListView 是把这些数据渲染到屏幕上的经典控件,两者配合就是 Android 入门阶段最核心的一条数据链路:建库建表 → SQL 增删改查 → Cursor 读取 → 封装 Bean → Adapter 绑定 → ListView 渲染。

我见过太多人卡在中间某一环:表建好了但查询返回空 Cursor,或者数据明明查出来了 ListView 却一片空白。问题往往不在 SQL 本身,而在几个固定位置——onCreate没执行、列名拼错、getCount()返回 0、getView()里没复用 convertView。这篇笔记就按这条链路走一遍,交付可直接复制的DBHelper与BaseAdapter骨架,同时演示怎么用 TaoToken 的统一 Key 接入 AI 辅助生成和检查 SQL 语句,把「写 SQL 靠猜」变成「写 SQL 有验证」。

2. TaoToken 前置:统一 Key 与 API 通道准备

TaoToken 在这里的角色是一个统一的模型调用入口。你不需要为每个模型单独申请一套凭证,一个 Key 就能走通对话、代码生成等通道。对于本篇场景,我们主要用它来辅助生成 SQL 语句、解释 Cursor 报错、检查建表字段类型是否合理。

先拿到凭证。打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册后进入控制台,在 API Keys 页面创建一个新 Key。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。创建后立刻复制保存,页面刷新后就不再完整显示。

接口基地址统一用 https://taotoken.net/api ,注意这个地址不带任何查询参数。调用时把 Key 放在请求头的 Authorization 字段里,格式是Bearer 你的Key。如果你只是想先在网页上验证模型能不能正常回答 SQL 相关问题,可以直接用模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 试一句「帮我写一条查询 person 表里 money 大于 5000 的 SQL」,看返回是否符合预期,再去写代码。

注意:Key 只存在于你的本地配置或环境变量里,不要硬编码进提交到 Git 的源码。Android 项目里可以放在local.properties或gradle.properties,通过 BuildConfig 注入。

3. 可复制配置:DBHelper 与 Adapter 骨架

3.1 建库建表:MyOpenHelper 完整写法

SQLite 在 Android 里通过SQLiteOpenHelper管理数据库的创建和版本升级。必须定义一个构造方法,参数依次是上下文、数据库文件名、游标工厂(一般传 null)、版本号。数据库第一次被创建时会回调onCreate,版本号变大时会回调onUpgrade。

public class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table person (" + "_id integer primary key autoincrement, " + "name char(10), " + "phone char(20), " + "money integer(20))"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("drop table if exists person"); onCreate(db); } }

获取数据库对象时,getWritableDatabase()打开可读写库,getReadableDatabase()在磁盘空间不足时退化为只读,否则同样返回可读写库。实际开发中直接用getWritableDatabase()即可。

MyOpenHelper oh = new MyOpenHelper(getContext(), "person.db", null, 1); SQLiteDatabase db = oh.getWritableDatabase();

3.2 增删改查:execSQL 与 API 两种方式

原生 SQL 方式适合复杂语句,execSQL支持占位符,能防注入:

db.execSQL("insert into person (name, phone, money) values (?, ?, ?);", new Object[]{"张三", "15987461", 75000}); Cursor cs = db.rawQuery("select _id, name, money from person where name = ?;", new String[]{"张三"});

API 方式更类型安全,插入返回主键(出错返回 -1),删除和修改返回影响行数:

ContentValues cv = new ContentValues(); cv.put("name", "刘能"); cv.put("phone", "1651646"); cv.put("money", 3500); long rowId = db.insert("person", null, cv); int deleted = db.delete("person", "_id = ? and name = ?", new String[]{"1", "张三"}); ContentValues updateCv = new ContentValues(); updateCv.put("money", 25000); int updated = db.update("person", updateCv, "name = ?", new String[]{"赵四"}); Cursor queryCs = db.query("person", new String[]{"name", "money"}, "name = ?", new String[]{"张三"}, null, null, null); while (queryCs.moveToNext()) { String name = queryCs.getString(queryCs.getColumnIndex("name")); String money = queryCs.getString(queryCs.getColumnIndex("money")); System.out.println(name + ";" + money); }

事务用于保证多条 SQL 要么全成功要么全失败,典型场景是转账:

try { db.beginTransaction(); // 多条 SQL 操作 db.setTransactionSuccessful(); } finally { db.endTransaction(); }

3.3 用 TaoToken 辅助生成与检查 SQL

写 SQL 时最容易犯的错是列名拼错、类型不匹配、where 条件漏占位符。这时候可以把表结构贴给模型,让它生成语句并解释。用 curl 走 TaoToken 的 API 通道:

curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [ {"role": "user", "content": "表结构:person(_id integer primary key autoincrement, name char(10), phone char(20), money integer(20))。请生成一条查询 money 大于 5000 且按 money 降序排列的 SQL,并说明每个字段含义。"} ] }'

返回的 SQL 可以直接粘进rawQuery验证。如果模型给的列名和你的建表语句不一致,说明你的表结构描述有歧义,这正是提前发现问题的价值。长期做 Android 开发、需要频繁让模型辅助生成代码片段的,可以了解 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。

3.4 ListView 绑定:BaseAdapter 骨架

ListView 遵循 MVC 结构:Model 是要显示的数据集合,View 是 ListView 本身,Controller 是 Adapter。每个条目都是一个 View 对象。BaseAdapter 必须实现getCount()和getView()。

public class PersonAdapter extends BaseAdapter { private List<Person> people; private Context context; public PersonAdapter(Context context, List<Person> people) { this.context = context; this.people = people; } @Override public int getCount() { return people.size(); } @Override public Object getItem(int position) { return people.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.item_person, parent, false); holder = new ViewHolder(); holder.tvName = convertView.findViewById(R.id.tv_name); holder.tvMoney = convertView.findViewById(R.id.tv_money); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } Person p = people.get(position); holder.tvName.setText(p.getName()); holder.tvMoney.setText(String.valueOf(p.getMoney())); return convertView; } static class ViewHolder { TextView tvName; TextView tvMoney; } }

从数据库读取全部数据并封装成集合:

List<Person> people = new ArrayList<>(); Cursor cs = db.query("person", null, null, null, null, null, null); while (cs.moveToNext()) { String name = cs.getString(cs.getColumnIndex("name")); String phone = cs.getString(cs.getColumnIndex("phone")); String money = cs.getString(cs.getColumnIndex("money")); people.add(new Person(name, phone, money)); } cs.close(); ListView lv = findViewById(R.id.lv); lv.setAdapter(new PersonAdapter(this, people));

分页查询用limit参数,db.query的最后一个参数传"0, 10"表示从第 0 条取 10 条。

4. 验证请求与成功结果

4.1 验证 TaoToken 通道是否通

先用一条最小请求确认 Key 和网络都正常:

curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"回复 OK"}]}'

返回 JSON 里choices[0].message.content包含内容,说明通道正常。如果返回 401,检查 Key 是否复制完整;返回 404,检查路径是否写成了带 UTM 的地址——接口地址就是https://taotoken.net/api,不要加多余参数。

4.2 验证数据库写入与查询

插入一条数据后立刻查回来,确认onCreate确实执行了:

Cursor check = db.rawQuery("select count(*) from person", null); if (check.moveToFirst()) { Log.d("SQLiteCheck", "总行数=" + check.getInt(0)); } check.close();

如果这里抛no such table: person,说明onCreate没被调用,通常是数据库文件已存在但版本号没变,或者构造方法参数传错。

4.3 验证 ListView 渲染

在getView里打一行日志,观察调用次数:

Log.d("ListViewCheck", "getView position=" + position + " convertView=" + (convertView == null ? "null" : "reused"));

屏幕能显示几个条目,getView就被调用几次。向下滑动时继续调用,但convertView应该开始出现reused,说明复用生效。如果每次都是null,检查是否在getView里错误地每次都inflate而没有判断convertView == null。

5. 本篇常见错排查

报错一:no such table: person原因通常是数据库已存在但表没建。onCreate只在数据库文件第一次创建时调用。解决办法:卸载应用重装,或把版本号从 1 改成 2 触发onUpgrade,或在onUpgrade里先 drop 再 create。

报错二:Cursor 返回 0 行但表里明明有数据检查where条件的占位符和参数数组是否一一对应。db.query("person", null, "name = ?", new String[]{"张三"}, ...)里问号数量必须和数组长度一致。另外注意char类型在 SQLite 里实际按 TEXT 存储,比较时不要混用数字和字符串。

报错三:ListView 一片空白先确认getCount()返回值大于 0。如果集合是空的,回到 Cursor 那一步打印行数。如果getCount()正常但界面空白,检查item_person.xml的根布局高度是不是wrap_content,以及setAdapter是否在数据加载完成之后调用。

报错四:getView里findViewById返回 null检查inflate的布局文件里控件 id 是否和代码里一致。用 ViewHolder 模式时,setTag和getTag要成对出现,否则复用时会拿到错误的控件引用。

报错五:TaoToken 返回 401 或 403Key 失效或格式不对。确认请求头是Authorization: Bearer sk-xxx,中间有一个空格。如果 Key 是在控制台刚创建的,等几秒再试。模型名写错也会返回错误,先用模型对话页确认可用模型名。

报错六:主线程查询导致 ANR数据量大时db.query放在主线程会卡界面。正确做法是把查询放到子线程,查完通过 Handler 或 runOnUiThread 更新 Adapter 并调用notifyDataSetChanged()。

6. 继续把这条链路用起来

数据库和 ListView 这条链路跑通之后,下一步可以做的事很具体:把PersonAdapter换成SimpleAdapter或ArrayAdapter对比体验,给查询加分页参数做上拉加载,把事务用在批量插入上。需要模型帮你生成复杂 SQL 或解释 Cursor 报错时,走模型对话 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 最快;要在 Android 项目里长期集成代码辅助,看 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ;接入细节和参数说明在文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。Key 管理和新建在 API Keys https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。接口基地址始终是 https://taotoken.net/api 。

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

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

立即咨询