
文档教程【免费下载链接】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点击查看免费下载本篇技术指南以《Go Web 编程》第 5 章访问数据库的总结为主线系统梳理 Go 语言操作数据库的完整技术栈从database/sql标准接口的设计哲学到 MySQL、SQLite、PostgreSQL 三大关系型驱动的接入与增删改查实战再到 beego orm 的 ORM 开发范式最后覆盖 Redis 与 MongoDB 两类 NOSQL 数据库的 Go 客户端用法。读完本篇你将掌握 Go 项目中统一的数据访问方案理解驱动注册、连接池、参数化查询与 ORM 映射的底层原理并能直接复用到自己的 Web 项目中。一、本章核心脉络为什么数据库是 Web 应用的重心对绝大多数 Web 应用而言数据库是核心所在——用户信息、产品目录、新闻列表等一切可查询、可修改的数据最终都落在数据库中。第 5 章围绕Go 如何访问数据库展开了一条从接口到实现、从关系到非关系的完整学习路径参见 章节导言5.1 database/sql 接口Go 官方不内置任何数据库驱动而是定义标准接口让第三方驱动按约定实现5.25.4 关系型驱动MySQL、SQLite、PostgreSQL 三种主流关系型数据库的接入与 CRUD 实战5.5 beego orm基于database/sql标准接口开发的轻量级 ORM 库用 Go Style 操作数据库5.6 NOSQLRedis 与 MongoDB 两类非关系型数据库的 Go 客户端操作。一句话概括本章成果学完这一章就解决了 Web 开发中最重要的数据存储问题而这一切都建立在深入理解database/sql设计思想的基础上。二、database/sql 接口设计驱动注册与核心接口族Go 与 PHP 等语言不同官方不提供数据库驱动而是为驱动开发者定义标准接口。这样做的最大好处是——只要代码按照标准接口编写未来迁移数据库时无需修改业务代码只需更换驱动包即可。2.1 sql.Register驱动的注册机制database/sql包中的Register(name string, driver driver.Driver)函数用于注册数据库驱动。第三方驱动通常在包的init()函数中完成注册// https://github.com/mattn/go-sqlite3 驱动 func init() { sql.Register(sqlite3, SQLiteDriver{}) } // https://github.com/mikespook/mymysql 驱动 // Driver automatically registered in database/sql var d Driver{proto: tcp, raddr: 127.0.0.1:3306} func init() { Register(SET NAMES utf8) sql.Register(mymysql, d) }database/sql内部用一个 map 存储用户注册的驱动var drivers make(map[string]driver.Driver) drivers[name] driver因此可以同时注册多个驱动只要名称不重复。这里也解释了新手常见的困惑——导入驱动时为什么要用_占位import ( database/sql _ github.com/mattn/go-sqlite3 )_的作用是只引入包、触发其init()函数完成驱动注册而不直接使用包中定义的函数和变量init 函数的初始化过程在本书 2.3 节流程和函数中有详细介绍。2.2 driver 包的核心接口族database/sql/driver定义了驱动层必须实现的一组接口一个驱动只要实现这些接口就能完成增删查改等基本操作接口核心方法职责说明driver.DriverOpen(name string) (Conn, error)解析 name 参数获取连接信息初始化并返回一个 Conndriver.ConnPrepare/Close/Begin连接准备 SQL、关闭连接、开启事务driver.StmtClose/NumInput/Exec/Query预编译语句状态driver.TxCommit/Rollback事务的提交与回滚driver.ResultLastInsertId/RowsAffected写操作返回结果driver.RowsColumns/Close/Next查询结果集迭代driver.Valueinterface{}驱动可操作的值类型driver.ValueConverterConvertValue(v interface{}) (Value, error)普通值与 driver.Value 的互转几个关键设计点值得深入理解Conn 与 goroutine 的约束Driver.Open返回的 Conn只能用于一个 goroutine不能并发使用。下面的代码是错误用法会导致结果错乱——goroutineA 的查询结果可能被返回给 goroutineB被误当成自己的插入结果go goroutineA(Conn) // 执行查询操作 go goroutineB(Conn) // 执行插入操作Stmt 的 NumInput返回预编译语句的占位参数个数当驱动不知道预留参数时返回-1此时database/sql不会做参数个数校验返回0时会智能检查调用者传入的参数。Rows.Next 的类型约定Next(dest []Value)把下一条数据赋给 destdest 元素必须是driver.Value的值类型特别地返回数据中的所有 string 都必须转换成[]byte遍历结束时返回io.EOF。driver.Value 的合法类型只能是nil、int64、float64、bool、[]byte、stringRows.Next返回的除外、time.Time中的一种。ValueConverter 的三个使用场景把 driver.Value 转换成数据库表对应字段如 int64 转 uint16、把查询结果转成 driver.Value、在 Scan 中把 driver.Value 转成用户自定义值。2.3 database/sql 的高层封装与连接池在driver接口之上database/sql提供了更高阶的DB对象简化数据库操作并实现了一个建议性的连接池type DB struct { driver driver.Driver dsn string mu sync.Mutex // protects freeConn and closed freeConn []driver.Conn closed bool }从源码结构可以推断其连接池实现相当简单执行db.prepare→db.prepareDC时会通过defer dc.releaseConn调用db.putConn把连接放回池中每次调用db.conn时先检查freeConn长度大于 0 则直接复用现有连接否则新建连接。由于驱动层面已实现建议的连接池开发者不应再自行实现连接缓存否则容易引发问题。三、关系型数据库实战MySQL / SQLite / PostgreSQL本章从 5.2 到 5.4 使用了几乎相同的代码骨架演示三种关系型数据库的增删改查这正体现了database/sql标准接口的迁移优势。3.1 MySQL最常用的 Web 后端数据库LAMP 架构中的 M 即 MySQL。Go 支持 MySQL 的驱动主要有github.com/go-sql-driver/mysql支持database/sql纯 Go 实现本书推荐理由较新、维护好、完全支持标准接口、底层支持 keepalive 长连接github.com/ziutek/mymysql支持database/sql也支持自定义接口纯 Go 实现github.com/Philio/GoMySQL不支持database/sql自定义接口。演示用建表语句数据库 test用户表 userinfo 与关联表 userdetailCREATE TABLE userinfo ( uid INT(10) NOT NULL AUTO_INCREMENT, username VARCHAR(64) NULL DEFAULT NULL, department VARCHAR(64) NULL DEFAULT NULL, created DATE NULL DEFAULT NULL, PRIMARY KEY (uid) ); CREATE TABLE userdetail ( uid INT(10) NOT NULL DEFAULT 0, intro TEXT NULL, profile TEXT NULL, PRIMARY KEY (uid) )完整 CRUD 示例package main import ( database/sql fmt //time _ github.com/go-sql-driver/mysql ) func main() { db, err : sql.Open(mysql, astaxie:astaxie/test?charsetutf8) checkErr(err) // 插入数据 stmt, err : db.Prepare(INSERT INTO userinfo SET username?,department?,created?) checkErr(err) res, err : stmt.Exec(astaxie, 研发部门, 2012-12-09) checkErr(err) id, err : res.LastInsertId() checkErr(err) fmt.Println(id) // 更新数据 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) // 查询数据 rows, err : db.Query(SELECT * FROM userinfo) checkErr(err) for rows.Next() { var uid int var username string var department string var created string err rows.Scan(uid, username, department, created) checkErr(err) fmt.Println(uid, username, department, created) } // 删除数据 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) } }关键函数说明sql.Open()打开一个已注册的驱动。第二个参数是DSNData Source Namego-sql-driver 支持以下格式userunix(/path/to/socket)/dbname?charsetutf8user:passwordtcp(localhost:5555)/dbname?charsetutf8user:password/dbnameuser:passwordtcp([de:ad:be:ef::ca:fe]:80)/dbnamedb.Prepare()返回准备执行的 SQL 语句状态db.Query()直接执行 SQL 返回 Rows 结果集stmt.Exec()执行准备好的语句。值得注意示例中所有传入参数都用?占位符对应的数据传入这种方式可以在一定程度上防止 SQL 注入。3.2 SQLite零配置的嵌入式数据库SQLite 是开源的嵌入式关系数据库实现自包容、零配置、支持事务其安装和运行非常简单——只要确保 SQLite 二进制文件存在即可开始使用堪称开源的 Access。支持database/sql的 SQLite 驱动目前只有github.com/mattn/go-sqlite3基于 cgo 实现这也是本书推荐采用的驱动采用标准接口有利于以后出现更好驱动时平滑迁移。SQLite 版建表语句与 Go 代码几乎与 MySQL 版一致唯一变化是导入驱动与打开方式import ( database/sql fmt time _ github.com/mattn/go-sqlite3 ) db, err : sql.Open(sqlite3, ./foo.db)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) );代码骨架插入/更新/查询/删除与 MySQL 完全一样只是created字段可以扫描为time.Time类型。这再次印证了database/sql标准接口带来的驱动即插即用体验。3.3 PostgreSQL企业级对象-关系数据库PostgreSQL 是自由的对象-关系数据库服务器作为 Oracle 的替代品设计体量比 MySQL 更大在企业应用中更受青睐。文档同时提醒MySQL 被 Oracle 收购后逐步封闭自 MySQL 5.5.31 以后的所有版本不再遵循 GPL 协议因此 PostgreSQL 是未来值得考虑的后端选择。常用驱动均为纯 Go 且支持database/sqlgithub.com/lib/pq本书采用使用人数最多github.com/jbarham/gopgsqldrivergithub.com/lxn/go-pgsqlPostgreSQL 建表与操作示例有两个显著差异占位符语法不同PostgreSQL 使用$1、$2方式指定参数而非 MySQL 的?不支持 LastInsertId因为 PostgreSQL 内部没有 MySQL 式的自增 ID 返回机制需要用RETURNING uid配合QueryRow获取插入后的 ID。CREATE TABLE userinfo ( uid serial NOT NULL, username character varying(100) NOT NULL, department character varying(500) NOT NULL, Created date, CONSTRAINT userinfo_pkey PRIMARY KEY (uid) ) WITH (OIDSFALSE);db, err : sql.Open(postgres, userastaxie passwordastaxie dbnametest sslmodedisable) // pg 不支持 LastInsertId改用 RETURNING 取回自增主键 var lastInsertId int err db.QueryRow(INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;, astaxie, 研发部门, 2012-12-09).Scan(lastInsertId) fmt.Println(最后插入id , lastInsertId) // 更新/查询/删除均使用 $n 占位符 stmt, err db.Prepare(update userinfo set username$1 where uid$2)此外 PostgreSQL 的 DSN 是keyvalue键值对形式如user... password... dbname... sslmodedisable与 MySQL 驱动的 DSN 格式不同使用时需注意区分。四、beego orm基于标准接口的 Go Style ORM 库beego orm 是本书作者开发的轻量级 Go ORM 库采用 Go Style 方式操作数据库实现 struct 到数据表记录的映射。它基于database/sql标准接口因此理论上所有支持database/sql的驱动都可无缝接入作者测试过的驱动包括go-sql-driver/mysql、lib/pq、mattn/go-sqlite3、ziutek/mymysql暂未支持的包括 MsSqlgo-mssqldb、MS ADODB、Oraclego-oci8、ODBC 等。4.1 安装与初始化go get github.com/astaxie/beego初始化流程注册驱动 → 注册数据库 → 注册模型 → 同步建表import ( database/sql github.com/astaxie/beego/orm _ github.com/go-sql-driver/mysql ) func init() { // 注册驱动 orm.RegisterDriver(mysql, orm.DRMySQL) // 设置默认数据库最后一个参数 30 为最大空闲连接数 orm.RegisterDataBase(default, mysql, root:root/my_db?charsetutf8, 30) // 注册定义的 model orm.RegisterModel(new(User)) // 创建 table orm.RunSyncdb(default, false, true) }三种数据库的配置范式// PostgreSQL 配置 orm.RegisterDriver(postgres, orm.DR_Postgres) orm.RegisterDataBase(default, postgres, userpostgres passwordzxxx dbnametest host127.0.0.1 port5432 sslmodedisable) // MySQL 配置 orm.RegisterDriver(mysql, orm.DR_MySQL) orm.RegisterDataBase(default, mysql, root:zxxx/test?charsetutf8) // Sqlite 配置 orm.RegisterDriver(sqlite, orm.DR_Sqlite) orm.RegisterDataBase(default, sqlite3, ./datas/test.db)连接池参数可直接按数据库别名设置需 Go 1.2orm.SetMaxIdleConns(default, 30) // 最大空闲连接 orm.SetMaxOpenConns(default, 30) // 最大数据库连接 orm.Debug true // 打印调试 SQL4.2 模型定义与关系映射beego orm 针对驼峰命名自动转换为下划线字段例如 struct 名为UserInfo时底层表名为user_info字段命名同样遵循该规则。关系模型示例type Userinfo struct { Uid int orm:PK // 如果主键不是 id需显式加上 pk 注释 Username string Departname string Created time.Time } type User struct { Uid int orm:PK Name string Profile *Profile orm:rel(one) // OneToOne 关系 Post []*Post orm:reverse(many) // 一对多反向关系 } type Profile struct { Id int Age int16 User *User orm:reverse(one) // 一对一反向关系可选 } type Post struct { Id int Title string User *User orm:rel(fk) Tags []*Tag orm:rel(m2m) // 多对多关系 } type Tag struct { Id int Name string Posts []*Post orm:reverse(many) } func init() { orm.RegisterModel(new(Userinfo), new(User), new(Profile), new(Post), new(Tag)) }4.3 增删改查实战插入数据——操作 struct 对象而非原生 SQL插入后user.Uid即为自增 IDo : orm.NewOrm() var user User user.Name zxxx user.Departname zxxx id, err : o.Insert(user) if err nil { fmt.Println(id) }批量插入 InsertMulti——第一个参数 bulk 为并列插入数量第二个为对象 slice返回成功插入数量bulk 为 1 时顺序插入users : []User{{Name: slene}, {Name: astaxie}, {Name: unknown}} successNums, err : o.InsertMulti(100, users) // 等价于 insert into table (name, age) values(slene, 28),(astaxie, 30),(unknown, 20)更新数据——主键已有值时beego orm 内部自动执行 update 而非 inserto : orm.NewOrm() user : User{Uid: 1} if o.Read(user) nil { user.Name MyName if num, err : o.Update(user); err nil { fmt.Println(num) } } // 只更新指定字段 o.Update(user, Name) // o.Update(user, Field1, Field2, ...)查询数据——灵活的条件查询// 例1根据主键获取 o : orm.NewOrm() user : User{Id: 1} err o.Read(user) if err orm.ErrNoRows { fmt.Println(查询不到) } else if err orm.ErrMissPK { fmt.Println(找不到主键) } else { fmt.Println(user.Id, user.Name) } // 例2QuerySeter 条件查询 qs : o.QueryTable(user) qs.Filter(id, 1) // WHERE id 1 qs.Filter(profile__age, 18) // WHERE profile.age 18 // 例3WHERE IN qs.Filter(profile__age__in, 18, 20) // WHERE profile.age IN (18, 20) // 例4组合复杂条件 qs.Filter(profile__age__in, 18, 20).Exclude(profile__lt, 1000) // WHERE profile.age IN (18, 20) AND NOT profile_id 1000 // 例5分页注意与 SQL 顺序相反 qs.Filter(profile__age__gt, 17) // WHERE profile.age 17 qs.Limit(10, 20) // LIMIT 10 OFFSET 20删除数据——Delete 会对反向关系进行操作此例中 Post 拥有到 User 的外键删除 User 时若 on_delete 为默认的级联操作将一并删除对应的 Posto : orm.NewOrm() if num, err : o.Delete(User{Id: 1}); err nil { fmt.Println(num) }关联查询——通过Filter(User__Name, ...)完成跨表条件过滤var posts []*Post qs : o.QueryTable(post) num, err : qs.Filter(User__Name, slene).All(posts)排序 GroupBy/Havingqs.OrderBy(id, -profile__age) // ORDER BY id ASC, profile.age DESC qs.OrderBy(-profile__age, profile) // ORDER BY profile.age DESC, profile_id ASC其中 OrderBy 支持字段前缀-表示降序GroupBy 用于指定分组字段Having 用于指定分组后的过滤条件。使用原生 SQL——当 ORM 无法表达复杂查询时可通过o.Raw直接执行 SQLo : orm.NewOrm() var r orm.RawSeter r o.Raw(UPDATE user SET name ? WHERE name ?, testing, slene) // 复杂查询 rs o.Raw(SELECT * FROM user WHERE name? AND uid10 ORDER BY uid DESC LIMIT 100, name) num, err : rs.QueryRows(user)五、NOSQL 数据库操作Redis 与 MongoDBNoSQLNot Only SQL指非关系型数据库。随着 Web2.0 兴起传统关系数据库在超大规模、高并发的 SNS 类纯动态网站面前力不从心而 Go 作为21 世纪的 C 语言对 NOSQL 的支持同样出色。本章重点讲解 Redis 与 MongoDB 的操作。5.1 Rediskey-value 存储系统Redis 与 Memcached 类似但支持的 value 类型更多string字符串、list链表、set集合、zset有序集合。Go 支持的 Redis 驱动有 redigo推荐、go-redis/redis、hoisie/redis、alphazero/Go-Redis、simonz05/godis 等。使用 redigo 的池化连接示例package main import ( fmt os os/signal syscall time github.com/gomodule/redigo/redis ) var Pool *redis.Pool func init() { redisHost : :6379 Pool newPool(redisHost) close() } func newPool(server string) *redis.Pool { return redis.Pool{ MaxIdle: 3, IdleTimeout: 240 * time.Second, Dial: func() (redis.Conn, error) { c, err : redis.Dial(tcp, server) if err ! nil { return nil, err } return c, err }, TestOnBorrow: func(c redis.Conn, t time.Time) error { _, err : c.Do(PING) return err }, } } func close() { c : make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) signal.Notify(c, syscall.SIGTERM) signal.Notify(c, syscall.SIGKILL) go func() { -c Pool.Close() os.Exit(0) }() } func Get(key string) ([]byte, error) { conn : Pool.Get() defer conn.Close() var data []byte data, err : redis.Bytes(conn.Do(GET, key)) if err ! nil { return data, fmt.Errorf(error get key %s: %v, key, err) } return data, err } func main() { test, err : Get(test) fmt.Println(test, err) }连接池配置要点MaxIdle控制最大空闲连接数IdleTimeout控制空闲超时回收Dial定义建连方式TestOnBorrow在借用连接时执行 PING 检测连接健康度close()通过信号监听在程序退出时优雅关闭连接池。5.2 MongoDB文档型数据库MongoDB 是高性能、开源、无模式的文档型数据库介于关系与非关系之间是非关系数据库中功能最丰富、最像关系数据库的产品。它采用类似 JSON 的 bson 格式存储数据查询语言强大支持复杂数据类型与索引。MySQL 与 MongoDB 的对应关系如下图所示两者操作语义高度相似Go 支持 MongoDB 最好的驱动是 mgogopkg.in/mgo.v2安装方式go get gopkg.in/mgo.v2操作示例package main import ( fmt log gopkg.in/mgo.v2 gopkg.in/mgo.v2/bson ) type Person struct { Name string Phone string } func main() { session, err : mgo.Dial(server1.example.com,server2.example.com) if err ! nil { panic(err) } defer session.Close() // 可选将会话切换为单调模式 session.SetMode(mgo.Monotonic, true) c : session.DB(test).C(people) err c.Insert(Person{Ale, 55 53 8116 9639}, Person{Cla, 55 53 8402 8510}) if err ! nil { log.Fatal(err) } result : Person{} err c.Find(bson.M{name: Ale}).One(result) if err ! nil { log.Fatal(err) } fmt.Println(Phone:, result.Phone) }可以看到 mgo 的操作方式与 beego orm 几乎类似——都是基于 struct 的操作方式这正是 Go Style 在数据访问层的统一体现。六、本章知识图谱与后续延伸第 5 章的核心收获可以归纳为一张能力清单层次技术点实战价值接口层database/sqldriver接口族驱动即插即用数据库迁移零代码改动关系型MySQL / SQLite / PostgreSQL掌握三套 CRUD 范式及 DSN、占位符差异ORM 层beego ormstruct 映射、自动建表、关系查询、原生 SQLNOSQL 层Redisredigo、MongoDBmgo缓存/会话/文档型数据的高性能读写学习建议本小结特别强调希望大家能够深入去了解 database/sql 的设计思想——因为无论是驱动开发、ORM 封装还是 NOSQL 接入最终都回归到这套标准接口的约定之上。官方 Go database/sql tutorial 提供了惯用的范例及详细说明可作为深入学习的补充资料。下一章将进入 session 和数据存储 的学习届时第 5 章掌握的数据库操作能力将直接服务于 Web 应用中的会话持久化与用户数据管理。相关文档导航本章导言访问数据库接口详解database/sql 接口关系型实战使用 MySQL 数据库 · 使用 SQLite 数据库 · 使用 PostgreSQL 数据库ORM 开发使用 Beego orm 库进行 ORM 开发NOSQL 操作NOSQL 数据库操作全书目录preface.md赞分享文档教程【免费下载链接】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点击查看免费下载相关推荐Go 数据库编程全攻略database/sql 接口、关系型驱动、beedb ORM 与 NoSQL 实战回顾build-web-application-with-golang 第 5 章总结Go 数据库编程全攻略database/sql 接口、关系型驱动、beedb ORM 与 NoSQL 实战回顾build web application w文档教程第五章总结Go 数据库编程全貌——从 database/sql 接口设计到 beedb ORM 与 NoSQL 实践第五章总结Go 数据库编程全貌——从 database/sql 接口设计到 beedb ORM 与 NoSQL 实践 导读 本章是《Build Web App文档教程Go 数据库操作全景指南database/sql 接口、ORM 与 NoSQL 实战总结Build Web Application with Golang 第 5 章Go 数据库操作全景指南database/sql 接口、ORM 与 NoSQL 实战总结Build Web Application with Golang文档教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考