golang数据库crud

462 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

DML操作:增删改

有两种方法:

1.直接使用Exec函数添加

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

示例代码:

result, err := db.Exec("UPDATE userinfo SET username = ?, departname = ? WHERE uid = ?", "aaa","行政部",2)

2.首先使用Prepare获得stmt,然后调用Exec添加。

建立连接后,通过操作DB对象的Prepare()方法,可以进行

func (db *DB) Prepare(query string) (*Stmt, error) {
    return db.PrepareContext(context.Background(), query)
}

示例代码:

stmt,err:=db.Prepare("INSERT INTO userinfo(username,departname,created) values(?,?,?)") 
//补充完整sql语句,并执行
result,err:=stmt.Exec("aaa","技术部","2020-11-21")

预编译语句(Prepared Statement) 预编译语句(PreparedStatement)提供了诸多好处, 因此我们在开发中尽量使用它. 下面列出了使用预编译语句所提供的功能:

  • PreparedStatement 可以实现自定义参数的查询
  • PreparedStatement 通常来说, 比手动拼接字符串 SQL 语句高效.
  • PreparedStatement 可以防止SQL注入攻击

处理结果:影响数据库的行数

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

 count, err := result.RowsAffected()

获得刚刚添加数据的自增ID

id, err := result.LastInsertId()
DQL操作:查询
  • 查询单条数据,QueryRow 函数
func (db *DB) QueryRow(query string, args ...interface{}) *Row

示例代码:

var username, departname, created string
err := db.QueryRow("SELECT username,departname,created FROM userinfo WHERE uid=?", 3)
err := row.Scan(&uid, &username, &departname, &created)

//也可以简写:
err := db.QueryRow("SELECT username,departname,created FROM userinfo WHERE uid=?", 3).Scan(&uid, &username, &departname, &created)

扫描并复制当前行中每一列的值,但是要求行必须与行中的列数相同。

func (rs *Rows) Scan(dest ...interface{}) error
  1. rows.Scan 参数的顺序很重要, 需要和查询的结果的column对应. 例如 “SELECT * From user where age >=20 AND age < 30” 查询的行的 column 顺序是 “id, name, age” 和插入操作顺序相同, 因此 rows.Scan 也需要按照此顺序 rows.Scan(&id, &name, &age), 不然会造成数据读取的错位.
  2. 因为golang是强类型语言,所以查询数据时先定义数据类型,但是查询数据库中的数据存在三种可能:存在值,存在零值,未赋值NULL 三种状态, 因为可以将待查询的数据类型定义为sql.Nullxxx类型,可以通过判断Valid值来判断查询到的值是否为赋值状态还是未赋值NULL状态.
  • 查询多条数据,并遍历

Query 获取数据,for xxx.Next() 遍历数据:

首先使用Query()方法进行查询,如果查询无误,返回Rows,就是所有行的信息,类似结果集。

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

我们可以通过Next()方法判断是否存在下一条数据,如果有,可以使用之前的Scan()方法读取一行,然后继续判断,继续获取。这个过程的本质就是迭代,所以通常要配合循环使用。

func (rs *Rows) Next() bool

每次db.Query操作后,都建议调用rows.Close()。因为 db.Query() 会从数据库连接池中获取一个连接, 这个底层连接在结果集(rows)未关闭前会被标记为处于繁忙状态。当遍历读到最后一条记录时,会发生一个内部EOF错误,自动调用rows.Close(),但如果提前退出循环,rows不会关闭,连接不会回到连接池中,连接也不会关闭, 则此连接会一直被占用。因此通常我们使用 defer rows.Close() 来确保数据库连接可以正确放回到连接池中;不过阅读源码发现rows.Close()操作是幂等操作,即一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同,所以即便对已关闭的rows再执行close()也没关系。

插入示例:

package main
// step1:导入包
import (
    "database/sql"
    _"github.com/go-sql-driver/mysql"
    "fmt"
)

func main()  {
    // step2:打开数据库,相当于和数据库建立连接:db对象
    /*
    func Open(driverName, dataSourceName string) (*DB, error)
    drvierName,"mysql"
    dataSourceName,用户名:密码@协议(地址:端口)/数据库?参数=参数值
     */
    db,err:=sql.Open("mysql","root:123456@tcp(127.0.0.1:3306)/mytest?charset=utf8")
    if err !=nil{
        fmt.Println("连接失败。。")
        return
    }

    //step3:插入一条数据

    stmt,err:=db.Prepare("INSERT INTO userinfo(username,departname,created) values(?,?,?)")
    if err !=nil{
        fmt.Println("操作失败。。")
    }
    //补充完整sql语句,并执行
    result,err:=stmt.Exec("aaa","技术部","2020-11-21")
    if err !=nil{
        fmt.Println("插入数据失败。。")
    }
    //step4:处理sql操作后的结果
    lastInsertId,err:=result.LastInsertId()
    rowsAffected,err:=result.RowsAffected()
    fmt.Println("lastInsertId",lastInsertId)
    fmt.Println("影响的行数:", rowsAffected)

    //再次插入数据:
    result,_=stmt.Exec("test","人事部","2020-11-11")
    count,_:=result.RowsAffected()
    fmt.Println("影响的行数:",count)

    //step5:关闭资源
    stmt.Close()
    db.Close()

}

