前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Go实战]golang使用mysql实例和第三方库Gendry

[Go实战]golang使用mysql实例和第三方库Gendry

作者头像
TOMOCAT
发布2020-06-10 15:00:08
1.2K0
发布2020-06-10 15:00:08
举报
文章被收录于专栏:懂点编程的数据分析师

导入对应的包

代码语言:javascript
复制
// 安装
$ go get github.com/go-sql-driver/mysql

// 导入
import (
    "database/sql"
    _ "thirdpkg/go-sql-driver/mysql"
)

初始化mysql客户端

代码语言:javascript
复制
// 打开数据库,格式是⽤户名:密码@协议/数据库名称?编码⽅式
db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8"
if err != nil {
    fmt.Println(err)
}
// 确保db正常关闭
defer db.Close()
// 使用前Ping, 确保db连接正常
err = db.Ping()
if err != nil {
    fmt.Println(err)
}

数据库查询示例

golang本身的mysql库存在很多不便利的地方和一些坑,需要注意一下

代码语言:javascript
复制
// 假设日期和查询条件是从http客户端发过来的参数
// start_day: "2020-05-02"
// end_day: "2020-05-10"
// city: "[1,2,3,4,5,6]"

// 1) 获取参数并校验参数有效性
var citys []int
r.FormValue("city")
startDay := r.FormValue("start_day")
endDay := r.FormValue("end_day")

err = json.Unmarshal([]byte(cityStr), &citys)
if err != nil {
    fmt.Println(err)
}
if ok, _ := regexp.MatchString(`^\d{4}-\d{2}-\d{2}$`, startDay); !ok {
    fmt.Printf("invalid param, start day:[%s]\n", startDay)
}
if ok, _ := regexp.MatchString(`^\d{4}-\d{2}-\d{2}$`, endDay); !ok {
    fmt.Printf("invalid param, end day:[%s]\n", endDay)
}

// 2) 构造sql语句
sqlText := `
select
    sum(sales)/(to_days('end_day') - to_days('start_day')) as daily_sum,
    sum(price)/(to_days('end_day') - to_days('start_day')) as daily_price
from sales_table
where dt between 'start_day' and 'end_day' 
    and city_id in %s 
`

// 获取城市对应的range字符串用于sql语句:"[1,2,3,4,5,6]" ==> "(1,2,3,4,5,6)"
cityRange := genSQLRangeStrByIntArr(citys)

// 通过fmt.Sprintf拼接得到对应的字符串
sqlText = fmt.Sprintf(sqlText, cityRange)

// 对于多次出现的变量, 使用strings.Replace替换
sqlText = strings.Replace(sqlText, "start_day", startDay, -1)
sqlText = strings.Replace(sqlText, "endDay", endDay, -1)

// 3) 查询sql
rows, err := db.Query(sqlText)
defer rows.Close() // rows必须scan, 否则会导致链接无法关闭而一直占用链接, 直到超过设置的生命周期
if err != nil {
    fmt.Println(err)
}

// 存储结果的切片, 用于存储多行返回结果
var resInfoArr []*resInfo
for rows.Next() {
    var tempInfo resInfo
    // 注意rows.Scan的参数顺序和个数都很重要, 必须和sql查询语句的返回结果一一对应
    // 另外必须注意结构体的变量类型也必须和mysql一致
    rows.Scan(&resInfo.dailySum, &resInfo.dailyPrict)
    resInfoArr = append(resInfoArr, &tempInfo)
}


// 存储结果的结构体
type resInfo struct {
    dailySum   float64 `db:"daily_sum"`
    dailyPrict float63 `db:"daily_price"`
}

// 生成between...and...的范围字符串, 用于SQL语句
func genSQLRangeStrByIntArr(arr []int) (res string) {
    var tempStrArr = make([]string, len(arr))
    for k, v := range arr {
        tempStrArr[k] = fmt.Sprintf("%d", v)
    }
    res = "(" + strings.Join(tempStrArr, ",") + ")"
    return
}

其他操作示例

代码语言:javascript
复制
import (
    _"mysql"
    "database/sql"
    "fmt"
)
 
