java的数据库访问工具MyBatis给大家留下了深刻的印象,早在几年前,我刚刚接触golang的时候,也希望golang也有类似的工具,对golang稍微熟悉后,发现golang自带模板功能(text/template),于是在另外一个开源库sqlx的基础上,增加模板拼接sql的功能,所以 sqlt 就诞生了。
最早sqlt是托管在oschina,后续迁移到github.com/it512/sqlt,之后加入twiglab
go get github.com/twiglab/sqlt
sqlt 也支持 go mod
sqlt 深度依赖sqlx, 是在sqlx的基础上增加了模板功能,底层的数据库方法全部通过sqlx的NamedStmt和PrepareName完成对数据库的访问。
sqlt 对外提供的所有操作全部通过Dbop
struct 提供,Dbop struct 组合了sqlx.DB和模板(由Maker接口定义)
typeDbopstruct {Maker*sqlx.DB}
- sqlt 没有隐藏任何使用sqlx的细节,Dbop对外直接暴露sqlx.DB,任何sqlt.DB的方法均可以直接使用,请参考sqlx的文档
- sqlt 全部采用Parper和NamedStmt 完成对数据库的访问,所以也受到数据库驱动的限制,请详细参考数据库驱动的文档
目前sqlt自带的模板为text/template
,任何Maker接口的实现都可以作为sqlt的模板使用。
typeMakerinterface {MakeSql(string,interface{}) (string,error)}
(golang 自带的模板未必最好,欢迎pr更好模板实现)
最简单的创建方式为:
dbop:=sqlt.Default("postgres","dbname=testdb sslmode=disable","tpl/*.tpl")
注意:不要忘记引入数据库驱动
如果你有现成的数据库链接,或者对模板有特殊的要求,有可以用使用sqlt.New
方法创建
dbx:=sqlt.MustConnect("postgres","dbname=testdb sslmode=disable")tpl:=sqlt.NewSqlTemplate("tpl/*.tpl")tpl.SetDebug(true)dbop:=sqlt.New(dbx,tpl)
如果你的模板方法中用到了自定义的函数,sqlt也提供了一个NewSqlTemplateWithFuncs
的方法用于创建带有自定义函数的模板 (位于tpl.go
中)
再次说明sqlt默认自带的模板是text/template的封装实现,详细的用法请参考text/template
(example目录中有完整的例子)
最新版本中,所有的sqlt的方法都可以直接调用,分别为:
- func Query(execer TExecer, ctx context.Context, id string, param interface{}, h RowsExtractor) (err error)
- func Exec(execer TExecer, ctx context.Context, id string, param interface{}) (r sql.Result, err error)
以及对应的Must版本
Texcer 为*Dbop
,所有的方法都支持context.Context
,id 为模板的id,param为传递给模板和用于Prepare的参数(用于构建条件,和sql拼接)
Exec方法对应执行无返回的sql语句,如:insert, update, delete和存储过程。
Query方法用于执行带有返回结果的sql语句,如:select,和带有returnning子句的insert, update (returnning子句需要数据库和驱动的支持)
sqlt 提供了RowsExtractor
接口处理结果集,Query的最后一个参数中传入RowsExtractor的实现。
例子:
typeStaffstruct {StaffIdint`db:"staff_id"`//StaffNamestring`db:"staff_name"`//CreatedAt time.Time`db:"created_at"`//UpdatedAt time.Time`db:"updated_at"`//Ageint`db:"age"`//}typeStaffHandlerstruct {Staffs []*Staff}func (sh*StaffHandler)Extract(rs sqlt.Rows) (errerror) {forrs.Next() {staff:=new(Staff)iferr=rs.StructScan(staff);err!=nil {return}sh.Staffs=append(sh.Staffs,staff)}returnrs.Err()}staff:=new(Staff)staff.StaffId=67890h:=new(StaffHandler)sqlt.MustQuery(dbop,context.Background(),"Staff.select",staff,h)
sqlt不要求返回字段和struct字段一一对应,struct映射是按照row和struct公共部分映射的
staff 最为查询条件传入模板,模板会根据staff字段构建查询条件,然后通过sqlx执行查询,返回结果由RowsExtractor的实现StaffHandler处理模板:
{{ define "Staff.select"}}selectstaff_id,staff_name,created_atfromStaffwhere{{if .StaffId}} staff_id = :staff_id {{end}}{{if .StaffName}}and staff_name = :staff_name {{end}}{{end}}
cmd目录下的pggen是postgresql数据库的代码生成器,目前sqlt只提供了pg代码生成
首先需要说明的是:
- 生成器以表为单位生成对应的struct,常量,和增删改查通用的模板
- 生成器生成的内容直接输出到屏幕上(stdout),需要重定向到文件
- 生成器生成的代码片段,并不是可以直接使用的结果
生成器生成的是代码片段,并不是可以直接使用的结果
开发人员需要从生成结果中复制有用的片段到程序中
这些规则不是强制的,但是如果不遵循这些规则,使用sqlt的工作量会增大,从而失去价值
- 数据库对象(表,字段)的命名,采用下划线格式(_) 如: staff_name, staff_id
- struct 中的命名采用golang推荐的snake风格,如:StaffName, StaffId
- 每个表里面最好都加上 created_at和updated_at这2个字段,用于记录创建时间和修改时间(后面生成模板的时候会简单一些)
- 模板里面,逗号,and 都放在字段前面
- 模板的名称要能顾名思义
- sqlt底层用为sqlx,所以struct的处理也必须符合sqlx的要求(增加db tag)
第一条和第二天规则方便生成和struct和row直接的映射第三条和第四条方便模板的编写,确保不会生成没有字段的错误sql
较之 Mybatis,由于golang和模板的限制,sqlt存在下列问题
- text/template是没有上下文的, 所以无法帮助你处理结尾逗号(,)问题,避免的方法参见规则3,4
- 0值问题,golang默认0值,对于0值模板会排除,需要用自定义函数去处理
模板只是帮您拼接了sql, sql是否正确,以及效率需要开发人员保证。
MIT