image-20210726175227997

更新数据:

我们可以按照插入数据的方式,使用Prepare()方法,然后再Exec()。也可以直接执行Exec()。

我们修改uid为2的这条数据,将username由原来的aaa改为bbb,department由原来的人事部改为行政部。

package main
// step1:导入包
import (
    "database/sql"
    _"github.com/go-sql-driver/mysql"
    "fmt"
)

func main()  {
    // step2:打开数据库,相当于和数据库建立连接:db对象
    /*
    func Open(driverName, dataSourceName string) (*DB, error)
    drvierName,"mysql"
    dataSourceName,用户名:密码@协议(地址:端口)/数据库?参数=参数值
     */
    db,err:=sql.Open("mysql","root:123456@tcp(127.0.0.1:3306)/mytest?charset=utf8")
    if err !=nil{
        fmt.Println("连接失败。。")
        return
    }

    //step3:修改一条数据,我们直接使用Exec()方法。
    //更新数据

    result, err := db.Exec("UPDATE userinfo SET username = ?, departname = ? WHERE uid = ?", "aaa","行政部",2)
    if err !=nil{
        fmt.Println("更新数据失败。。",err)
    }

    //step4:处理sql操作后的结果
    lastInsertId,err:=result.LastInsertId()
    rowsAffected,err:=result.RowsAffected()
    fmt.Println("lastInsertId",lastInsertId)
    fmt.Println("影响的行数:", rowsAffected)

    //step5:关闭资源
    db.Close()

}

image-20210726175331615

查询一条示例:

使用QueryRow查询一条数据

package main

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

func main() {
    /*
    查询一条
     */
    db, _ := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/mytest?charset=utf8")

    row := db.QueryRow("SELECT uid,username,departname,created FROM userinfo WHERE uid=?", 1)
    var uid int
    var username, departname, created string
    /*
    row:Scan()-->将查询的结果从row取出
        err对象
        判断err是否为空,
            为空,查询有结果,数据可以成功取出
            不为空,没有数据,sql: no rows in result set
     */
    err := row.Scan(&uid, &username, &departname, &created)

    //fmt.Println(err)
    if err != nil {
        fmt.Println("查无数据。。")
    } else {
        fmt.Println(uid, username, departname, created)

    }
    db.Close()

}

查询多条示例:

使用Query 获取数据,for xxx.Next() 遍历数据。

package main

//step1:导入包
import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

type User struct {
    uid        int
    username   string
    departname string
    created    string
}

func main() {
    /*
    查询操作:
     */
    //step2:打开数据库,建立连接
    db, _ := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/mytest?charset=utf8")

    //stpt3:查询数据库
    rows, err := db.Query("SELECT uid,username,departname,created FROM userinfo")
    if err != nil {
        fmt.Println("查询有误。。")
        return
    }

    //fmt.Println(rows.Columns()) //[uid username departname created]

    //创建slice,存入struct,
    datas := make([] User, 0)
    //step4:操作结果集获取数据
    for rows.Next() {
        var uid int
        var username string
        var departname string
        var created string
        if err := rows.Scan(&uid, &username, &departname, &created); err != nil {
            fmt.Println("获取失败。。")
        }

        //每读取一行,创建一个user对象,存入datas2中
        user := User{uid, username, departname, created}
        datas = append(datas, user)
    }
    //step5:关闭资源
    rows.Close()
    db.Close()

    for _, v := range datas {
        fmt.Println(v)
    }

}

/*
查询:处理查询后的结果:
    思路一:创建结构体
    思路二:将数据,存入到map中
 */

也可以操作map来存储查询的结果,这样就不用创建对应的结构体了。示例代码:

package main

//step1:导入包
import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

func main() {
    /*
    查询操作:
     */
    //step2:打开数据库,建立连接
    db, _ := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/mytest?charset=utf8")

    //stpt3:查询数据库
    rows, err := db.Query("SELECT uid,username,departname,created FROM userinfo")
    if err != nil {
        fmt.Println("查询有误。。")
        return
    }

    //fmt.Println(rows.Columns()) //[uid username departname created]
    //定义一个map,用于存储从数据库中查询出来的数据,字段作为key,string,数据作为value,任意类型,空接口

    datas := make([] map[string]interface{}, 0)

    //step4:操作结果集获取数据
    for rows.Next() {
        var uid int
        var username string
        var departname string
        var created string
        if err := rows.Scan(&uid, &username, &departname, &created); err != nil {
            fmt.Println("获取失败。。")
        }
        map1 := make(map[string]interface{})
        //将读取到的数据,存入了map中
        map1["uid"] = uid
        map1["username"] = username
        map1["departname"] = departname
        map1["created"] = created
        //将map存入切片中
        datas = append(datas, map1)

    }
    //step5:关闭资源
    rows.Close()
    db.Close()

    for _, v := range datas {
        fmt.Println(v)
    }

}