前言
數(shù)據(jù)庫查詢
在 go 開發(fā)中, 查詢數(shù)據(jù)庫一般有兩種選擇:
- 使用 orm (gorm\xorm 等)
- 直接寫 SQL
直接編寫 SQL 語義清晰, 不易出錯, 但是遇到多個可變條件時顯得不靈活
ORM 有模型關系, 記錄預加載 (sql 生成優(yōu)化) 等功能, 但是 sql 語句對開發(fā)人員相對透明, 管了太多數(shù)據(jù)庫相關的東西, 相對封閉, 語法晦澀語義不明確, 想要操作 db 連接斯稳、構造復雜 SQL 很繁瑣
查詢構造器
對于查詢場景少巍糯、查詢條件相對固定的系統(tǒng), 直接寫 SQL 無疑是一種好的選擇梯醒。那么, 對于 SQL 多變的場景而又不想使用 orm 的開發(fā)者, 如何能快速開發(fā)數(shù)據(jù)層呢?
go 的官方包已經提供了好用的 database/sql 工具, 也有各個數(shù)據(jù)庫的驅動包, 屏蔽了底層驅動差異, 使數(shù)據(jù)庫查詢變得簡單, 只需提供 SQL 語句和占位符參數(shù)即可快速查詢, 也無需考慮 SQL 注入等問題癌别。那么, 只要解決了 SQL 語句和占位符參數(shù)的構造問題, 就解決了直接寫 SQL 的靈活性問題棒假。
為了解決 SQL 語句和占位符參數(shù)的構造問題, 我們需要查詢構造器 (Query Builder)。簡而言之, 查詢構造器就是利用 database/sql 的優(yōu)勢, 提供了一種 orm 和 raw sql 之間的中間方案囤捻。有了查詢構造器, 你可以在遇到不定 SQL 時動態(tài)構造 SQL, 遇到復雜確定 SQL 時直接寫原生 SQL, 使數(shù)據(jù)查詢更加靈活可控食听。
思路
做什么
查詢構造器, 顧名思義, 最主要的就是構造籍救。構造什么? 查詢語句。查詢語句本身就是一個滿足標準 SQL 規(guī)范的字符串, 所以我們要做查詢構造器, 主要的任務就是構造字符串渠抹。
拆解 SQL
在構造一條 SQL 之前, 不妨看看一條 SQL 是什么樣的吧蝙昙。
SELECT `name`,`age`,`school` FROM `test` WHERE `name` = 'jack'
復雜點的, 帶聯(lián)合查詢、分組梧却、排序奇颠、分頁
SELECT `t1`.`name`,`t1`.`age`,`t2`.`teacher`,`t3`.`address` FROM `test` as t1 LEFT JOIN `test2` as `t2` ON `t1`.`class` = `t2`.`class` INNER JOIN `test3` as t3 ON `t1`.`school` = `t3`.`school` WHERE `t1`.`age` >= 20 GROUP BY `t1`.`age` HAVING COUNT(`t1`.`age`) > 2 ORDER BY `t1`.`age` DESC LIMIT 10 OFFSET 0
當然, 標準 SQL 還有很多語法規(guī)定, 這里就不一一舉例。而對于規(guī)范中最常用的語法, 我們的查詢構造器必須要有構造它們的能力
一個標準的查詢語句結構如下:
SELECT [字段] FROM [表名] [JOIN 子句] [WHERE 子句] [GROUP BY 子句] [HAVING 子句] [ORDER BY 子句] [LIMIT 子句]
其中 JOIN 子句放航、WHERE 子句烈拒、 HAVING 子句和 LIMIT 子句會用到占位符參數(shù)
再看 INSERT、UPDATE广鳍、DELETE 操作的結構:
INSERT
INSERT INTO [表名] ([字段名]) VALUES ([要插入的值])
要插入的值會用到占位符參數(shù)
UPDATE
UPDATE [表名] [SET 子句] [WHERE 子句]
SET 子句和 WHERE 子句會用到占位符參數(shù)
DELETE
DELETE FROM [表名] [WHERE 子句]
WHERE 子句會用到占位符參數(shù)
OK, 拆解后是不是覺得 SQL 語句的基本結構很簡單? 要實現(xiàn)查詢構造器, 只需按照這些語句的結構構造出相應的字符串, 并保存需要的占位符參數(shù)即可荆几。
實現(xiàn)
有了思路, 實現(xiàn)起來就簡單了。
參考其他語言的查詢構造器, 方法名直接體現(xiàn) SQL 語法, 多為鏈式調用:
$db.table("`test`").
where("a", ">", 20).
where("b", "=", "aaa").
get()
要實現(xiàn)查詢構造器, 這是一個好的示范赊时。
話不多說, 開寫!
首先定義我們的 SQLBuilder 類型:
type SQLBuilder struct {
_select string // select 子句字符串
_insert string // insert 子句字符串
_update string // update 子句字符串
_delete string // delete 子句字符串
_table string // 表名
_join string // join 子句字符串
_where string // where 子句字符串
_groupBy string // group by 子句字符串
_having string // having 子句字符串
_orderBy string // order by 子句字符串
_limit string // limit 子句字符串
_insertParams []interface{} // insert 插入值需要的占位符參數(shù)
_updateParams []interface{} // update SET 子句需要的占位符參數(shù)
_whereParams []interface{} // where 子句需要的占位符參數(shù)
_havingParams []interface{} // having 子句需要的占位符參數(shù)
_limitParams []interface{} // limit 子句需要的占位符參數(shù)
_joinParams []interface{} // join 子句需要的占位符參數(shù)
}
SQLBuilder 的構造函數(shù):
func NewSQLBuilder() *SQLBuilder {
return &SQLBuilder{}
}
獲取 SQL 字符串
獲取字符串很簡單, 只要按照 SQL 的規(guī)定將各個子句組合即可吨铸。
獲取 QuerySQL:
var ErrTableEmpty = errors.New("table empty")
func (sb *SQLBuilder) GetQuerySQL() (string, error) {
if sb._table == "" {
return "", ErrTableEmpty
}
var buf strings.Builder
buf.WriteString("SELECT ")
if sb._select != "" {
buf.WriteString(sb._select)
} else {
buf.WriteString("*")
}
buf.WriteString(" FROM ")
buf.WriteString(sb._table)
if sb._join != "" {
buf.WriteString(" ")
buf.WriteString(sb._join)
}
if sb._where != "" {
buf.WriteString(" ")
buf.WriteString(sb._where)
}
if sb._groupBy != "" {
buf.WriteString(" ")
buf.WriteString(sb._groupBy)
}
if sb._having != "" {
buf.WriteString(" ")
buf.WriteString(sb._having)
}
if sb._orderBy != "" {
buf.WriteString(" ")
buf.WriteString(sb._orderBy)
}
if sb._limit != "" {
buf.WriteString(" ")
buf.WriteString(sb._limit)
}
return buf.String(), nil
}
tips: 上述代碼使用 strings.Builder 包來拼接字符串。當然構造查詢語句本身不是一個高頻操作, 不考慮效率使用 + 來拼接也是可以的
獲取 InsertSQL:
var ErrInsertEmpty = errors.New("insert content empty")
func (sb *SQLBuilder) GetInsertSQL() (string, error) {
if sb._table == "" {
return "", ErrTableEmpty
}
if sb._insert == "" {
return "", ErrInsertEmpty
}
var buf strings.Builder
buf.WriteString("INSERT INTO ")
buf.WriteString(sb._table)
buf.WriteString(" ")
buf.WriteString(sb._insert)
return buf.String(), nil
}
獲取 UpdateSQL:
var ErrUpdateEmpty = errors.New("update content empty")
func (sb *SQLBuilder) GetUpdateSQL() (string, error) {
if sb._table == "" {
return "", ErrTableEmpty
}
if sb._update == "" {
return "", ErrUpdateEmpty
}
var buf strings.Builder
buf.WriteString("UPDATE ")
buf.WriteString(sb._table)
buf.WriteString(" ")
buf.WriteString(sb._update)
if sb._where != "" {
buf.WriteString(" ")
buf.WriteString(sb._where)
}
return buf.String(), nil
}
獲取 DeteleSQL:
func (sb *SQLBuilder) GetDeleteSQL() (string, error) {
if sb._table == "" {
return "", ErrTableEmpty
}
var buf strings.Builder
buf.WriteString("DELETE FROM ")
buf.WriteString(sb._table)
if sb._where != "" {
buf.WriteString(" ")
buf.WriteString(sb._where)
}
return buf.String(), nil
}
獲取占位符參數(shù)
同樣, 我們要填充占位符 "?" 的參數(shù)也需要獲得, query祖秒、insert诞吱、update、delete 擁有的參數(shù)類型都有差別, 也都有著不同的順序
func (sb *SQLBuilder) GetQueryParams() []interface{} {
params := []interface{}{}
params = append(params, sb._joinParams...)
params = append(params, sb._whereParams...)
params = append(params, sb._havingParams...)
params = append(params, sb._limitParams...)
return params
}
func (sb *SQLBuilder) GetInsertParams() []interface{} {
params := []interface{}{}
params = append(params, sb._insertParams...)
return params
}
func (sb *SQLBuilder) GetUpdateParams() []interface{} {
params := []interface{}{}
params = append(params, sb._updateParams...)
params = append(params, sb._whereParams...)
return params
}
func (sb *SQLBuilder) GetDeleteParams() []interface{} {
params := []interface{}{}
params = append(params, sb._whereParams...)
return params
}
表名設置
設置表名, 這里我們設置完成后返回 SQLBuilder 指針自己, 可以完成鏈式調用竭缝。之后大部分方法都會使用這種方式房维。
func (sb *SQLBuilder) Table(table string) *SQLBuilder {
sb._table = table
return sb
}
用例:
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test`").
Select("*").
GetQuerySQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetQueryParams()
log.Println(sql) // SELECT * FROM `test`
log.Println(params) // []
}
select 子句
設置 select 子句, 支持多個參數(shù)用逗號隔開, 注意最后一個逗號要去掉
func (sb *SQLBuilder) Select(cols ...string) *SQLBuilder {
var buf strings.Builder
for k, col := range cols {
buf.WriteString(col)
if k != len(cols)-1 {
buf.WriteString(",")
}
}
sb._select = buf.String()
return sb
}
用例:
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test`").
Select("`age`", "COUNT(age)").
GetQuerySQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetQueryParams()
log.Println(sql) // SELECT `age`,COUNT(age) FROM `test`
log.Println(params) // []
}
where 子句
where
對于 where 子句, 第一個 where 條件需要 WHERE 關鍵字, 再有其它條件, 會通過 AND 和 OR 來連接, 那么我們可以增加 Where() 和 OrWhere() 方法, 兩個方法公共邏輯可以提出來:
func (sb *SQLBuilder) Where(field string, condition string, value interface{}) *SQLBuilder {
return sb.where("AND", condition, field, value)
}
func (sb *SQLBuilder) OrWhere(field string, condition string, value interface{}) *SQLBuilder {
return sb.where("OR", condition, field, value)
}
func (sb *SQLBuilder) where(operator string, condition string, field string, value interface{}) *SQLBuilder {
var buf strings.Builder
buf.WriteString(sb._where) // 載入之前的 where 子句
if buf.Len() == 0 { // where 子句還沒設置
buf.WriteString("WHERE ")
} else { // 已經設置, 拼接 OR 或 AND 操作符
buf.WriteString(" ")
buf.WriteString(operator)
buf.WriteString(" ")
}
buf.WriteString(field) // 拼接字段
buf.WriteString(" ")
buf.WriteString(condition) // 拼接條件 =、!=抬纸、<咙俩、>、like 等
buf.WriteString(" ")
buf.WriteString("?") // 拼接占位符
sb._where = buf.String() // 寫字符串
sb._whereParams = append(sb._whereParams, value) // push 占位符參數(shù)到數(shù)組
return sb
}
用例:
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test`").
Select("`name`", "`age`", "`school`").
Where("`name`", "=", "jack").
Where("`age`", ">=", 18).
OrWhere("`name`", "like", "%admin%").
GetQuerySQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetQueryParams()
log.Println(sql) // SELECT `name`,`age`,`school` FROM `test` WHERE `name` = ? AND `age` >= ? OR `name` like ?
log.Println(params) // [jack 18 %admin%]
}
上述代碼可以解決簡單的條件子句, 如果遇到 WHERE a = ? AND (b = ? OR c = ?) 這樣的復雜子句, 該如何構造呢? 面對這種場景, 我們需要提供書寫原生 where 子句的能力, 增加 WhereRaw() 和 OrWhereRaw() 方法:
func (sb *SQLBuilder) WhereRaw(s string, values ...interface{}) *SQLBuilder {
return sb.whereRaw("AND", s, values)
}
func (sb *SQLBuilder) OrWhereRaw(s string, values ...interface{}) *SQLBuilder {
return sb.whereRaw("OR", s, values)
}
func (sb *SQLBuilder) whereRaw(operator string, s string, values []interface{}) *SQLBuilder {
var buf strings.Builder
buf.WriteString(sb._where) // append
if buf.Len() == 0 {
buf.WriteString("WHERE ")
} else {
buf.WriteString(" ")
buf.WriteString(operator)
buf.WriteString(" ")
}
buf.WriteString(s) // 直接使用 raw SQL 字符串
sb._where = buf.String()
for _, value := range values {
sb._whereParams = append(sb._whereParams, value)
}
return sb
}
用例:
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test`").
Select("`name`", "`age`", "`school`").
WhereRaw("`title` = ?", "hello").
Where("`name`", "=", "jack").
OrWhereRaw("(`age` = ? OR `age` = ?) AND `class` = ?", 22, 25, "2-3").
GetQuerySQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetQueryParams()
log.Println(sql) // SELECT `name`,`age`,`school` FROM `test` WHERE `title` = ? AND `name` = ? OR (`age` = ? OR `age` = ?) AND `class` = ?
log.Println(params) // [hello jack 22 25 2-3]
}
where in
where in 也是常見的 where 子句, where in 子句分為 where in湿故、or where in暴浦、where not in溅话、or where not in 四種模式, 占位符數(shù)量等于 where in 的集合數(shù)量。
我們希望構造 where in 子句的方法入?yún)⑹且粋€ slice, 占位符的數(shù)量等于 slice 的長度, 那么我們需要封裝一個生成占位符的函數(shù):
func GenPlaceholders(n int) string {
var buf strings.Builder
for i := 0; i < n-1; i++ {
buf.WriteString("?,") // 生成 n-1 個 "?" 占位符
}
if n > 0 {
buf.WriteString("?") // 生成最后一個占位符, 如果 n <= 0 則不生成任何占位符
}
return buf.String()
}
按照 where in 子句的四種模式, 增加 WhereIn() OrWhereIn() WhereNotIn() OrWhereNotIn() 方法:
func (sb *SQLBuilder) WhereIn(field string, values ...interface{}) *SQLBuilder {
return sb.whereIn("AND", "IN", field, values)
}
func (sb *SQLBuilder) OrWhereIn(field string, values ...interface{}) *SQLBuilder {
return sb.whereIn("OR", "IN", field, values)
}
func (sb *SQLBuilder) WhereNotIn(field string, values ...interface{}) *SQLBuilder {
return sb.whereIn("AND", "NOT IN", field, values)
}
func (sb *SQLBuilder) OrWhereNotIn(field string, values ...interface{}) *SQLBuilder {
return sb.whereIn("OR", "NOT IN", field, values)
}
func (sb *SQLBuilder) whereIn(operator string, condition string, field string, values []interface{}) *SQLBuilder {
var buf strings.Builder
buf.WriteString(sb._where) // append
if buf.Len() == 0 {
buf.WriteString("WHERE ")
} else {
buf.WriteString(" ")
buf.WriteString(operator)
buf.WriteString(" ")
}
buf.WriteString(field)
plhs := GenPlaceholders(len(values)) // 生成占位符
buf.WriteString(" ")
buf.WriteString(condition)
buf.WriteString(" ")
buf.WriteString("(")
buf.WriteString(plhs) // 拼接占位符
buf.WriteString(")")
sb._where = buf.String()
for _, value := range values {
sb._whereParams = append(sb._whereParams, value) // push 占位符參數(shù)
}
return sb
}
用例:
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test`").
Select("`name`", "`age`", "`school`").
WhereIn("`id`", 1, 2, 3).
OrWhereNotIn("`uid`", 2, 4).
GetQuerySQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetQueryParams()
log.Println(sql) // SELECT `name`,`age`,`school` FROM `test` WHERE `id` IN (?,?,?) OR `uid` NOT IN (?,?)
log.Println(params) // [1 2 3 2 4]
}
group by 子句
group by 子句可以根據(jù)多個字段分組:
func (sb *SQLBuilder) GroupBy(fields ...string) *SQLBuilder {
var buf strings.Builder
buf.WriteString("GROUP BY ")
for k, field := range fields {
buf.WriteString(field)
if k != len(fields)-1 {
buf.WriteString(",")
}
}
sb._groupBy = buf.String()
return sb
}
having 子句和 where 子句基本相同, 這里就不費篇幅說明了, 詳細見 QueryBuilder/builder/builder.go
用例:
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test`").
Select("`school`", "`class`", "COUNT(*) as `ct`").
GroupBy("`school`", "`class`").
Having("`ct`", ">", "2").
GetQuerySQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetQueryParams()
log.Println(sql) // SELECT `school`,`class`,COUNT(*) as `ct` FROM `test` GROUP BY `school`,`class` HAVING `ct` > ?
log.Println(params) // [2]
}
order by 子句和 limit 子句
order by 子句可以根據(jù)多個字段來排序:
func (sb *SQLBuilder) OrderBy(operator string, fields ...string) *SQLBuilder {
var buf strings.Builder
buf.WriteString("ORDER BY ")
for k, field := range fields {
buf.WriteString(field)
if k != len(fields)-1 {
buf.WriteString(",")
}
}
buf.WriteString(" ")
buf.WriteString(operator) // DESC 或 ASC
sb._orderBy = buf.String()
return sb
}
limit 來限制查詢的結果, 這里我們使用 LIMIT OFFSET 語法, 這個語法是標準 SQL 規(guī)定的, LIMIT x,x 這個形式只有 mysql 支持
func (sb *SQLBuilder) Limit(offset, num interface{}) *SQLBuilder {
var buf strings.Builder
buf.WriteString("LIMIT ? OFFSET ?")
sb._limit = buf.String()
sb._limitParams = append(sb._limitParams, num, offset)
return sb
}
用例:
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test`").
Select("`name`", "`age`", "`school`").
Where("`name`", "=", "jack").
Where("`age`", ">=", 18).
OrderBy("DESC", "`age`", "`class`").
Limit(1, 10).
GetQuerySQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetQueryParams()
log.Println(sql) // SELECT `name`,`age`,`school` FROM `test` WHERE `name` = ? AND `age` >= ? ORDER BY `age`,`class` DESC LIMIT ? OFFSET ?
log.Println(params) // [jack 18 10 1]
}
join 子句
使用 join 子句后, SQL 變得復雜歌焦。標準 SQL join 有 left join飞几、right join、inner join独撇、full join 幾種模式 join 子句的 on 條件類似 where 子句, 連表后需要給表起別名用來區(qū)分字段所屬...面對這樣靈活多變的語法, 我們這里較好的方式就是提供 raw sql 的形式來處理 join 操作:
func (sb *SQLBuilder) JoinRaw(join string, values ...interface{}) *SQLBuilder {
var buf strings.Builder
buf.WriteString(sb._join)
if buf.Len() != 0 {
buf.WriteString(" ")
}
buf.WriteString(join) // 拼接 raw join sql
sb._join = buf.String()
for _, value := range values {
sb._joinParams = append(sb._joinParams, value)
}
return sb
}
用例 (構造一個復雜的查詢):
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test` as t1").
Select("`t1`.`name`", "`t1`.`age`", "`t2`.`teacher`", "`t3`.`address`").
JoinRaw("LEFT JOIN `test2` as `t2` ON `t1`.`class` = `t2`.`class`").
JoinRaw("INNER JOIN `test3` as t3 ON `t1`.`school` = `t3`.`school`").
Where("`t1`.`age`", ">=", 18).
GroupBy("`t1`.`age`").
Having("COUNT(`t1`.`age`)", ">", 2).
OrderBy("DESC", "`t1`.`age`").
Limit(1, 10).
GetQuerySQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetQueryParams()
log.Println(sql) // SELECT `t1`.`name`,`t1`.`age`,`t2`.`teacher`,`t3`.`address` FROM `test` as t1 LEFT JOIN `test2` as `t2` ON `t1`.`class` = `t2`.`class` INNER JOIN `test3` as t3 ON `t1`.`school` = `t3`.`school` WHERE `t1`.`age` >= ? GROUP BY `t1`.`age` HAVING COUNT(`t1`.`age`) > ? ORDER BY `t1`.`age` DESC LIMIT ? OFFSET ?
log.Println(params) // [18 2 10 1]
}
insert
insert SQL 構建:
func (sb *SQLBuilder) Insert(cols []string, values ...interface{}) *SQLBuilder {
var buf strings.Builder
// 拼接字段
buf.WriteString("(")
for k, col := range cols {
buf.WriteString(col)
if k != len(cols)-1 {
buf.WriteString(",")
}
}
buf.WriteString(") VALUES (")
// 拼接占位符
for k := range cols {
buf.WriteString("?")
if k != len(cols)-1 {
buf.WriteString(",")
}
}
buf.WriteString(")")
sb._insert = buf.String()
for _, value := range values { // push 占位符參數(shù)
sb._insertParams = append(sb._insertParams, value)
}
return sb
}
用例:
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test`").
Insert([]string{"`name`", "`age`"}, "jack", 18).
GetInsertSQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetInsertParams()
log.Println(sql) // INSERT INTO `test` (`name`,`age`) VALUES (?,?)
log.Println(params) // [jack 18]
}
update
update SQL 構建:
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test`").
Update([]string{"`name`", "`age`"}, "jack", 18).
Where("`id`", "=", 11).
GetUpdateSQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetUpdateParams()
log.Println(sql) // UPDATE `test` SET `name` = ?,`age` = ? WHERE `id` = ?
log.Println(params) // [jack 18 11]
}
delete
delete SQL 構建:
package main
import (
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
func main() {
sb := builder.NewSQLBuilder()
sql, err := sb.Table("`test`").
Where("`id`", "=", 11).
GetDeleteSQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetDeleteParams()
log.Println(sql) // DELETE FROM `test` WHERE `id` = ?
log.Println(params) // [11]
}
OK, 查詢構造器的實現(xiàn)到此結束, 是不是很簡單呢?
使用
查詢構造器實現(xiàn)了, 那么就結合 database/sql 用用吧!
以 mysql 為例:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/wazsmwazsm/QueryBuilder/builder"
"log"
)
// Info 定義一個數(shù)據(jù)模型, 用于接收查詢數(shù)據(jù)
type Info struct {
Age int
AgeCount int
}
func main() {
// 創(chuàng)建 mysql 連接
dataSource := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=utf8",
"test", "test", "127.0.0.1", 3306, "test")
mysqlConn, err := sql.Open("mysql", dataSource)
if err != nil {
log.Panic("Db connect failed!" + err.Error())
}
// 創(chuàng)建查詢構造器實例
sb := builder.NewSQLBuilder()
querySQL, err := sb.Table("`test`").
Select("`age`", "COUNT(age)").
GroupBy("`age`").
GetQuerySQL()
if err != nil {
log.Fatal(err)
}
params := sb.GetQueryParams()
// 執(zhí)行查詢
rows, err := mysqlConn.Query(querySQL, params...)
if err != nil {
log.Panic(err)
}
defer rows.Close()
// 查詢數(shù)據(jù)綁定到 info 結構中
infos := []*Info{}
for rows.Next() {
info := new(Info)
if err := rows.Scan(
&info.Age,
&info.AgeCount,
); err != nil {
log.Panicln(err)
}
infos = append(infos, info)
}
for _, info := range infos {
fmt.Println(info)
}
}
源碼地址
該項目的全部源碼詳見 QueryBuilder, 單元測試已 100% 覆蓋