☰
build-web-application-with-golang 第 5.5 章实战:基于 beedb 的 SQLite3 环境搭建与 CRUD 示例运行指南
2026/10/3 2:09:31 网站建设 项目流程
  • 文档
  • 教程

【免费下载链接】build-web-application-with-golang

A golang ebook intro how to build a web with golang

项目地址:https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang
点击查看免费下载

本章示例(ch.5.5)是 build-web-application-with-golang 英文版《第 5.5 章:Develop ORM based on beedb》配套的可运行代码,用于演示如何使用 beedb 这个轻量级 Go ORM 对 SQLite3 数据库完成完整的增删改查(CRUD)操作。本篇文章以 ch.5.5 的 readme.md 给出的六步搭建流程为主线,结合仓库中的 schema.sql 表结构与 main.go 源码逐行讲解,帮助你在一台干净的环境上从零跑通「安装 SQLite3 → 建库建表 → 拉取依赖 → 运行 ORM 示例」的完整链路,并理解 beedb 的 Save / Insert / Find / Update / Delete 等核心 API 的底层行为。

一、示例背景:beedb ORM 与 SQLite3 的搭配

第 5.5 章介绍的 beedb 是作者用 Go 开发的一个轻量级 ORM(对象关系映射)库,它遵循database/sql标准接口,因此任何实现了该接口的数据库驱动都可以接入。章节文档 en/05.5.md 明确说明:beedb 项目已不再维护,但代码仍然可用;它支持基础的 ORM 功能,但不支持 struct 之间的关联查询(关联查询需要借助链式 API 手写 Join 完成,见下文)。

在 main.go 中可以看到本章示例采用了两个外部依赖:

