ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Go Web 开发实战:使用 SQLite 数据库(build-web-application-with-golang 第 5.3 节)

Go Web 开发实战:使用 SQLite 数据库(build-web-application-with-golang 第 5.3 节) 文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载本文围绕开源电子书《Build Web Application with Golang》第 5.3 节展开系统讲解如何在 Go Web 项目中通过database/sql标准接口使用 SQLite从驱动选型、建库建表到完整的增删改查CRUD代码再到多协程场景下的事务处理。读完本文你将掌握 SQLite 在 Go 中的标准接入方式、事务的正确打开/提交/回滚模式并能在 MySQL、SQLite、PostgreSQL 之间低成本切换数据层代码。文中所引用的示例代码与配套工程均可在仓库的en/code/src/apps/ch.5.3目录下找到并直接运行。SQLite嵌入式关系数据库的定位SQLite 是一个开源的嵌入式关系数据库实现了自包含self-contained、零配置zero-configuration、支持事务transaction-supported的 SQL 数据库引擎。它的特点是高度可移植、使用方便、结构紧凑、高效且可靠。与其他数据库管理系统不同SQLite 的安装和运行非常简单——在大多数情况下只要确保 SQLite 的二进制文件存在就可以开始创建、连接和使用数据库无需独立的服务进程也没有繁琐的初始化配置。如果你正在寻找一个嵌入式数据库解决方案SQLite 是值得优先考虑的选项原文档甚至将其比作“开源的 Access”。这种特性让 SQLite 非常适合以下 Web 场景原型验证与本地开发无需安装数据库服务一个文件即可完成全部开发调试小型站点或单机应用数据量有限时免去运维成本测试环境配合database/sql标准接口测试与生产可以共用同一套业务代码。Go 的 SQLite 驱动选型Go 支持 SQLite 的驱动并不少但许多驱动并不支持database/sql接口标准。原文档列举了三款具有代表性的驱动驱动是否支持database/sql实现方式说明mattn/go-sqlite3支持基于 cgo唯一支持标准接口的 SQLite 驱动也是本书采用的驱动feyeleanor/gosqlite3不支持基于 cgo使用自有接口标准phf/go-sqlite3不支持基于 cgo使用自有接口标准目前支持database/sql的 SQLite 驱动只有第一个mattn/go-sqlite3本书作者在实际项目中也采用它来开发。采用标准接口的收益在于以后如果出现更好的驱动代码可以平滑迁移——只要保持database/sql的调用方式不变更换驱动通常只涉及导入语句和注册名称的调整。从仓库配套源码看该驱动的典型导入方式如下示例入口import ( database/sql fmt _ github.com/mattn/go-sqlite3 time )这里_空白标识符导入的含义在第 5.1 节 database/sql 接口 中有深入解释Go 规定导入的包必须被使用而空白导入表示只需要执行该包的init()函数而不直接引用其符号。第三方驱动正是在init()中调用sql.Register完成自身注册例如 go-sqlite3 驱动内部等价于func init() { sql.Register(sqlite3, SQLiteDriver{}) }database/sql内部用一个 map 保存这些驱动因此可以同时注册多个不同名称的驱动。这就是为什么下面的sql.Open第一个参数可以写sqlite3——它对应的是驱动的注册名。准备环境创建数据库与建表仓库的 ch.5.3 运行说明 给出了完整的本地复现步骤整理如下下载并安装 SQLite 3运行sqlite3 foo.db创建一个名为foo的数据库文件使用 schema.sql 在 SQLite 中创建userinfo表CREATE TABLE userinfo ( uid INTEGER PRIMARY KEY AUTOINCREMENT, username VARCHAR(64) NULL, department VARCHAR(64) NULL, created DATE NULL );在 sqlite3 交互环境中读取并执行该 SQLsqlite .read schema.sql随后可以用.tables确认表已创建成功sqlite .tables userinfo退出 sqlite3sqlite .exit执行go get下载并安装远程依赖包即 mattn/go-sqlite3运行程序go run main.go。创建完成后仓库目录下会生成 foo.db 数据库文件——这也是 SQLite“嵌入式、单文件”特性的直观体现整个数据库就是磁盘上的一个文件。建表 SQL 中需要注意两个 SQLite 特色INTEGER PRIMARY KEY AUTOINCREMENT自增整数主键对应后面代码中LastInsertId()返回的自增 IDDATE类型SQLite 实际采用动态类型type affinity这里声明DATE会按亲和性规则将值存储为文本或数值Go 侧读取时可按time.Time或字符串处理。完整 CRUD 实例基于 database/sql 操作 SQLite原文档给出了完整的增删改查示例仓库中的 main.go 是同一示例的可运行工程化版本增加了常量DB_PATH、defer db.Close()与格式化输出。以下是文档中的完整代码package main import ( database/sql fmt time _ github.com/mattn/go-sqlite3 ) func main() { db, err : sql.Open(sqlite3, ./foo.db) checkErr(err) // insert stmt, err : db.Prepare(INSERT INTO userinfo(username, department, created) values(?,?,?)) checkErr(err) res, err : stmt.Exec(astaxie, 研发部门, 2012-12-09) checkErr(err) id, err : res.LastInsertId() checkErr(err) fmt.Println(id) // update stmt, err db.Prepare(update userinfo set username? where uid?) checkErr(err) res, err stmt.Exec(astaxieupdate, id) checkErr(err) affect, err : res.RowsAffected() checkErr(err) fmt.Println(affect) // query rows, err : db.Query(SELECT * FROM userinfo) checkErr(err) var uid int var username string var department string var created time.Time for rows.Next() { err rows.Scan(uid, username, department, created) checkErr(err) fmt.Println(uid) fmt.Println(username) fmt.Println(department) fmt.Println(created) } rows.Close() //good habit to close // delete stmt, err db.Prepare(delete from userinfo where uid?) checkErr(err) res, err stmt.Exec(id) checkErr(err) affect, err res.RowsAffected() checkErr(err) fmt.Println(affect) db.Close() } func checkErr(err error) { if err ! nil { panic(err) } }关键 API 拆解这段代码涉及的核心database/sqlAPI其底层接口语义详见 en/05.1.mdsql.Open(sqlite3, ./foo.db)打开注册名为sqlite3的驱动第二个参数是数据源标识。对 SQLite 而言它就是数据库文件路径例如./foo.db。注意sql.Open返回的*sql.DB内部带有一个简单的连接池freeConn []driver.Conn因此不必手动缓存连接db.Prepare(sql)预编译一条 SQL返回*sql.Stmt之后可反复Exec。预编译语句在文档与本书其他章节中都被强调为防 SQL 注入的必要手段——参数一律用?占位符传递而不是拼接字符串stmt.Exec(args...)执行预编译的写操作insert/update/delete返回sql.Resultres.LastInsertId()返回自增主键 ID对应建表时的AUTOINCREMENTres.RowsAffected()返回受影响的行数db.Query(sql)执行查询返回*sql.Rows结果集rows.Next()rows.Scan(...)逐行迭代并扫描到变量rows.Close()/db.Close()关闭结果集与数据库连接文档明确建议养成关闭的良好习惯仓库版本还使用defer db.Close()进一步简化资源管理。仓库版本的工程化写法值得对照参考const DB_PATH ./foo.db ... db, err : sql.Open(sqlite3, DB_PATH) checkErr(err) defer db.Close() ... res, err : stmt.Exec(astaxie, software developement, time.Now().Format(2006-01-02)) ... fmt.Printf(%3v | %6v | %8v | %6v\n, uid, username, department, created)这里将日期改为time.Now().Format(2006-01-02)动态生成并用fmt.Printf输出对齐的表格样式更贴近真实项目的日志习惯。与 MySQL 示例的对比为什么代码几乎一样原文档特别指出这段代码与上一节 使用 MySQL 数据库 的示例几乎一模一样唯一改变的只有两点——导入的驱动包以及sql.Open的驱动名mysql→sqlite3与数据源格式DSN 字符串 → 文件路径。这正是database/sql标准接口带来的核心价值数据层代码与具体数据库解耦。对比第 5.4 节 使用 PostgreSQL 数据库 可以看到PostgreSQL 驱动连占位符风格都不同使用$1, $2而非?而且不支持LastInsertId()相比之下SQLite 与 MySQL 在写法上高度一致迁移成本最低。单行结果的读取if 与 for 的取舍原文档提醒了一个非常容易踩的坑当查询结果确定只有一行甚至可能为零行时for循环并非唯一选择也可以改用if语句if rows.Next() { err rows.Scan(uid, username, department, created) checkErr(err) fmt.Println(uid) fmt.Println(username) fmt.Println(department) fmt.Println(created) }同时必须强调的是必须先调用rows.Next()否则无法在Scan函数中取到数据。Next的作用是推进结果集游标到下一行底层对应driver.Rows.Next(dest []Value)无更多数据时返回io.EOFScan只负责把当前行的数据映射到目标变量。理解这一机制后无论是for全量遍历还是if单行取值写起来都会更有把握。事务处理多协程访问下的正确姿势上面的示例只展示了读取数据但一个真实的 Web 应用不仅有读还要频繁写。此时必须使用事务原因在于Web 应用通常是多 goroutine 并发访问数据库的在并发写的情况下数据库可能被锁住locked这是我们不希望发生的。事务的作用是确保一次数据库活动要么整体成功、要么整体失败从而避免大量意外问题。原文档给出的事务代码范式如下trashSQL, err : database.Prepare(update task set is_deletedY,last_modified_atdatetime() where id?) if err ! nil { fmt.Println(err) } tx, err : database.Begin() if err ! nil { fmt.Println(err) } _, err tx.Stmt(trashSQL).Exec(id) if err ! nil { fmt.Println(doing rollback) tx.Rollback() } else { tx.Commit() }这段代码展示的流程可以拆解为四步也是 Go 中使用database/sql事务的通用模式Prepare先预编译待执行的 SQL 语句这里database指代*sql.DB对象得到可复用的*sql.StmtBegin调用database.Begin()开启事务返回*sql.Tx。在database/sql的底层接口中Begin()对应driver.Conn.Begin()返回一个代表事务句柄的driver.Tx它只有Commit()与Rollback()两个方法详见 en/05.1.md 的 driver.Tx 小节执行通过tx.Stmt(trashSQL).Exec(id)把已预编译的语句绑定到事务上执行——注意这里用的是tx.Stmt而不是db.Stmt确保操作发生在事务的上下文中决策根据执行结果判断——出错则打印 doing rollback 并调用tx.Rollback()撤销本次操作成功则调用tx.Commit()提交。从代码可以清晰看到原文档强调的设计思路先准备语句再执行最后根据执行结果决定回滚还是提交。这套“Begin → Stmt/Exec → Commit/Rollback”的事务模板是 Web 写入路径上的标准防线尤其适用于“软删除”如示例中的is_deletedY标记这类需要多个步骤协同、中途失败必须整体撤销的业务操作。数据库管理工具作为本节的补充原文档推荐了一个实用的 SQLite 管理工具SQLite Browsersqlitebrowser。它提供图形化界面可以方便地完成新建数据库、浏览表结构、编辑数据等管理工作配合命令行sqlite3使用可以显著提升开发效率。管理工具本身只是辅助核心的数据访问逻辑仍然以database/sql代码为准。从 SQLite 出发标准接口带来的横向扩展SQLite 这一节在整个第 5 章中扮演着“标准接口示范”的角色。结合仓库文档可以进一步延伸第 5.1 节 database/sql 接口 解释了sql.Register、driver.Driver、driver.Conn、driver.Tx等底层接口SQLite 示例中的每一个调用都能在其中找到对应的接口语义第 5.5 节 基于 beedb 开发 ORM 指出beedb 这类 ORM 之所以能同时兼容 MySQL、PostgreSQL、SQLite正是因为它们都实现了database/sql接口标准第 5.2 节 MySQL 与第 5.4 节 PostgreSQL 展示了同一套database/sql写法在不同数据库上的细微差异而 SQLite 是其中接入成本最低的一个。因此在项目初期使用 SQLite 做开发、后期按需迁移到 MySQL 或 PostgreSQL是标准接口架构下一种非常务实的技术路线。相关章节与配套源码配套源码ch.5.3 示例程序、建表 SQL、运行步骤、数据库文件 foo.db目录en/preface.md上一节使用 MySQL 数据库下一节使用 PostgreSQL 数据库相关基础database/sql 接口、第 5 章导读、基于 beedb 的 ORM赞分享文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载相关推荐AreaCity数据校验实战用QGIS和数据库验证行政边界拓扑无间隙无重叠完整指南AreaCity数据校验实战用QGIS和数据库验证行政边界拓扑无间隙无重叠完整指南 AreaCity 省市区县乡镇行政区划数据含拼音、坐标与行政边界发布前文档教程Go 标准库 XML 编解码实战基于 build-web-application-with-golang 第 7.1 节Go 标准库 XML 编解码实战基于 build web application with golang 第 7.1 节 导读 XML 是 Web 服务中最常文档教程使用 Go 的 net/http 包构建简易 Web 服务器Build Web Application with Golang 第 3.2 节实战使用 Go 的 net/http 包构建简易 Web 服务器Build Web Application with Golang 第 3.2 节实战 导读 本文文档教程上一篇网盘直链下载助手终极指南告别限速8大网盘高速下载全解析下一篇网盘直链下载助手八大网盘一站式下载解决方案深度解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表