zoukankan      html  css  js  c++  java
  • Go使用数据库

    Go中提供了database包,database包下有sql.driver。该包用来定义操作数据库的接口,这保证了无论使用哪种数据库,操作都是相同的。但Go并没有提供连接数据库的driver,如果需要操作数据库,需要使用第三方的driver包。

    因此以mysql为例:

    go get github.com/Go-SQL-Driver/MySQL

    安装成功之后导入方式如下:

    import (
       "database/sql"
       "fmt"
       _ "github.com/go-sql-driver/mysql"
       "os"
    )
    

    Go虽然提供一些方法,但是并不会提供一些特有的方法,但是众所周知会有些特别的方法需要交给数据库的驱动去实现。

    选择使用匿名导入包,会让被导入的包编译到可执行文件中,通常来讲,导入包就可以使用包中的数据和方法。但是对于数据操作来讲,我们不应该直接使用导入的驱动包所提供的方法,而应该使用sql.DB对象所提供的统一的方法。因此在导入MySQL的区动时,使用匿名导入的方式。在导入一个数据库驱动后,该驱动会自行初始化并注册到Golang的database/sql上下文中,这样就能使用database/sql包所提供的方法来访问数据库了。

    一, 连接数据库

    sql中的Open()函数,原型如下所示。

    func Open(driverName, dataSourceName string) (*DB, error)

    driverName: 使用的驱动名,就是这个名字其实就是数据驱动注册到database/sql时所需使用的名字。

    dataSourceName:数据库连接信息。它包含数据库的用户名、密码、数据库注浆机及其需要连接的数据名等信息

    db, err := sql.Open("mysql", "用户名:密码@tcp(ip:端口)/数据库?charset=utf8")

    var db *sql.DB  // 创建连接对象
    func init()  {
       db, _ := sql.Open("mysql", "disk:disk@tcp(127.0.0.1:3306)/fileserver?charset=utf8")
       db.SetMaxOpenConns(1000) // 同时连接数
       err := db.Ping()  // 测试连接是否成功
       if err != nil {
          fmt.Println("error to link sql:" + err.Error())
          os.Exit(1)
       }
    }
    

    如上面代码所示:一般将数据连接的方法写在init函数中,保证到爆时候立即被执行

    二,数据的增删改查

    直接调用DB对象的Exec()方法如下所示:

    func (db *DB) Exec(query string, args ...interface{}) (Result, error)

    通过db.Exec()插入数据,通过返回的err可知插入失败的原因,通过返回的结果可以进一步查询本次插入数据库所影响的行数(RowsAffected)和最后插入的ID(如果数据库支持查询最后插入ID)。

    Exec()方法使用方式如下:

    result, err := db.Exec("INSERT INTO userinfo (username, departname, created) VALUES (?,?,?)","Steven", "北京", "2020-09-16")

    预编译语句(PreparedStatement)提供了诸多好处。PreparedStatement可以实现自定义参数的查询,通常会比手动拼接字符SQL串高效;还可以防止SQL注入攻击。因此一般情况下用PreparedStatement和Exec()完成对INSERT、UPDATE、DELETE操作。使用DB对象的Prepare()方法获得预编译对象stmt,然后调用Exec()方法,语句如下。

    func (db *DB) Prepare(query string) (*Stmt, error)

    使用如下:

    stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")

    result,err : stmt.Exec("zhouli", "开发部", "2020-05-16")

    获取影响数据库的行数,可以根据该数值判断是否操作(插入、删除或修改)成功。

    count, err := result.RowAffected()

    // 注意SQL的注入攻击
       stm, err := mydb.DBConn().Prepare("insert ignore into tab_file(`file_sha1`, `file_name`, `file_size`)" +
       "`file_addr`,`status`values(?,?,?,?,1)")
    if err != nil {
       fmt.Println("Failed to Prepare statement, err:" + err.Error())
       return false
    }
    defer stm.Close()
    ret, err := stm.Exec(filehash, filename, filesize,fileaddr)
    if err != nil {
       fmt.Println(err.Error())
       return false
    }
    // 检测受到更新影响的行数
    if rf,err := ret.RowsAffected();nil == err {
       if rf <= 0 {
          fmt.Printf("插入失败")
       }
       return true
    }
    

    三, 查询数据

    数据库查询步骤如下:

    1, 调用db.Query()方法执行SQL语句,此方法返回一个Rows作为查询结果。语法如下:

    func (db *DB) Query(query string,args ...interface{}) (*Rows, error)

    2, 将rows.Next()方法的返回值作为for循环的条件,迭代查询数据,语法如下所示。

    func (rs *Rows) Next() bool

    3, 再循环中,通过rows.Scan()方法读取每一行数据,语法如下所示:

    func (rs *Rows) Scan(dest ...interface{}) error

    4, 调用db.Close()关闭查询

    通过QueryRow()方法查询单挑数据,语法如下所示:

    func (db *DB) QueryRow(query string,args ...interface{}) *Row

    整体步骤如下所示:

    var username,departname, created string

    err := db.QueryRow("SELECT username,departname, created FORM user_info WHERE uid=?",3).Scan(&username, &departname, &created)

    查询多行数据:

    stmt, err := db.Prepare("SELECT * FORM user_info WHERE uid<?")
    
    rows, err := stmt.Query(10)
    
    user := new(UserTable)
    for rows.Next() {
        err := row.Scan(&user.Uid, &user.Username, &user.Department, &user.Created)
        if err != nil {
            panic(err)
            continue
        }
        fmt.Println(*user)
    }
    

    rows.Scan()方法参数的顺序很重要,必须和查询结果的column相对应(数量和顺序都要相对应),不然会造成数据读取的错位

    四, 注意点

    每次db.Query()操作后,都建议调用rows.Close()

    因为db.Query()会从数据库连接池中获取一个连接,这个底层连接在结果集rows未关闭前会被标记为繁忙状态。当遍历到最后一条记录,会发生一个内部错误EOF错误,自动调用row.Close()。

    但如果出现异常,提前退出循环,rows不会被关闭,连接不会回到连接池中,连接不会关闭,则词里阿杰会一直被占用。因此通常使用defer rows.Close()来确保数据库连接可以被正确的放回到连接池中。

    但是我们从源码中可以找到rows.Close()操作是幂等操作,而一个幂等操作最大的特点就是:其任意多次指定所产生的的影响与一次执行的影响相同。所以即便对已关闭的row再执行close也是没事的。

  • 相关阅读:
    android隐藏底部虚拟键Navigation Bar实现全屏
    TextView.setTextColor颜色值的理解
    GridLayout自定义数字键盘(两个EditText)
    EditText的一些属性及用法
    比较两个Long对象值
    vue全家桶(vue-cli,vue-router,vue-resource,vuex)-1
    vue-vuex状态管理-1
    vue-router进阶-3-过渡动效
    vue-router进阶-2-路由原信息
    vue-router进阶-1-导航守卫
  • 原文地址:https://www.cnblogs.com/zhoulixiansen/p/12902576.html
Copyright © 2011-2022 走看看