技术经验分享:Go开源项目

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 技术经验分享:Go开源项目

gorp 是一个Go开源ORM框架.


Go关系型数据库持久化


我很含糊的称gorp是一个ORM框架. Go还没有真正的对象, 至少没有一个经典的感觉和Smalltalk/Java一样的,这是"O". gorp不知道任何你struct之间的关系(以后会实现,现在还在TODO list中). 所以"R"也是有质疑的(但是我使用这个名字,因为感觉上更贴切).


"M"是没有问题的. 给定一些Go结构和一个数据库, gorp应该可以减少你的重复代码.


我希望gorp能节省你的时间并简化从数据库获取数据的苦活来帮助你把精力投入到算法上而不是基础设施上面.


数据库驱动


gorp使用Go1 database/sql包. 一个完整可用的兼容驱动程序如下:


遗憾的是SQL数据库很多不一样的问题. gorp提供一个应该被每个数据库厂商实现的Dialect接口, Dialect支持如下数据库:


MySQL


PostgreSQL


sqlite3


这三个数据库都通过了测试, 请查看gorp_test.go, 例如针对这三个数据库的DSN.


特点


通过API或tag绑定struct字段到表的列


支持事务


从struct建立db架构正向工程(来做更好的单元测试)


在insert/update/delete的前后提供hook


自动为struct生成generate insert/update/delete语句


在insert后自动绑定自增主键到struct


通过主键删除


通过主键选择


可选的SQL跟踪日志


绑定任意SQL查询到struct


可通过一个version column来为update和delete实现乐观锁


待办事项


支持内嵌struct


安装


# install the library:


go get github.com/coopernurse/gorp


// use in your .go code:


import (


"github.com/coopernurse/gorp"


)


运行测试


现在测试测试包括了MySQL.我需要为测试工具添加额外的驱动, 但是现在你可以克隆repo并设置一个环境变量来为运行"go test"做准备


# Set env variable with dsn using mymysql format. From the mymysql docs,


# the format can be of 3 types:


#


# DBNAME/USER/PASSWD


# unix:SOCKPATHDBNAME/USER/PASSWD


# tcp:ADDRDBNAME/USER/PASSWD


#


# for example, on my box I use:


export GORP_TEST_DSN=gomysql_test/gomysql_test/abc123


# run the tests


go test


# run the tests and benchmarks


go test -bench="Bench" -benchtime 10


性能


gorp使用反射来构造SQL查询和绑定参数. 在gorp_test.go中查看BenchmarkNativeCrud 对 BenchmarkGorpCrud的一个简单的性能测试. 在我的MacBook Pro上它比手写SQL慢大约2%-3%.


示例


首先定义一些类型:


type Invoice struct {


Id int64


Created int64


Updated int64


Memo string


PersonId int64


}


type Person struct {


Id int64


Created int64


//代码效果参考:http://www.jhylw.com.cn/224639297.html

Updated int64

FName string


LName string


}


// Example of using tags to alias fields to column names


// The 'db' value is the column name


//


// A hyphen will cause gorp to skip this field, similar to the


// Go json package.


//


// This is equivalent to using the ColMap methods:


//


// table := dbmap.AddTableWithName(Product{}, "product")


// table.ColMap("Id").Rename("product_id")


// table.ColMap("Price").Rename("unit_price")


// table.ColMap("IgnoreMe").SetTransient(true)


//


type Product struct {


Id int64 db:"product_id"


Price int64 db:"unit_price"


IgnoreMe string db:"-"


}


然后创建一个映射器, 一般你再app启动时做一次.


// connect to db using standard Go database/sql API


// use whatever database/sql driver you wish


db, err := sql.Open("mymysql", "tcp:localhost:3306mydb/myuser/mypassword")


// construct a gorp DbMap


dbmap := &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{"InnoDB", "UTF8"}}


// register the structs you wish to use with gorp


// you can also use the shorter dbmap.AddTable() if you


// don't want to override the table name


//


// SetKeys(true) means we have a auto increment primary key, which


// will get automatically bound to your struct post-insert


//


t1 := dbmap.AddTableWithName(Invoice{}, "invoice_test").SetKeys(true, "Id")


t2 := dbmap.AddTableWithName(Person{}, "person_test").SetKeys(true, "Id")


t3 := dbmap.AddTableWithName(Product{}, "product_test").SetKeys(true, "Id")


自动创建删除已注册的表


// create all registered tables


dbmap.CreateTables()


// drop


dbmap.DropTables()


可选项:你可以传入一个log.Logger来跟踪全部的SQL语句:


// Will log all SQL statements + args as they are run


// The first arg is a string prefix to prepend to all log messages


dbmap.TraceOn("【gorp】", log.New(os.Stdout, "myapp:", log.Lmicroseconds))


// Turn off tracing


dbmap.TraceOff()


然后保存一些数据:


// Must declare as pointers so optional callback hooks


// can operate on your data, not copies


inv1 := &Invoice{0, 100, 200, "first order", 0}


inv2 := &Invoice{0, 100, 200, "second order", 0}


// Insert your rows


err := dbmap.Insert(inv1, inv2)


// Because we called SetKeys(true) on Invoice, the Id field


// will be populated after the Insert() automatically


fmt.Printf("inv1.Id=%d inv2.Id=%d\n", inv1.Id, inv2.Id)


你可以执行原始的SQL语句.尤其是批量操作时.


res, err := dbmap.Exec("delete from invoice_test where PersonId=?", 10)


想要做join? 只写SQL和struct, gorp将绑定他们:


// Define a type for your join


