【go学习笔记五】标准库、数据库基本操作、gorm


golang标准库

bytes

提供了对字节切片进行读写操作的一系列函数。如比较、后缀检查、索引函数等。

类型强制转换

var i int = 100
var b byte = 10
b = byte(i)
fmt.Printf("string(b): %v\n", string(b)) // string(b): d ascii对照值:100

b2 := []byte{100, 101, 102}
var s string = string(b2)
fmt.Printf("s: %v\n", s) // s: def
b2 = []byte(s)
fmt.Printf("b2: %v\n", b2) // b2: [100 101 102]

基本函数

bytes.Contains:

s := "orzlinux.cn"
b := []byte(s)
// b1 := []byte("hqinglau")
// b2 := []byte("HQINGLAU")

// strings也有这个方法
fmt.Printf("strings.Contains(\"Hello\", \"lo\"): %v\n", strings.Contains("Hello", "lo"))
// strings.Contains("Hello", "lo"): true
b3 := bytes.Contains(b, []byte("linux")) // b3: true
fmt.Printf("b3: %v\n", b3)

bytes.Count

s := "orzlinux.cn-hqinglau-li"
b := []byte(s)
i := bytes.Count(b, []byte("li"))
fmt.Printf("i: %v\n", i) // i: 2

bytes.Repeat:

s := "orzlinux.cn-"
b := []byte(s)
b2 := bytes.Repeat(b, 3)
fmt.Printf("b2: %v\n", string(b2))
// b2: orzlinux.cn-orzlinux.cn-orzlinux.cn-

bytes.Replace

替换。

bytes.Runes(s):

考虑非英文字符:

func main() {
    s := "雷雅迪"
    b := []byte(s)
    r := bytes.Runes(b) // []rune类型
    fmt.Printf("len(b): %v\n", len(b))
    fmt.Printf("b[0]: %v\n", b[0])
    fmt.Printf("len(r): %v\n", len(r))
    fmt.Printf("r[0]: %v\n", r[0])
}

// len(b): 9
// b[0]: 233
// len(r): 3
// r[0]: 38647

bytes.Join:

b := [][]byte{[]byte("你好"), []byte("世界")}
b2 := []byte(",")
fmt.Println(string(bytes.Join(b, b2))) // 你好,世界

Reader类型

Reader实现了io.Reader, io.ReaderAt, io.WriterTo, io.Seeker, io.ByteScanner, io.RuneScanner接口,只读,可以seek。

func main() {
    data := "123456789"
    // 通过[]byte创建Reader
    re := bytes.NewReader([]byte(data))
    // 返回魏都区部分的长度
    fmt.Println("re len: ", re.Len())
    // 返回底层数据总长度
    fmt.Println("re size: ", re.Size())
    fmt.Println("---------------")

    buf := make([]byte, 2)
    for {
        n, err := re.Read(buf)
        if err != nil {
            fmt.Printf("err: %v\n", err)
            break
        }
        fmt.Println(string(buf[:n]))
    }

    fmt.Println("-----------------")
    // 设置偏移量,因为上面的操作已经修改了读取位置信息
    re.Seek(0, 0)
    for {
        b, err := re.ReadByte()
        if err != nil {
            fmt.Printf("err: %v\n", err)
            break
        }
        fmt.Println(string(b))
    }

    fmt.Println("-----------------")
    re.Seek(0, 0)
    off := int64(0)
    for {
        n, err := re.ReadAt(buf, off)
        if n == 0 { // 只判断err的话,最后一茬会漏掉
            fmt.Printf("err: %v\n", err)
            break
        }
        off += int64(n)
        fmt.Println(off, string(buf[:n]))
    }
}


// re len:  9
// re size:  9
// ---------------
// 12
// 34
// 56
// 78
// 9
// err: EOF
// -----------------
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// err: EOF
// -----------------
// 2 12
// 4 34
// 6 56
// 8 78
// 9 9
// err: EOF

Buffer类型