import ( "database/sql" "fmt" "github.com/astaxie/beedb" // beedb ORM 本体 _ "github.com/mattn/go-sqlite3" // SQLite3 驱动(匿名导入,仅注册驱动) "time" )

_匿名导入github.com/mattn/go-sqlite3是 Go 中注册数据库驱动的标准写法,之后sql.Open("sqlite3", "./foo.db")才能识别sqlite3这个驱动名。这意味着:只要驱动实现了database/sql接口,beedb 理论上都可以无缝接入——文档中列出的已测试驱动还包括 MySQL、PostgreSQL、mymysql、ADODB、Oracle、ODBC 等。

二、Step 1:安装 SQLite3 客户端

readme 的第一步是下载并安装 sqlite3。SQLite 是嵌入式数据库,无需独立的服务端进程,只需一个命令行客户端工具即可操作数据库文件。

  • 在 Debian/Ubuntu 系 Linux 上:sudo apt-get install sqlite3
  • 在 macOS 上:brew install sqlite3
  • 在 Windows 上:从 SQLite 官网下载预编译的sqlite-tools压缩包,解压后将sqlite3.exe放入 PATH 即可

安装完成后可用sqlite3 --version验证。除了命令行工具外,github.com/mattn/go-sqlite3驱动的编译还依赖 CGO 与 C 编译器(gcc/clang),因此运行本示例的机器需要具备 C 编译环境,且在CGO_ENABLED=1状态下执行go run,否则会报cgo相关编译错误。

三、Step 2:创建名为 foo 的数据库文件

readme 第二步要求在项目目录下执行:

sqlite3 foo.db

该命令会做两件事:如果foo.db不存在则创建这个数据库文件,然后进入 sqlite 交互式命令行。退出后,文件会保存在当前目录。

注意 main.go 中定义了const DB_PATH = "./foo.db",即程序读取的是相对路径下的数据库文件,所以运行go run main.go时必须保证当前工作目录与foo.db所在目录一致(建议直接在ch.5.5目录内操作)。

四、Step 3:导入 schema.sql 建表

readme 第三步要求把 schema.sql 中的建表语句在 sqlite 中执行:

sqlite> .read schema.sql

.read是 sqlite3 客户端的内置命令,用于批量执行文件中的 SQL 语句。schema.sql 共定义了两张表,恰好对应 beedb 的 struct 到数据表映射关系:

CREATE TABLE `userinfo` ( `uid` INTEGER PRIMARY KEY AUTOINCREMENT, `username` VARCHAR(64) NULL, `department` VARCHAR(64) NULL, `created` DATE NULL ); CREATE TABLE `userdetail` ( `uid` INT(10) NULL, `intro` TEXT NULL, `profile` TEXT NULL, PRIMARY KEY (`uid`) );

两张表的含义如下:

  • userinfo(用户信息表):uid是自增主键(INTEGER PRIMARY KEY AUTOINCREMENT),username、department是长度 64 的可空字符串,created是日期字段。它对应 main.go 中的Userinfo结构体:

    type Userinfo struct { Uid int `beedb:"PK"` // 显式声明主键字段 Username string Department string Created string }

    注意 en/05.5.md 特别强调的规则:beedb 会把驼峰命名自动转换为小写蛇形命名——结构体名UserInfo映射为表user_info,字段Username映射为列username。这正是示例选择userinfo表并直接使用字段名Username/Department/Created的原因。同时,由于主键不是默认的id,必须用beedb:"PK"标签(章节文档中的写法是`PK`)显式声明Uid是主键。

  • userdetail(用户详情表):uid为主键,intro、profile为 TEXT 类型。该表用于演示 beedb 的关联查询(Join)能力。

五、Step 4:验证表结构并退出 sqlite

建表完成后,readme 要求用.tables命令确认表已创建成功:

sqlite> .tables userinfo userdetail

输出中应同时出现userinfo与userdetail。如果想进一步核对列定义,可以补充执行sqlite> .schema userinfo查看单张表的完整 DDL。确认无误后退出交互式命令行:

sqlite> .exit

至此数据库文件foo.db已经包含两张空表,可以交给 Go 程序使用了。

六、Step 5:go get 安装远程依赖包

readme 第五步是运行go get拉取远程依赖。结合 main.go 的 import 列表,本项目实际需要安装的是:

go get github.com/astaxie/beedb go get github.com/mattn/go-sqlite3

在 Go Modules 环境下(Go 1.11+),也可以直接进入ch.5.5目录执行go mod init ch.5.5 && go mod tidy,让工具自动解析并下载这两个依赖;随后会生成go.mod/go.sum锁定依赖版本。需要说明的是,章节文档 en/05.5.md 中注明 beedb 已停止维护,其 API 停留在示例所使用的形态,本示例恰好完整覆盖了它提供的全部核心方法。

七、Step 6:运行 main.go 并理解 CRUD 全流程

执行:

go run main.go

程序会在foo.db上依次执行插入 → 查询 → 更新 → 高级查询 → 删除的完整链路,每步都会向标准输出打印操作名称与结果。下面结合 main.go 源码逐段拆解。

7.1 初始化:从 database/sql 到 beedb

程序入口先通过标准库打开数据库连接,再创建 beedb 的 ORM 对象:

func main() { db, err := sql.Open("sqlite3", DB_PATH) checkError(err) orm = beedb.New(db) // ... }

beedb.New(db)是初始化核心:它接收*sql.DB作为第一个参数。章节文档 en/05.5.md 说明该方法实际上还有第二个可选参数用于指定数据库引擎——使用 MySQL/SQLite 时可直接省略;使用 SQLServer 需传beedb.New(db, "mssql"),PostgreSQL 需传beedb.New(db, "pg")。示例中orm是包级变量var orm beedb.Model,后续所有操作都通过它完成。

如需观察 beedb 生成的 SQL,可以在初始化后开启调试开关:

beedb.OnDebug = true

(该开关见 en/05.5.md 初始化章节。)

7.2 插入数据:struct 与 map 两种姿势

方式一:传入 struct,使用Save

func insertUsingStruct() int64 { var obj Userinfo obj.Username = "Test Add User" obj.Department = "Test Add Department" obj.Created = getTimeStamp() checkError(orm.Save(&obj)) fmt.Printf("%+v\n", obj) return int64(obj.Uid) }

orm.Save(&obj)会依据结构体字段映射生成INSERT语句;记录插入成功后,beedb 会把数据库自增产生的uid回填到obj.Uid——这正是函数末尾能读取到obj.Uid并作为返回值的原因。getTimeStamp()使用time.Now().Format("2006-01-02 15:04:05")生成 Go 标准格式的时间字符串,与created列的 DATE 类型对应。

方式二:传入 map,使用链式SetTable().Insert()

func insertUsingMap() int64 { add := make(map[string]interface{}) add["username"] = "astaxie" add["department"] = "cloud develop" add["created"] = getTimeStamp() id, err := orm.SetTable("userinfo").Insert(add) checkError(err) fmt.Println("Last row inserted id =", id) return id }

这里体现了 beedb 的链式调用风格:SetTable("userinfo")显式指定目标表(map 方式无法从类型推断表名,必须指定),Insert(add)返回最后插入的行 id。章节文档 en/05.5.md 还补充了批量插入接口InsertBatch(addslice),接受[]map[string]interface{}。

7.3 查询数据:Find 与 FindAll

按主键/条件查单条记录:

func getOneUserInfo(id int64) Userinfo { var obj Userinfo checkError(orm.Where("uid=?", id).Find(&obj)) return obj }

Where("uid=?", id)是标准的占位符条件写法,结果映射到Userinfo结构体。章节文档中还展示了另一种简写:orm.Where(3).Find(&user)——当第一个参数是整数时,等价于Where("<主键>=?", 值)。

带 Limit 的批量查询:

func getAllUserInfo(id int64) []Userinfo { var alluser []Userinfo checkError(orm.Limit(10).Where("uid>?", id).FindAll(&alluser)) return alluser }

FindAll将多行结果填充到结构体切片;Limit(10)限制返回条数。章节文档说明Limit支持两个参数Limit(条数, 起始位置),第二个参数缺省为 0,例如orm.Where("id > ?", "3").Limit(10, 20).FindAll(&allusers)表示从第 20 条开始取 10 条。

7.4 更新数据:Save 的“插入/更新二象性”与 map 批量更新

struct 方式:

func updateUserinfo(id int64) { var obj Userinfo obj.Uid = int(id) // 主键有值 obj.Username = "Update Username" obj.Department = "Update Department" obj.Created = getTimeStamp() checkError(orm.Save(&obj)) // beedb 自动识别为 UPDATE }

Save是本章最值得注意的 API:章节文档 en/05.5.md 明确指出,当主键已经有值(如自增 ID 回填后)时,beedb 执行的是 UPDATE 而非 INSERT。因此同一方法同时承担了新增与修改两种职责。

map 方式(支持单条与批量):

func updateUsingMap(id int64) { t := make(map[string]interface{}) t["username"] = "updateastaxie" // 单条更新示例(注释掉):orm.SetTable("userinfo").SetPK("uid").Where(2).Update(t) // 批量更新:更新所有 uid > id 的记录 lastId, err := orm.SetTable("userinfo").Where("uid>?", id).Update(t) checkError(err) fmt.Println("Last row updated id =", lastId) }

源码注释中保留了单条更新的写法,需要理解三个链式方法(详见 en/05.5.md):

  • .SetPK("uid"):告诉 ORM 表的主键列是uid;
  • .Where(2):整数参数即Where("uid=?", 2)的简写;
  • .Update(t):接受 map 并生成UPDATE语句;示例中省略SetPK直接Where("uid>?", id),则按条件批量更新。

7.5 高级查询:Select / FindMap / GroupBy / Having / Join

以下四个函数集中演示了 beedb 的链式查询扩展能力,全部返回[]map[string][]byte类型,需要自行转换后再使用。

投影查询 + 返回 map:

func getMapsFromSelect(id int64) []map[string][]byte { c, err := orm.SetTable("userinfo").SetPK("uid").Where(id).Select("uid,username").FindMap() checkError(err) fmt.Printf("%+v\n", c) return c }

.Select("uid,username")限定只取这两列(不指定则默认返回全部字段);.Where(id)整数简写按主键过滤;.FindMap()以 map 形式返回结果,避免为每类查询都定义结构体。

分组与聚合过滤:

func groupby() { b, err := orm.SetTable("userinfo").GroupBy("username").Having("username='updateastaxie'").FindMap() checkError(err) fmt.Printf("%+v\n", b) }

.GroupBy("username")与.Having("username='updateastaxie'")分别对应 SQL 的GROUP BY与HAVING子句,组合后可对分组结果做条件过滤。

表关联 Join:

func joinTables(id int64) { a, err := orm.SetTable("userinfo").Join("LEFT", "userdetail", "userinfo.uid=userdetail.uid").Where("userinfo.uid=?", id).Select("userinfo.uid,userinfo.username,userdetail.profile").FindMap() checkError(err) fmt.Printf("%+v\n", a) }

这是章节文档「Association queries」一节给出的简陋关联方案:beedb 本身不支持 struct 间的 ORM 关联,但可以通过.Join()直接拼接 SQL。.Join()三个参数分别是:连接类型(INNER / LEFT / OUTER / CROSS 等)、被连接的表名、连接条件。示例用LEFT JOIN userdetail按uid关联,并投影出userinfo与userdetail两侧的字段——这正是前面单独建userdetail表的意义所在。

7.6 删除数据:三种删除粒度

按对象删除单条:

func deleteWithUserinfo(id int64) { obj := getOneUserInfo(id) id, err := orm.Delete(&obj) // 依据主键删除 checkError(err) fmt.Println("Last row deleted id =", id) }

先查出对象再orm.Delete(&obj),beedb 依据对象主键生成DELETE语句。

按条件批量删除:

func deleteRows() { id, err := orm.SetTable("userinfo").Where("uid>?", 2).DeleteRow() checkError(err) fmt.Println("Last row updated id =", id) }

SetTable + Where + DeleteRow()组合相当于原生DELETE FROM userinfo WHERE uid > ?,一次删除满足条件的所有行。

按对象集合删除全部:

func deleteAllUserinfo(id int64) { alluser := getAllUserInfo(id) id, err := orm.DeleteAll(&alluser) // 删除切片中所有对象 checkError(err) fmt.Println("Last row updated id =", id) }

DeleteAll接受结构体切片,逐条(或批量)删除查询到的所有记录,适合先查后删的清理场景。

八、运行结果与排查要点

程序正常执行时会依次打印Inserting、Updating、Querying、Deleting等阶段标记,以及每条记录的结构体/ map 内容与受影响行 id。若某一步失败,checkError(err)会直接panic并终止程序,因此任何一步输出异常都可通过 panic 堆栈快速定位。

常见问题与对策:

  1. go: cannot find module ...类错误:说明依赖未拉取,回到 Step 5 执行go get或在模块目录执行go mod tidy。
  2. #cgo/exec: "gcc": executable file not found:go-sqlite3需要 CGO 与 C 编译器,请安装 gcc/clang 并确保CGO_ENABLED=1。
  3. no such table: userinfo:说明schema.sql未正确导入或当前目录下没有foo.db,回到 Step 3/4 用.tables验证。
  4. unable to open database file:DB_PATH是相对路径./foo.db,请确认go run main.go的执行目录与建库目录一致。

九、相关章节与资源

  • 章节正文(完整讲解 beedb 的安装、初始化、CRUD、关联查询与未来规划):en/05.5.md
  • 本示例的六步搭建说明原文:readme.md
  • 建表语句:schema.sql
  • 可运行源码(含全部 CRUD 函数):main.go
  • 上一章(PostgreSQL 数据库操作):en/05.4.md;下一章(NoSQL 数据库操作):en/05.6.md
  • 同样基于 SQLite3 的姊妹示例(5.3 章 MySQL 操作):en/code/src/apps/ch.5.3/readme.md
  • 文档
  • 教程

【免费下载链接】build-web-application-with-golang

A golang ebook intro how to build a web with golang

项目地址:https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang
点击查看免费下载
上一篇:你的Windows资源管理器,也能拥有Windows 11的优雅毛玻璃效果!
下一篇:3步快速美化Windows资源管理器:免费实现毛玻璃和Mica效果终极教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询