
本文详解 MyBatis(Plus)在 SQLite 中实现可靠自增主键的关键要点:必须严格使用 INTEGER PRIMARY KEY(推荐加 AUTOINCREMENT),避免误用 AUTO_INCREMENT;MyBatis 需正确配置 useGeneratedKeys=true 及 keyProperty,并确保建表语句被完整执行——常见问题多源于 SQLite 表结构未生效或 SQL 批量执行截断。
本文详解 mybatis(plus)在 sqlite 中实现可靠自增主键的关键要点:必须严格使用 `integer primary key`(推荐加 `autoincrement`),避免误用 `auto_increment`;mybatis 需正确配置 `usegeneratedkeys=true` 及 `keyproperty`,并确保建表语句被完整执行——常见问题多源于 sqlite 表结构未生效或 sql 批量执行截断。
在 Java 项目中通过 MyBatis 或 MyBatis Plus 同时适配 MySQL 和 SQLite 时,开发者常遭遇 SQLite 主键写入为 NULL、查询失败或主键冲突等异常。根本原因并非框架缺陷,而是 SQLite 的自增机制与主流关系型数据库存在本质差异,且其 DDL 执行约束更为严格。
✅ 正确的建表方式:INTEGER PRIMARY KEY 是唯一合法路径
SQLite 完全不支持 AUTO_INCREMENT 关键字(非标准 SQL,MySQL 特有)。以下写法均非法或无效:
-- ❌ 错误:语法错误(SQLite 报 near "AUTO_INCREMENT": syntax error) CREATE TABLE event (id INTEGER AUTO_INCREMENT, ...); -- ❌ 错误:虽能建表,但插入 NULL 时 id 仍为 NULL(未触发 ROWID 映射) CREATE TABLE event (id INTEGER, PRIMARY KEY(id));
✅ 正确写法(两种等效形式,推荐显式 AUTOINCREMENT):
-- 方式1:隐式自增(复用已删除 ID)
CREATE TABLE event (
id INTEGER PRIMARY KEY,
time DATETIME NOT NULL,
duration INT NOT NULL,
summary_id INT NOT NULL
);
-- 方式2:严格自增(永不复用,推荐用于审计/日志等场景)
CREATE TABLE event (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- ⚠️ 注意:AUTOINCREMENT 必须紧随 PRIMARY KEY
time DATETIME NOT NULL,
duration INT NOT NULL,
summary_id INT NOT NULL
);? 原理说明:INTEGER PRIMARY KEY 是 SQLite 中 ROWID 的别名,插入 NULL 时自动映射为下一个整数。AUTOINCREMENT 则强制 SQLite 维护内部 sqlite_sequence 表,确保 ID 单调递增且不可回退,但会略微降低性能。
✅ MyBatis 插入配置:精准绑定生成主键
MyBatis 需明确告知 JDBC 驱动“需获取生成键”,并指定赋值目标属性。针对你的 @Insert 注解,应修正如下:
@Insert({
"INSERT INTO event(time, duration, summary_id)",
"VALUES(#{e.time}, #{e.duration}, #{summaryId})"
})
@Options(
useGeneratedKeys = true, // ✅ 启用 JDBC getGeneratedKeys()
keyProperty = "e.id", // ✅ 将生成的主键写入 Event 对象的 id 字段
keyColumn = "id" // ✅ 显式指定数据库列名(SQLite 必填,避免歧义)
)
void insert(@Param("e") Event item, @Param("summaryId") int summaryId);⚠️ 关键注意事项:
- keyProperty 必须指向 传入参数对象的属性(如 e.id),而非额外包装类(如原示例中的 result.id);
- keyColumn = "id" 在 SQLite 中强烈建议显式声明,因部分 JDBC 驱动对无列名返回值解析不稳定;
- 若使用 MyBatis Plus,实体类中 @TableId(type = IdType.AUTO) 仅在 useGeneratedKeys=true 生效时起作用,且依赖底层驱动支持。
✅ 排查高频陷阱:让建表真正生效
正如问题答案所揭示,90% 的“自增失效”实际源于表结构未按预期创建:
- 文件级缓存干扰:SQLite 数据库文件若已存在且含旧表结构,CREATE TABLE 将静默跳过(非报错);
- SQL 批处理截断:单个 .sql 文件含多个 ; 分隔语句时,sqlite3 CLI 或某些 JDBC 工具(如 HikariCP 的 connectionInitSql)仅执行第一条,后续语句被忽略;
-
验证方法:
# 连接数据库后检查表结构 sqlite3 your.db ".schema event" # 输出应包含:id INTEGER PRIMARY KEY AUTOINCREMENT
✅ 推荐实践:
- 初始化阶段使用独立 .sql 文件,每文件仅含一条 CREATE TABLE;
- 或改用 ScriptRunner(MyBatis 内置)逐行执行,确保全部 DDL 生效;
- 开发时添加断言验证主键行为:
// 插入后立即验证 eventMapper.insert(event); assert event.getId() != null && event.getId() > 0 : "SQLite auto-increment failed!";
? 总结:四步保障 SQLite 自增稳定运行
| 步骤 | 操作 | 验证点 |
|---|---|---|
| 1. 建表 | 使用 INTEGER PRIMARY KEY AUTOINCREMENT | .schema table_name 输出含 AUTOINCREMENT |
| 2. 驱动 | 确保 sqlite-jdbc ≥ 3.42.0(高兼容性版本) | Maven 依赖 |
| 3. MyBatis | @Options(useGeneratedKeys=true, keyProperty="obj.id", keyColumn="id") | 插入后 obj.getId() 返回正整数 |
| 4. 环境 | 清理旧 DB 文件 + 分离 DDL 脚本 | 避免“看似修改实则无效”的隐性故障 |
遵循以上规范,MyBatis 与 SQLite 的自增主键即可像 MySQL 一样稳定工作——无需额外序列、UUID 或手动 ID 管理,真正实现跨数据库的轻量级统一抽象。

