var b bytes.Buffer
// type Buffer struct {
//     buf      []byte // contents are the bytes buf[off : len(buf)]
//     off      int    // read at &buf[off], write at &buf[len(buf)]
//     lastRead readOp // last read operation, so that Unread* can work correctly.
// }
fmt.Printf("b: %v\n", b) // b: {[] 0 0}
b2 := bytes.NewBufferString("hiyou")
fmt.Printf("b2: %v\n", b2) // b2: hiyou

n, _ := b.WriteString("hello")
fmt.Printf("string(b.Bytes()): %v\n", string(b.Bytes()[:n]))

errors

type error interface {
    Error() string
}

不一定是一个错误,可以是任何信息,例如io.EOF表示数据读取结束,而不是遇到了什么错误。

func check(v int) (int, error) {
    if v <= 0 {
        return 0, errors.New("输入错误:应大于0")
    } else {
        return v, nil
    }
}

func main() {
    fmt.Println("1")
    _, err := check(-1)
    if err != nil {
        fmt.Printf("err: %v\n", err)
    }
    fmt.Println("2")
}

// 1
// err: 输入错误:应大于0
// 2

sort

提供了排序切片和自定义数据集以及相关功能的函数

简单int切片排序:

s := []int{2, 4, 1, 3}
fmt.Printf("sort.IntsAreSorted(s): %v\n", sort.IntsAreSorted(s))
sort.Ints(s)
fmt.Printf("s: %v\n", s) // s: [1 2 3 4]

自定义类型

应实现Len(), Less(), Swap()方法

type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

接收类型不能为切片:

image-20220419122350733

但是type一下就可以了:

image-20220419122852631

自定义结构体排序例子:

type Person struct {
    name string
    age  int
}

type PersonSlice []Person

func (p PersonSlice) Len() int {
    return len(p)
}

func (p PersonSlice) Less(i, j int) bool {
    if p[i].name == p[j].name {
        return p[i].age < p[j].age
    } else {
        return p[i].name < p[j].name
    }
}

func (p PersonSlice) Swap(i, j int) {
    p[i], p[j] = p[j], p[i]
}

func main() {
    s := PersonSlice{
        Person{
            name: "yadi",
            age:  23,
        },
        Person{
            name: "hqing",
            age:  20,
        },
        Person{
            name: "yadi",
            age:  21,
        },
    }
    sort.Sort(s)
    fmt.Printf("s: %v\n", s)
    // s: [{hqing 20} {yadi 21} {yadi 23}]
}

time

基本使用

func main() {
    t := time.Now()
    fmt.Printf("t: %v\n", t)
    // t: 2022-04-19 13:17:14.2850915 +0800 CST m=+0.003264801
    fmt.Printf("t.Year(): %v\n", t.Year()) // t.Year(): 2022
    fmt.Printf("t.Month(): %s\n", t.Month()) // t.Month(): April
    fmt.Printf("t.Month(): %d\n", t.Month()) // t.Month(): 4
    fmt.Printf("t.Day(): %v\n", t.Day()) // t.Day(): 19
}

时间戳

1970年1月1日至当前时间的总毫秒数。

t := time.Now()
fmt.Printf("t.Unix(): %v\n", t.Unix()) // t.Unix(): 1650345665

其他

t := time.Now()
t1 := t.Add(time.Hour * 24)
fmt.Printf("t1: %v\n", t1)
// t1: 2022-04-20 13:28:22.1204393 +0800 CST m=+86400.003243801
t2 := t1.Sub(t)
fmt.Printf("t2: %v\n", t2) // t2: 24h0m0s
b := t.Equal(t1)
fmt.Printf("b: %v\n", b) // b: false

// Before
// After
// Ticker
// Format 必须用这个时间格式化(go的诞生时间),不然不对
fmt.Println(t.Format("2006-01-02 03:04"))
// 2022-04-19 01:32
fmt.Println(t.Format("2006-01-02 03:04:05.000 PM Mon Jan"))
// 2022-04-19 01:32:11.888 PM Tue Apr

// ParseInLocation解析时间

encoding

json

json的编解码,将json字符串转换为struct,或者将struct转换为json。

type Person struct {
    Name  string
    Age   int
    Email string
}

