427 lines
9.9 KiB
Markdown
427 lines
9.9 KiB
Markdown
# SQLite3 数据库操作模块技术文档
|
||
|
||
## 概述
|
||
|
||
该模块提供了一个轻量级的 SQLite3 数据库封装类 `SQLite3`,支持多线程安全访问、字符编码转换(本地化与 UTF-8)、自动数据库路径管理,并通过 `Record` 类将查询结果封装为对象。模块还集成了日志记录和公共数据存储机制。
|
||
|
||
主要功能包括:
|
||
- 自动连接与重连数据库
|
||
- 多线程隔离属性管理
|
||
- SQL 执行与结果遍历
|
||
- 表结构查询(表名、字段)
|
||
- 事务控制(提交、回滚)
|
||
- 字符串编码处理(str ↔ unicode)
|
||
- 数据库路径自动生成与缓存
|
||
|
||
---
|
||
|
||
## 模块依赖
|
||
|
||
```python
|
||
import os, sys
|
||
import thread
|
||
from sqlite3 import dbapi2 as sqlite
|
||
import time
|
||
from localefunc import *
|
||
from folderUtils import mkdir
|
||
from PublicData import public_data
|
||
from mylog import mylog
|
||
```
|
||
|
||
### 第三方/内部模块说明:
|
||
|
||
| 模块 | 功能 |
|
||
|------|------|
|
||
| `localefunc` | 提供 `localeString()` 和 `local_encoding` 变量,用于本地字符串处理 |
|
||
| `folderUtils.mkdir` | 确保目录存在(创建目录) |
|
||
| `PublicData.public_data` | 全局共享数据容器,用于缓存数据库实例和路径 |
|
||
| `mylog` | 日志输出函数 |
|
||
|
||
---
|
||
|
||
## 公共函数
|
||
|
||
### `logit(s)`
|
||
记录当前文件名及消息到日志系统。
|
||
|
||
**参数:**
|
||
- `s` (str) - 要记录的日志内容
|
||
|
||
**示例:**
|
||
```python
|
||
logit("初始化数据库")
|
||
# 输出: __file__: 初始化数据库
|
||
```
|
||
|
||
---
|
||
|
||
### `str2unicode(s)`
|
||
将字符串转换为 Unicode,尝试使用本地编码或 UTF-8 解码。
|
||
|
||
**参数:**
|
||
- `s` (str 或其他类型) - 输入字符串
|
||
|
||
**返回值:**
|
||
- 成功时返回 `unicode` 对象
|
||
- 若解码失败,则返回 `buffer(s)`
|
||
- 非字符串类型原样返回
|
||
|
||
**编码优先级:**
|
||
1. `local_encoding`(来自 `localefunc`)
|
||
2. `'utf8'`
|
||
3. fallback 到 `buffer`
|
||
|
||
---
|
||
|
||
### `unicode2str(s)`
|
||
将 Unicode 或其他类型转换为适合输出的字符串形式。
|
||
|
||
**参数:**
|
||
- `s` - 待转换的数据
|
||
|
||
**返回值:**
|
||
| 类型 | 转换方式 |
|
||
|------|---------|
|
||
| `int` / `long` | 返回 `long(s)` |
|
||
| `buffer` | 转为 `str(s)` |
|
||
| `unicode` | `.encode('utf8')` |
|
||
| 其他 | 原样返回 |
|
||
|
||
---
|
||
|
||
### `argConvert(args)`
|
||
递归地将参数中的字符串转为 Unicode,支持多种数据结构。
|
||
|
||
**参数:**
|
||
- `args` - 可为 `tuple`, `list`, `dict`, 或单个值
|
||
|
||
**行为:**
|
||
- 列表或元组 → 对每个元素调用 `str2unicode`
|
||
- 字典 → 对每个值调用 `str2unicode`
|
||
- None → 返回 None
|
||
- 其他 → 直接返回
|
||
|
||
**用途:** 在执行 SQL 前统一编码格式
|
||
|
||
---
|
||
|
||
## 核心类:`Record`
|
||
|
||
表示一条数据库记录,字段作为属性访问。
|
||
|
||
### 构造函数:`__init__(self, data, localize=False)`
|
||
**参数:**
|
||
- `data` (dict) - 键值对数据(如从数据库取出)
|
||
- `localize` (bool) - 是否对字符串字段应用 `localeString()`
|
||
|
||
**逻辑:**
|
||
- 所有键名转小写后设为对象属性
|
||
- 若 `localize=True` 且值是字符串,则调用 `localeString()`
|
||
|
||
**示例:**
|
||
```python
|
||
r = Record({'Name': '张三', 'Age': 25}, localize=True)
|
||
print(r.name) # 输出经过 localeString 处理后的名字
|
||
print(r.age) # 25
|
||
```
|
||
|
||
### `__getattr__(self, name)`
|
||
实现属性访问时统一转为小写。
|
||
|
||
> ⚠️ **注意:此方法存在无限递归风险!**
|
||
>
|
||
> 实现中直接调用了 `getattr(self, name)` 而不是访问 `__dict__`,会导致死循环。
|
||
>
|
||
> ✅ 正确做法应为:
|
||
> ```python
|
||
> try:
|
||
> return self.__dict__[name.lower()]
|
||
> except KeyError:
|
||
> raise AttributeError(name)
|
||
> ```
|
||
|
||
---
|
||
|
||
### `__str__(self)`
|
||
返回对象所有属性的格式化字符串。
|
||
|
||
**返回格式:**
|
||
```
|
||
[field1 : value1
|
||
field2 : value2]
|
||
```
|
||
|
||
---
|
||
|
||
## 核心类:`SQLite3`
|
||
|
||
封装了 SQLite3 的连接、执行、查询等操作。
|
||
|
||
### 构造函数:`__init__(self, dbpath, localize=False)`
|
||
**参数:**
|
||
- `dbpath` (str) - 数据库文件路径
|
||
- `localize` (bool) - 查询结果是否进行本地化处理(传给 `Record`)
|
||
|
||
**初始化动作:**
|
||
- 创建线程隔离映射 `threadMap`
|
||
- 调用 `_connection(dbpath)` 建立连接
|
||
|
||
---
|
||
|
||
### 私有方法:`_connection(dbpath=None)`
|
||
重新建立数据库连接。
|
||
|
||
**参数:**
|
||
- `dbpath` - 新的数据库路径(可选)
|
||
|
||
**行为:**
|
||
- 更新 `self.dbpath`
|
||
- 创建新的 `sqlite.Connection` 和 `cursor`
|
||
- 重置 `result`, `sqlcmd`
|
||
|
||
---
|
||
|
||
### 特殊方法:`__setattr__` 和 `__getattr__`
|
||
实现**线程局部属性存储**,确保不同线程不会互相干扰属性读写。
|
||
|
||
#### 工作原理:
|
||
- 使用 `thread.get_ident()` 获取当前线程 ID
|
||
- 每个线程在 `self.threadMap[thread_id]` 中拥有独立命名空间
|
||
- 属性读写仅影响当前线程
|
||
|
||
> ✅ 这是一种模拟“线程局部变量”的方式,避免并发冲突
|
||
|
||
---
|
||
|
||
### 方法:`tables()`
|
||
获取数据库中所有用户表名。
|
||
|
||
**SQL 查询:**
|
||
```sql
|
||
SELECT * FROM sqlite_master WHERE type='table'
|
||
```
|
||
|
||
**返回值:**
|
||
- `list` of table names (`str`)
|
||
|
||
**流程:**
|
||
1. 执行查询
|
||
2. 遍历结果,提取 `.name` 字段
|
||
3. 返回表名列表
|
||
|
||
---
|
||
|
||
### 方法:`columns(tablename)`
|
||
⚠️ **存在拼写错误:形参名为 `tablenmae`,但使用了 `tablename`**
|
||
|
||
应修正为:
|
||
```python
|
||
def columns(self, tablename):
|
||
self.SQL('select * from %s' % tablename)
|
||
self.desc = self.results.description
|
||
return self.desc
|
||
```
|
||
|
||
**功能:**
|
||
- 查询指定表的一条记录以获取字段描述
|
||
- 返回 `cursor.description`(包含字段名、类型等信息)
|
||
|
||
---
|
||
|
||
### 方法:`FETCHALL()`
|
||
执行 `fetchall()` 并返回全部结果。
|
||
|
||
> ❌ 当前实现有问题:
|
||
```python
|
||
r = True
|
||
r = self.cursor.fetchall()
|
||
return r
|
||
```
|
||
第一行赋值无意义,建议改为:
|
||
```python
|
||
def FETCHALL(self):
|
||
if self.results:
|
||
return self.cursor.fetchall()
|
||
return []
|
||
```
|
||
|
||
---
|
||
|
||
### 方法:`_eatCursorNext()`
|
||
清空游标中未消费的结果,防止 `ProgrammingError`。
|
||
|
||
**目的:**
|
||
- 避免多次调用 `next()` 导致异常
|
||
- 在新查询前清理旧状态
|
||
|
||
**注意:** 使用 `.next()` 是旧式迭代器接口(Python 2),现代环境建议使用 `fetchone()` 或检查一致性。
|
||
|
||
---
|
||
|
||
### 方法:`SQL(cmd, args=(), retry=0)`
|
||
执行一条或多条 SQL 语句。
|
||
|
||
**参数:**
|
||
- `cmd` (str) - SQL 语句
|
||
- `args` (tuple/list/dict) - 参数化查询参数
|
||
- `retry` (int) - 重试次数(未实际使用)
|
||
|
||
**逻辑:**
|
||
1. 若连接断开,自动重连并重试
|
||
2. 清理上一次游标残留
|
||
3. 转换参数编码(`argConvert`)
|
||
4. 如果含多个语句(`;` 分隔),使用 `executescript`
|
||
5. 否则使用 `execute(cmd, args)`
|
||
6. 记录 `lastSQL` 和 `description`
|
||
|
||
**异常处理:**
|
||
- 打印错误信息
|
||
- 抛出原始异常
|
||
|
||
---
|
||
|
||
### 方法:`FETCH()`
|
||
逐行获取查询结果,每行封装为 `Record` 对象。
|
||
|
||
**返回值:**
|
||
- 成功 → `Record` 实例
|
||
- 结束 → `None`
|
||
- 出错 → 抛出异常
|
||
|
||
**流程:**
|
||
1. 检查是否有结果集
|
||
2. 获取 `description`(字段元信息)
|
||
3. 调用 `.next()` 获取下一行
|
||
4. 映射字段名 → 值,并用 `unicode2str` 转码
|
||
5. 构造 `Record(data, self.localize)`
|
||
|
||
---
|
||
|
||
### 方法:`COMMIT()`
|
||
提交事务,并设置大小写敏感 LIKE 匹配。
|
||
|
||
**执行命令:**
|
||
```sql
|
||
PRAGMA case_sensitive_like = 1;
|
||
```
|
||
|
||
> ✅ 注意:即使没有显式 BEGIN,SQLite 默认自动提交模式下也需 COMMIT 显式结束事务
|
||
|
||
此外还会尝试 `fetchall()` 清空结果,避免挂起。
|
||
|
||
---
|
||
|
||
### 方法:`ROLLBACK()`
|
||
执行回滚操作。
|
||
|
||
```python
|
||
self.SQL('ROLLBACK')
|
||
```
|
||
|
||
---
|
||
|
||
### 方法:`BEGIN()`
|
||
预留方法,目前为空。
|
||
|
||
> ⚠️ 注释掉 `self.SQL('BEGIN')`,可能导致事务未正确开启
|
||
> ✅ 建议启用:
|
||
```python
|
||
def BEGIN(self):
|
||
self.SQL('BEGIN')
|
||
```
|
||
|
||
---
|
||
|
||
### 方法:`CLOSE()`
|
||
关闭数据库连接。
|
||
|
||
**动作:**
|
||
- 设置 `self.con = None`
|
||
- 设置 `self.cursor = None`
|
||
|
||
> ⚠️ 不会自动提交或回滚,调用前请手动处理事务
|
||
|
||
---
|
||
|
||
## 辅助函数:`getDataBase(name)`
|
||
|
||
根据名称获取或创建一个全局唯一的数据库连接实例。
|
||
|
||
**参数:**
|
||
- `name` (str) - 数据库标识名(如 `'main'`, `'user'`)
|
||
|
||
**工作流程:**
|
||
1. 查找缓存 `public_data['db_main']`
|
||
2. 若不存在:
|
||
- 查找路径 `dbpath_main`
|
||
- 若仍不存在:
|
||
- 使用 `ProgramPath` + `var/xxx.db3` 自动生成路径
|
||
- 创建目录(`mkdir(var)`)
|
||
- 缓存路径
|
||
- 实例化 `SQLite3(dbpath)`
|
||
- 缓存数据库实例
|
||
3. 检查连接有效性,必要时重连
|
||
4. 返回数据库对象
|
||
|
||
**优势:**
|
||
- 单例模式,避免重复打开
|
||
- 自动路径管理
|
||
- 支持热修复断连
|
||
|
||
---
|
||
|
||
## 使用示例
|
||
|
||
```python
|
||
# 获取数据库实例
|
||
db = getDataBase('test')
|
||
|
||
# 查询表
|
||
db.SQL("SELECT * FROM users WHERE age > ?", (18,))
|
||
while True:
|
||
row = db.FETCH()
|
||
if not row:
|
||
break
|
||
print(row.name, row.age)
|
||
|
||
# 插入数据(事务)
|
||
db.BEGIN()
|
||
db.SQL("INSERT INTO users(name,age) VALUES (?,?)", ("Alice", 25))
|
||
db.COMMIT()
|
||
|
||
# 获取所有表
|
||
tables = db.tables()
|
||
print(tables)
|
||
```
|
||
|
||
---
|
||
|
||
## 已知问题与改进建议
|
||
|
||
| 问题 | 描述 | 建议修复 |
|
||
|------|------|--------|
|
||
| `__getattr__` 死循环 | 调用自身导致无限递归 | 改为访问 `self.__dict__` |
|
||
| `columns()` 拼写错误 | 参数名 `tablenmae` ≠ 使用 `tablename` | 更正拼写 |
|
||
| `FETCHALL()` 逻辑冗余 | 初始赋值无效 | 简化代码 |
|
||
| `BEGIN()` 为空 | 无法开启事务 | 启用 `self.SQL('BEGIN')` |
|
||
| 异常捕获过于宽泛 | `except:` 隐藏错误 | 改为具体异常类型 |
|
||
| Python 2 风格 | 使用 `thread`, `next()` | 迁移到 `threading`, `fetchone()` |
|
||
|
||
---
|
||
|
||
## 总结
|
||
|
||
本模块是一个适用于中小型项目的 SQLite 封装工具,具备以下优点:
|
||
|
||
✅ 自动路径生成与缓存
|
||
✅ 多线程安全属性隔离
|
||
✅ 编码自动转换(兼容中文)
|
||
✅ 简洁的面向对象查询接口
|
||
|
||
⚠️ 存在若干 bug 和设计瑕疵,建议在正式项目中审慎使用,并优先修复上述问题。
|
||
|
||
---
|
||
|
||
**最后更新时间:** `{{ 自动生成时间 }}`
|
||
**作者:** 自动生成文档 by AI
|
||
**适用版本:** Python 2.x(因使用 `thread` 和 `next()`) |