func check(err error){
    if err!=nil{
        fmt.Println(err)
    }
 
}
 
 
func main(){
    db,err:=sql.Open("mysql","root:123456@tcp(127.0.0.1:3306)/employee")
    check(err)
 
    //query
    type info struct {
        id      int     `db:"id"`
        name    string  `db:"name"`
        age     int     `db:"age"`
        sex     string  `db:"sex"`
        salary  int     `db:"salary"`
        work    string  `db:"work"`
        inparty string  `db:"inparty"`
    }
    rows,err:=db.Query("SELECT * FROM message")
    check(err)
    for rows.Next(){
        var s info
        err=rows.Scan(&s.id,&s.name,&s.age,&s.sex,&s.salary,&s.work,&s.inparty,)
        check(err)
        fmt.Println(s)
    }
    rows.Close()
    
    //insert
    db.Exec("INSERT INTO message(id,name,age,sex,salary,work,inparty)VALUES (?,?,?,?,?,?,?)",7,"李白",80,"男",1000,"中","是")
 
    //update
    results,err:=db.Exec("UPDATE message SET salary=? where id=?",8900,3)
    check(err)
    fmt.Println(results.RowsAffected())
 
 
    //delete
    results,err:=db.Exec("DELETE FROM message where id=?",2)
    check(err)
    fmt.Println(results.RowsAffected())

第三方库: gendry

以我们上面的查询为例,golang本身的go-sql-driver/mysql本身编程和维护方便都有不少需要注意的问题,Gendry是一个用于辅助操作数据库的Go包,提供了一系列的方法来为你调用标准库database/sql中的方法准备参数。

主要包括三部分:managerbuilderscanner

详细的资料可以阅读各个库的README

1. manager

主要用于初始化连接池,即sql.DB对象,设置各种参数:

代码语言:javascript
复制
var db *sql.DB
var err error
db, err = manager
        .New(dbName, user, password, host)
        .Set(
            manager.SetCharset("utf8"),
            manager.SetAllowCleartextPasswords(true),
            manager.SetInterpolateParams(true),
            manager.SetTimeout(1 * time.Second),
            manager.SetReadTimeout(1 * time.Second)
        ).Port(3302).Open(true)

manager本质做的事情即生成dataSourceName,一般它的格式如下:

代码语言:javascript
复制
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
2. Builder

Builder用于生成sql语句,手写sql简单直观但是可维护性差,并且硬编码容易出错,如果遇到大where in查询,且in的集合内容又是动态的就很麻烦了。

代码语言:javascript
复制
where := map[string]interface{}{
    "city in": []string{"beijing", "shanghai"},
    "score": 5,
    "age >": 35,
    "address": builder.IsNotNull,
    "_orderby": "bonus desc",
    "_groupby": "department",
}
table := "some_table"
selectFields := []string{"name", "age", "sex"}
cond, values, err := builder.BuildSelect(table, where, selectFields)

//cond = SELECT name,age,sex FROM g_xxx WHERE (score=? AND city IN (?,?) AND age>? AND address IS NOT NULL) GROUP BY department ORDER BY bonus DESC
//values = []interface{}{"beijing", "shanghai", 5, 35}

rows,err := db.Query(cond, values...)

如果你想清除where map中的零值可以使用builder.OmitEmpty

代码语言:javascript
复制
where := map[string]interface{}{
        "score": 0,
        "age": 35,
    }
finalWhere := builder.OmitEmpty(where, []string{"score", "age"})
// finalWhere = map[string]interface{}{"age": 35}

// support: Bool, Array, String, Float32, Float64, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, Map, Slice, Interface, Struct

同时,builder还提供一个便捷方法来进行聚合查询,比如:count,sum,max,min,avg

代码语言:javascript
复制
where := map[string]interface{}{
    "score > ": 100,
    "city in": []interface{}{"Beijing", "Shijiazhuang",}
}
// AggregateSum,AggregateMax,AggregateMin,AggregateCount,AggregateAvg is supported
result, err := AggregateQuery(ctx, db, "tableName", where, AggregateSum("age"))
sumAge := result.Int64()

result,err = AggregateQuery(ctx, db, "tableName", where, AggregateCount("*")) 
numberOfRecords := result.Int64()

result,err = AggregateQuery(ctx, db, "tableName", where, AggregateAvg("score"))
averageScore := result.Float64()

对于比较复杂的查询, NamedQuery将会派上用场:

代码语言:javascript
复制
cond, vals, err := builder.NamedQuery("select * from tb where name={{name}} and id in (select uid from anothertable where score in {{m_score}})", map[string]interface{}{
    "name": "caibirdme",
    "m_score": []float64{3.0, 5.8, 7.9},
})

assert.Equal("select * from tb where name=? and id in (select uid from anothertable where score in (?,?,?))", cond)
assert.Equal([]interface{}{"caibirdme", 3.0, 5.8, 7.9}, vals)
3. Scanner

执行了数据库操作之后,要把返回的结果集和自定义的struct进行映射。Scanner提供一个简单的接口通过反射来进行结果集和自定义类型的绑定:

代码语言:javascript
复制
type Person struct {
    Name string `ddb:"name"`
    Age int `ddb:"m_age"`
}

rows,err := db.Query("SELECT age as m_age,name from g_xxx where xxx")
defer rows.Close()

var students []Person

scanner.Scan(rows, &students)

for _,student := range students {
    fmt.Println(student)
}

scanner进行反射时会使用结构体的tag,如上所示,scanner会把结果集中的 m_age绑定到结构体的Age域上。默认使用的tagNameddb:"xxx",你也可以自定义:

代码语言:javascript
复制
scanner.SetTagName("json")

type Person struct {
    Name string `json:"name"`
    Age int `json:"m_age"`
}

// ...
var student Person
scaner.Scan(rows, &student)

scaner.SetTagName是全局设置,为了避免歧义,只允许设置一次,一般在初始化DB阶段进行此项设置

4. ScanMap

ScanMap方法返回的是一个map,有时候你可能不太像定义一个结构体去存你的中间结果,那么ScanMap或许比较有帮助:

代码语言:javascript
复制
rows,_ := db.Query("select name,m_age from person")
result,err := scanner.ScanMap(rows)
for _,record := range result {
    fmt.Println(record["name"], record["m_age"])
}

需要注意的点:

  • 如果是使用Scan或者ScanMap的话,你必须在之后手动close rows
  • 传给Scan的必须是引用
  • ScanCloseScanMapClose不需要手动close rows
5. CLI Tool

除了以上APIGendry还提供了一个命令行工具来进行代码生成,既可以生成Gendry相关的golang结构体,也可以生成完整的数据层dao layer

https://github.com/caibirdme/gforge

  • 安装
代码语言:javascript
复制
go get -u github.com/caibirdme/gforge
  • 用法
代码语言:javascript
复制
##################################################################
# 帮助文档
##################################################################
> gforge -h
A collection of tools to generate code for operating database supported by Gendry

Options:

  -h, --help   display help information
  -v           version

Commands:

  help    display help information
  table   schema could generate go struct code for given table
  dao     dao generates code of dao layer by given table name

##################################################################
# 生成表格对应的结构体
##################################################################
> gforge help table
schema could generate go struct code for given table

Options:

  -d               database name
  -t               table name
  -u               user name
  -p               password
  -h[=localhost]   host
  -P[=3306]        port

> gforge table -uusername -ppassword -hip -dinformation_schema -tCOLUMNS

// COLUMNS is a mapping object for COLUMNS
type COLUMNS struct {
    TABLECATALOG string `json:"TABLE_CATALOG"
    TABLESCHEMA string `json:"TABLE_SCHEMA"
    TABLENAME string `json:"TABLE_NAME"
    COLUMNNAME string `json:"COLUMN_NAME"
    ORDINALPOSITION uint64 `json:"ORDINAL_POSITION"
    COLUMNDEFAULT string `json:"COLUMN_DEFAULT"
    ISNULLABLE string `json:"IS_NULLABLE"
    DATATYPE string `json:"DATA_TYPE"
    CHARACTERMAXIMUMLENGTH uint64 `json:"CHARACTER_MAXIMUM_LENGTH"
    CHARACTEROCTETLENGTH uint64 `json:"CHARACTER_OCTET_LENGTH"
    NUMERICPRECISION uint64 `json:"NUMERIC_PRECISION"
    NUMERICSCALE uint64 `json:"NUMERIC_SCALE"
    DATETIMEPRECISION uint64 `json:"DATETIME_PRECISION"
    CHARACTERSETNAME string `json:"CHARACTER_SET_NAME"
    COLLATIONNAME string `json:"COLLATION_NAME"
    COLUMNTYPE string `json:"COLUMN_TYPE"
    COLUMNKEY string `json:"COLUMN_KEY"
    EXTRA string `json:"EXTRA"
    PRIVILEGES string `json:"PRIVILEGES"
    COLUMNCOMMENT string `json:"COLUMN_COMMENT"
    GENERATIONEXPRESSION string `json:"GENERATION_EXPRESSION"
}

##################################################################
# 根据一张表生成对应的dao layer
##################################################################
> gforge dao -uusername -ppassword -hip -dinformation_schema -tCOLUMNS | gofmt
package COLUMNS

import (
    "database/sql"
    "errors"
    "github.com/didichuxing/gendry/builder"
    "github.com/didichuxing/gendry/scanner"
)

/*
    This code is generated by ddtool
*/

// COLUMNS is a mapping object for COLUMNS
type COLUMNS struct {
    TABLECATALOG           string `json:"TABLE_CATALOG"`
    TABLESCHEMA            string `json:"TABLE_SCHEMA"`
    TABLENAME              string `json:"TABLE_NAME"`
    COLUMNNAME             string `json:"COLUMN_NAME"`
    ORDINALPOSITION        uint64 `json:"ORDINAL_POSITION"`
    COLUMNDEFAULT          string `json:"COLUMN_DEFAULT"`
    ISNULLABLE             string `json:"IS_NULLABLE"`
    DATATYPE               string `json:"DATA_TYPE"`
    CHARACTERMAXIMUMLENGTH uint64 `json:"CHARACTER_MAXIMUM_LENGTH"`
    CHARACTEROCTETLENGTH   uint64 `json:"CHARACTER_OCTET_LENGTH"`
    NUMERICPRECISION       uint64 `json:"NUMERIC_PRECISION"`
    NUMERICSCALE           uint64 `json:"NUMERIC_SCALE"`
    DATETIMEPRECISION      uint64 `json:"DATETIME_PRECISION"`
    CHARACTERSETNAME       string `json:"CHARACTER_SET_NAME"`
    COLLATIONNAME          string `json:"COLLATION_NAME"`
    COLUMNTYPE             string `json:"COLUMN_TYPE"`
    COLUMNKEY              string `json:"COLUMN_KEY"`
    EXTRA                  string `json:"EXTRA"`
    PRIVILEGES             string `json:"PRIVILEGES"`
    COLUMNCOMMENT          string `json:"COLUMN_COMMENT"`
    GENERATIONEXPRESSION   string `json:"GENERATION_EXPRESSION"`
}

//GetOne gets one record from table COLUMNS by condition "where"
func GetOne(db *sql.DB, where map[string]interface{}) (*COLUMNS, error) {
    if nil == db {
        return nil, errors.New("sql.DB object couldn't be nil")
    }
    cond, vals, err := builder.BuildSelect("COLUMNS", where, nil)
    if nil != err {
        return nil, err
    }
    row, err := db.Query(cond, vals...)
    if nil != err || nil == row {
        return nil, err
    }
    defer row.Close()
    var res *COLUMNS
    err = scanner.Scan(row, &res)
    return res, err
}

//GetMulti gets multiple records from table COLUMNS by condition "where"
func GetMulti(db *sql.DB, where map[string]interface{}) ([]*COLUMNS, error) {
    if nil == db {
        return nil, errors.New("sql.DB object couldn't be nil")
    }
    cond, vals, err := builder.BuildSelect("COLUMNS", where, nil)
    if nil != err {
        return nil, err
    }
    row, err := db.Query(cond, vals...)
    if nil != err || nil == row {
        return nil, err
    }
    defer row.Close()
    var res []*COLUMNS
    err = scanner.Scan(row, &res)
    return res, err
}

//Insert inserts an array of data into table COLUMNS
func Insert(db *sql.DB, data []map[string]interface{}) (int64, error) {
    if nil == db {
        return nil, errors.New("sql.DB object couldn't be nil")
    }
    cond, vals, err := builder.BuildInsert("COLUMNS", data)
    if nil != err {
        return 0, err
    }
    result, err := db.Exec(cond, vals...)
    if nil != err || nil == result {
        return 0, err
    }
    return result.LastInsertId()
}

//Update updates the table COLUMNS
func Update(db *sql.DB, where, data map[string]interface{}) (int64, error) {
    if nil == db {
        return 0, errors.New("sql.DB object couldn't be nil")
    }
    cond, vals, err := builder.BuildUpdate("COLUMNS", where, data)
    if nil != err {
        return 0, err
    }
    result, err := db.Exec(cond, vals...)
    if nil != err {
        return 0, err
    }
    return result.RowsAffected()
}

// Delete deletes matched records in COLUMNS
func Delete(db *sql.DB, where,data map[string]interface{}) (int64, error) {
    if nil == db {
        return 0, errors.New("sql.DB object couldn't be nil")
    }
    cond,vals,err := builder.BuildDelete("{{.TableName}}", where)
    if nil != err {
        return 0, err
    }
    result,err := db.Exec(cond, vals...)
    if nil != err {
        return 0, err
    }
    return result.RowsAffected()
}

Reference

[1] https://www.runoob.com/mysql/mysql-install.html [2] https://www.jianshu.com/p/af27b7a2a239 [3] https://blog.csdn.net/a670531899/article/details/81226752 [4] https://github.com/didi/gendry

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导入对应的包
  • 初始化mysql客户端
  • 数据库查询示例
  • 其他操作示例
  • 第三方库: gendry
    • 1. manager
      • 2. Builder
        • 3. Scanner
          • 4. ScanMap
            • 5. CLI Tool
            • Reference
            相关产品与服务
            数据库
            云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档