func main() {
    p := Person{
        Name:  "tom",
        Age:   20,
        Email: "tom@gmailcom",
    }
    b, _ := json.Marshal(p)
    fmt.Printf("string(b): %v\n", string(b))
    // string(b): {"Name":"tom","Age":20,"Email":"tom@gmailcom"}

    s := `{"Name":"tom","Age":20,"Email":"tom@gmailcom"}`
    var m Person
    json.Unmarshal([]byte(s), &m)
    fmt.Printf("m: %#v\n", m)
    // m: main.Person{Name:"tom", Age:20, Email:"tom@gmailcom"}
}

xml

类似json

math

func main() {
    fmt.Printf("math.MaxFloat64: %v\n", math.MaxFloat64)
    fmt.Printf("math.Pi: %v\n", math.Pi)
    fmt.Printf("math.MaxInt32: %v\n", math.MaxInt32)
}

// math.MaxFloat64: 1.7976931348623157e+308
// math.Pi: 3.141592653589793
// math.MaxInt32: 2147483647

随机数

import (
    "fmt"
    "math"
    "math/rand"
)

func main() {
    fmt.Printf("math.Abs(-3.14): %v\n", math.Abs(-3.14))
    fmt.Printf("math.Pow(2, 10): %v\n", math.Pow(2, 10))
    fmt.Printf("math.Sqrt(64): %v\n", math.Sqrt(64))
    fmt.Printf("math.Cbrt(27): %v\n", math.Cbrt(27))       // 开立方
    fmt.Printf("math.Ceil(3.14): %v\n", math.Ceil(3.14))   // 向上取整
    fmt.Printf("math.Floor(8.75): %v\n", math.Floor(8.75)) // 向下取整
    fmt.Printf("math.Mod(10, 3): %v\n", math.Mod(10, 3))   // 取余
    // 取整数和小数部分
    int2, frac := math.Modf(3.1415)
    fmt.Printf("%f, %f\n", int2, frac) // 3.000000, 0.141500

    // 随机数
    rand.Seed(1)
    
    fmt.Printf("rand.Int: %d\n", rand.Int())
    fmt.Printf("rand.Int: %d\n", rand.Int())
    // rand.Int: 5577006791947779410
    // rand.Int: 8674665223082153551
    fmt.Printf("rand.Int: %d\n", rand.Intn(100)) // rand.Int: 47
}

操作数据库

创建数据

mysql> select database();
+------------+
| database() |
+------------+
| go_db      |
+------------+
1 row in set (0.00 sec)

表信息:

image-20220419143929811

安装包:

go.dev网站下的package:

go get -u github.com/go-sql-driver/mysql

连接数据库

当导入一个包时,包下文件里所有的init函数都会执行,但是有时候不需要将整个包都导入进去,仅仅是希望它执行init函数,这个时候就可以使用import _ 包路径引用这个包,无法通过包名调用包的其它函数。