// It must contain all the columns in your SELECT statement


//


// The names here should match the aliased column names you specify


// in your SQL - no additional binding work required. simple.


//


type InvoicePersonView struct {


InvoiceId int64


PersonId int64


Memo string


FName string


}


// Create some rows


p1 := &Person{0, 0, 0, "bob", "smith"}


dbmap.Insert(p1)


// notice how we can wire up p1.Id to the invoice easily


inv1 := &Invoice{0, 0, 0, "xmas order", p1.Id}


dbmap.Insert(inv1)


// Run your query


query := "select i.Id InvoiceId, p.Id PersonId, i.Memo, p.FName " +


"from invoice_test i, person_test p " +


"where i.PersonId = p.Id"


list, err := dbmap.Select(InvoicePersonView{}, query)


// this should test true


expected := &InvoicePersonView{inv1.Id, p1.Id, inv1.Memo, p1.FName}


if reflect.DeepEqual(list【0】, expected) {


fmt.Println("Woot! My join worked!")


}


你也可以在一个事务中批量操作:


func InsertInv(dbmap DbMap, inv Invoice, per Person) error {


// Start a new transaction


trans := dbmap.Begin()


trans.Insert(per)


inv.PersonId = per.Id


trans.Insert(inv)


// if the commit is successful, a nil error is returned


return trans.Commit()


}


在更新数据到数据库(前/后)使用hook, 对时间戳很有效:


// implement the PreInsert and PreUpdate hooks


func (i Invoice) PreInsert(s gorp.SqlExecutor) error {


i.Created = time.Now().UnixNano()


i.Updated = i.Created


return nil


}


func (i Invoice) PreUpdate(s gorp.SqlExecutor) error {


i.Updated = time.Now().UnixNano()


return nil


}


// You can use the SqlExecutor to cascade additional SQL


// Take care to avoid cycles. gorp won't prevent them.


//


// Here's an example of a cascading delete


//


func (p Person) PreDelete(s gorp.SqlExecutor) error {


query := "delete from invoice_test where PersonId=?"


err := s.Exec(query, p.Id); if err != nil {


return err


}


return nil


}


你可以实现以下的hook


PostGet


PreInsert


PostInsert


PreUpdate


PostUpdate


PreDelete


PostDelete


All have the same signature. for example:


func (p MyStruct) PostUpdate(s gorp.SqlExecutor) error


乐观锁 (有点像JPA)


// Version is an auto-incremented number, managed by gorp


// If this property is present on your struct, update


// operations will be constrained


//


// For example, say we defined Person as:


type Person struct {


Id int64


Created int64


Updated int64


FName string


LName string


// automatically used as the Version col


// use table.SetVersionCol("columnName") to map a different


// struct field as the version field


Version int64


}


p1 := &Person{0, 0, 0, "Bob", "Smith", 0}


dbmap.Insert(p1) // Version is now 1


obj, err := dbmap.Get(Person{}, p1.Id)


p2 := obj.(*Person)


p2.LName = "Edwards"


dbmap.Update(p2) // Version is now 2


p1.LName = "Howard"


// Raises error because p1.Version == 1, which is out of date


count, err := dbmap.Update(p1)


_, ok := err.(gorp.OptimisticLockError)


if ok {


// should reach this statement


// in a real app you might reload the row and retry, or


// you might propegate this to the user, depending on the desired


// semantics


fmt.Printf("Tried to update row with stale data: %v\n", err)


} else {


// some other db error occurred - log or return up the stack


fmt.Printf("Unknown db err: %v\n", err)


}


至此结束.

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
25天前
|
自然语言处理 搜索推荐 Go
goctl 技术系列 - Go 模板入门
goctl 技术系列 - Go 模板入门
|
25天前
|
JSON 运维 Go
Go 项目配置文件的定义和读取
Go 项目配置文件的定义和读取
|
1月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
|
26天前
|
API
企业项目迁移go-zero实战(二)
企业项目迁移go-zero实战(二)
|
12天前
|
消息中间件 NoSQL Go
PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践
【9月更文挑战第7天】在从 PHP 的 ThinkPHP 框架迁移到 Go 的 Gin 框架时,涉及 Redis 延时消息队列的技术实践主要包括:理解延时消息队列概念,其能在特定时间处理消息,适用于定时任务等场景;在 ThinkPHP 中使用 Redis 实现延时队列;在 Gin 中结合 Go 的 Redis 客户端库实现类似功能;Go 具有更高性能和简洁性,适合处理大量消息。迁移过程中需考虑业务需求及系统稳定性。
|
1月前
|
JSON 缓存 监控
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
Viper 是一个强大的 Go 语言配置管理库,适用于各类应用,包括 Twelve-Factor Apps。相比仅支持 `.ini` 格式的 `go-ini`,Viper 支持更多配置格式如 JSON、TOML、YAML
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
|
1月前
|
算法 程序员 编译器
Go deadcode:查找没意义的死代码,对于维护项目挺有用!
Go deadcode:查找没意义的死代码,对于维护项目挺有用!
|
1月前
|
SQL 关系型数据库 MySQL
「Go开源」goose:深入学习数据库版本管理工具
「Go开源」goose:深入学习数据库版本管理工具
「Go开源」goose:深入学习数据库版本管理工具
|
1月前
|
缓存 JavaScript 前端开发
为开源项目 go-gin-api 增加 WebSocket 模块
为开源项目 go-gin-api 增加 WebSocket 模块
31 2
|
26天前
|
Kubernetes API Go
企业项目迁移go-zero实战(一)
企业项目迁移go-zero实战(一)