- 文档
- 教程
【免费下载链接】build-web-application-with-golang
A golang ebook intro how to build a web 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 堆栈快速定位。
常见问题与对策:
go: cannot find module ...类错误:说明依赖未拉取,回到 Step 5 执行go get或在模块目录执行go mod tidy。#cgo/exec: "gcc": executable file not found:go-sqlite3需要 CGO 与 C 编译器,请安装 gcc/clang 并确保CGO_ENABLED=1。no such table: userinfo:说明schema.sql未正确导入或当前目录下没有foo.db,回到 Step 3/4 用.tables验证。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
相关推荐
基于 beedb 开发 Go 轻量级 ORM:struct 映射与 CRUD 全流程实战(build-web-application-with-golang 第 5.5 节)
基于 beedb 开发 Go 轻量级 ORM:struct 映射与 CRUD 全流程实战(build web application with golang 第
文档教程用 beedb ORM 操作 SQLite3:ch.5.5 示例的环境搭建与 CRUD 实战
用 beedb ORM 操作 SQLite3:ch.5.5 示例的环境搭建与 CRUD 实战 本篇指南围绕《Build Web Application with
文档教程build-web-application-with-golang ORM教程:基于beedb开发数据访问层
build web application with golang ORM教程:基于beedb开发数据访问层 你是否还在为Golang Web开发中的数据访问层
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考