import (
    "database/sql"
    "fmt"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

// 定义一个全局对象db
var db *sql.DB

func initDB() (err error) {
    dsn := "root:xxx@tcp(42.193.121.128:3306)/go_db?charset=utf8mb4&parseTime=True"
    // open不会校验账户密码是否正确
    db, err = sql.Open("mysql", dsn)
    if err != nil {
        panic(err)
    }
    print(db)

    // 尝试与数据库简历连接(检查dsn是否正确)
    // 为了让连接错误在初始化阶段就暴露出来
    err = db.Ping()
    if err != nil {
        return err
    }
    // 最大连接时长
    db.SetConnMaxLifetime(time.Minute * 3)
    // 最大连接数
    db.SetMaxOpenConns(10)
    // 空闲连接数
    db.SetMaxIdleConns(10)
    return nil
}

func main() {
    err := initDB()
    if err != nil {
        fmt.Printf("err: %v\n", err)
        return
    } else {
        fmt.Println("初始化成功")
        // 0xc0000a6c30初始化成功
    }
}

插入数据

func main() {
    err := initDB()
    if err != nil {
        fmt.Printf("err: %v\n", err)
        return
    }
    fmt.Println("数据库初始化成功!")

    sqlStr := "insert into user_tbl(username,password) values(?,?)"
    r, err := db.Exec(sqlStr, "hqinglau", "nopwd")
    if err != nil {
        fmt.Printf("err: %v\n", err)
    } else {
        i, _ := r.LastInsertId()
        fmt.Printf("i: %v\n", i) // i: 3
    }
}

查询单行

sqlStr := "select * from user_tbl where id=?"
var u User
err2 := db.QueryRow(sqlStr, 1).Scan(&u.id, &u.username, &u.password)
if err2 != nil {
    fmt.Printf("err: %v\n", err)
} else {
    fmt.Printf("u: %v\n", u) // u: {1 kite 456}
}

查询多行

sqlStr := "select * from user_tbl where id>?"
var u User
r, err2 := db.Query(sqlStr, 1)

if err2 != nil {
    fmt.Printf("err: %v\n", err)
} else {
    for r.Next() {
        r.Scan(&u.id, &u.username, &u.password)
        fmt.Printf("u: %v\n", u)
    }
}

image-20220420105410951

更新数据

sqlStr:="update user_tbl set username=?,password=? where id=?"
r, err := db.Exec(sqlStr, "big tom", "456", 2)
if err != nil {
    fmt.Printf("err: %v\n", err)
} else {
    i, _ := r.RowsAffected()
    fmt.Printf("受影响的行: %v\n", i) // 受影响的行: 1
}

再进行查询:

image-20220420110822247

删除数据

仍然是db.Exec()

sqlStr:="delete from user_tbl where id=?"
r, err := db.Exec(sqlStr, 3)
if err != nil {
    fmt.Printf("err: %v\n", err)
} else {
    i, _ := r.RowsAffected()
    fmt.Printf("受影响的行: %v\n", i)
}

image-20220420111101537

gorm

对象关系映射(Object Relational Mapping),ORM,解决面向对象和关系数据库存在的互不匹配的现象。

$ go get -u gorm.io/gorm
$ go get -u gorm.io/driver/mysql

模型

例子:

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

type Product struct {
    gorm.Model // 继承,包括id, 创建,更新,删除时间
    Code       string
    Price      uint
}

// type Model struct {
//     ID        uint `gorm:"primarykey"`
//     CreatedAt time.Time
//     UpdatedAt time.Time
//     DeletedAt DeletedAt `gorm:"index"`
// }

标签:

type Model struct {
  ID        uint           `gorm:"primaryKey;column:id"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

image-20220420145447500

image-20220420145530486

连接到数据库

dsn := "root:***@tcp(42.193.121.128:3306)/go_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
    panic(err)
}

官方支持的数据库类型有:MySQL,PostgreSQL,SQLite, SQL Server

根据结构体创建表

func main() {
    dsn := "root:***@tcp(42.193.121.128:3306)/go_db?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }
    // 创建表
    db.AutoMigrate(&Product{})
}

image-20220420131527392

image-20220420131553404

插入数据

// 插入数据
p := Product{
    Code:  "1001",
    Price: 100,
}
db.Create(&p)

image-20220420131953177

查询

var p Product
db.First(&p, 1) // 根据主键查询
fmt.Printf("p: %v\n", p)
// p: {{1 2022-04-20 13:18:48.959 +0800 CST 2022-04-20 13:18:48.959
// +0800 CST {0001-01-01 00:00:00 +0000 UTC false}} 1001 100}
db.First(&p, "code=?", "1001") // 根据列查询
fmt.Printf("p: %v\n", p)       // 同上

更新

var p Product
db.First(&p, 1)
db.Model(&p).Update("Price", 1000)

更新多个字段:

db.Model(&p).Updates(Product{Price: 200, Code: "AC101"})
db.Model(&p).Updates(map[string]interface{}{"Price": 200, "Code": "ada"})

删除

var p Product
db.First(&p, 1)
db.Delete(&p)
// [221.482ms] [rows:1] UPDATE `products` SET `deleted_at`='2022-04-20 13:33:24.912' 
// WHERE `products`.`id` = 1 AND `products`.`deleted_at` IS NULL

image-20220420133410596

软删除,只添加了删除标记。