【Go学习】-01-6-数据库泛型新特性

[复制链接]
发表于 2025-11-14 04:51:10 | 显示全部楼层 |阅读模式

数据库操纵

操纵mysql

创建go_learn数据库后创建user表
  1. CREATE TABLE `user` (
  2.     `user_id` int(11) NOT NULL AUTO_INCREMENT,
  3.     `username` varchar(255) DEFAULT NULL,
  4.     `sex` varchar(255) DEFAULT NULL,
  5.     `email` varchar(255) DEFAULT NULL,
  6.     PRIMARY KEY (`user_id`)
  7.   ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
复制代码
  mysql的前置知识,我们这里就不讲了,可自行去学习mysql教程
  

Insert

   起首,必要引入mysql驱动
  通过go get github.com/go-sql-driver/mysql@v1.6.0引入依赖
  1. _ "github.com/go-sql-driver/mysql"
复制代码
我们的数据库地点是192.168.101.68:3306
用户名:root
暗码:mysql
插入一条纪录:名字bblb,性别man,邮箱bblb123456789@qq.com
  1. package mainimport (        "database/sql"        "fmt"        _ "github.com/go-sql-driver/mysql"
  2.         "log"        "time")var DB *sql.DBfunc init() {        db, err := sql.Open("mysql", "root:mysql@tcp(192.168.101.68:3306)/go_learn")        if err != nil {                panic(err)        }        //最大空闲毗连数,默认不设置,是2个最大空闲毗连        db.SetMaxIdleConns(5)        //最大毗连数,默认不设置,是不限定最大毗连数        db.SetMaxOpenConns(100)        // 毗连最大存活时间        db.SetConnMaxLifetime(time.Minute * 3)        //空闲毗连最大存活时间        db.SetConnMaxIdleTime(time.Minute * 1)        err = db.Ping()        if err != nil {                log.Println("数据库毗连失败")                db.Close()                panic(err)        }        DB = db}func save() {        r, err := DB.Exec("insert into user (username,sex,email) values(?,?,?)", "bblb", "man", "bblb123456789@qq.com")        if err != nil {                log.Println("实行sql语句堕落")                panic(err)        }        id, err := r.LastInsertId()        if err != nil {                panic(err)        }        fmt.Println("插入乐成:", id)}func main() {        defer DB.Close()        save()}
复制代码
检察数据库

Select

  1. type User struct {
  2.         UserId   int    `db:"user_id"`
  3.         Username string `db:"username"`
  4.         Sex      string `db:"sex"`
  5.         Email    string `db:"email"`
  6. }
  7. func query(id int)  (*User,error) {
  8.         rows, err := DB.Query("select * from user where user_id=? limit 1", id)
  9.         if err != nil{
  10.                 log.Println("查询出现错误:",err)
  11.                 return nil,errors.New(err.Error())
  12.         }
  13.         user := new(User)
  14.         for rows.Next() {
  15.                 if err := rows.Scan(&user.UserId,&user.Username,&user.Sex,&user.Email); err != nil{
  16.                         log.Println("scan error:",err)
  17.                         return nil,errors.New(err.Error())
  18.                 }
  19.         }
  20.         return user,nil
  21. }
  22. func main() {
  23.         defer DB.Close()
  24.         //save()
  25.         user,err := query(2)
  26.         if err != nil{
  27.                 log.Println("查询出现错误:",err)
  28.                 return
  29.         }
  30.         fmt.Printf("查询成功:%+v\n",user)
  31. }
复制代码
可以看到我们刚刚插入的id是2以是查2
  1. 查询成功:&{UserId:2 Username:bblb Sex:man Email:bblb123456789@qq.com}
复制代码
Update

  1. func update(username string, id int)  {
  2.         ret, err := DB.Exec("update user set username=? where user_id=?", username, id)
  3.         if err != nil {
  4.                 log.Println("更新出现问题:",err)
  5.                 return
  6.         }
  7.         affected, _ := ret.RowsAffected()
  8.         fmt.Println("更新成功的行数:",affected)
  9. }
复制代码
Delete

  1. func delete(id int)  {
  2.         ret, err := DB.Exec("delete from user where user_id=?", id)
  3.         if err != nil {
  4.                 log.Println("删除出现问题:",err)
  5.                 return
  6.         }
  7.         affected, _ := ret.RowsAffected()
  8.         fmt.Println("删除成功的行数:",affected)
  9. }
复制代码
sql变乱

mysql变乱特性:

  • 原子性
  • 划一性
  • 隔离性
  • 恒久性
使用tx来举行数据库的变乱操纵。
  1. func insertTx(username string)  {
  2.         tx, err := DB.Begin()
  3.         if err != nil {
  4.                 log.Println("开启事务错误:",err)
  5.                 return
  6.         }
  7.         ret, err := tx.Exec("insert into user (username,sex,email) values (?,?,?)", username, "man", "test@test.com")
  8.         if err != nil {
  9.                 log.Println("事务sql执行出错:",err)
  10.                 return
  11.         }
  12.         id, _ := ret.LastInsertId()
  13.         fmt.Println("插入成功:",id)
  14.         if username == "lisi" {
  15.                 fmt.Println("回滚...")
  16.                 _ = tx.Rollback()
  17.         }else {
  18.                 _ = tx.Commit()
  19.         }
  20. }
复制代码
go操纵Redis

   redis不另行先容,默认会,如果不相识,先去学习redis教程
  安装:go get github.com/go-redis/redis/v8
  1. package main
  2. import (
  3.         "context"
  4.         "fmt"
  5.         "github.com/go-redis/redis/v8"
  6. )
  7. func main() {
  8.         ctx := context.Background()
  9.         rdb := redis.NewClient(&redis.Options{
  10.                 Addr:     "192.168.101.68:6379",
  11.                 Password: "redis", // no password set
  12.                 DB:       0,       // use default DB
  13.         })
  14.         err := rdb.Set(ctx, "key", "value", 0).Err()
  15.         if err != nil {
  16.                 panic(err)
  17.         }
  18.         val, err := rdb.Get(ctx, "key").Result()
  19.         if err != nil {
  20.                 panic(err)
  21.         }
  22.         fmt.Println("key", val)
  23.         val2, err := rdb.Get(ctx, "key2").Result()
  24.         if err == redis.Nil {
  25.                 fmt.Println("key2 does not exist")
  26.         } else if err != nil {
  27.                 panic(err)
  28.         } else {
  29.                 fmt.Println("key2", val2)
  30.         }
  31. }
复制代码

泛型

非泛型函数

两个 差异范例的映射:一种用于存储值,一种用于存储值。int64和float64
  1. package main
  2. import "fmt"
  3. // SumInts adds together the values of m.
  4. func SumInts(m map[string]int64) int64 {
  5.     var s int64
  6.     for _, v := range m {
  7.        s += v
  8.     }
  9.     return s
  10. }
  11. // SumFloats adds together the values of m.
  12. func SumFloats(m map[string]float64) float64 {
  13.     var s float64
  14.     for _, v := range m {
  15.        s += v
  16.     }
  17.     return s
  18. }
  19. func main() {
  20.     // Initialize a map for the integer values
  21.     ints := map[string]int64{
  22.        "first":  34,
  23.        "second": 12,
  24.     }
  25.     // Initialize a map for the float values
  26.     floats := map[string]float64{
  27.        "first":  35.98,
  28.        "second": 26.99,
  29.     }
  30.     fmt.Printf("Non-Generic Sums: %v and %v\n",
  31.        SumInts(ints),
  32.        SumFloats(floats))
  33. }
复制代码
针对差异的范例我们都必要写对应的函数来举行求和,这黑白常贫苦的
泛型函数

Go 1.18 引入了泛型(Generics),这是 Go 语言的一项巨大更新。通过泛型,Go 开发者可以编写更通用、更可复用的代码,而不必要手动编写多个范例的重复代码。
为了支持这一点,将编写一个函数,在添加到其寻常函数参数中。这些范例参数使 function generic,使其可以或许处置惩罚差异范例的参数。将使用范例参数宁静常函数参数调用函数。
  1. package main
  2. import "fmt"
  3. // 定义一个泛型函数,接受一个类型参数 T
  4. func Print[T any](value T) {
  5.     fmt.Println(value)
  6. }
  7. func main() {
  8.     Print(123)    // 输出: 123
  9.     Print("hello") // 输出: hello
  10. }
复制代码
T 是范例参数,any 是 Go 1.18 中的范例束缚,表现可以是任何范例。
在函数 Print[T any](value T) 中,T 是范例参数,表现 value 参数的范例。
可以为范例参数指定束缚,使其只能是某些特定的范例。比方,限定范例参数只能是整数范例:
  1. package main
  2. import "fmt"
  3. // 定义一个泛型函数,限制 T 类型为整型(int, int32, int64)
  4. func Sum[T int | int32 | int64](a, b T) T {
  5.     return a + b
  6. }
  7. func main() {
  8.     fmt.Println(Sum(1, 2))     // 输出: 3
  9.     fmt.Println(Sum(int32(3), int32(4))) // 输出: 7
  10. }
复制代码
泛型范例

泛型布局体

泛型不但可以用于函数,也可以用于布局体和接口。
  1. package main
  2. import "fmt"
  3. // 定义一个泛型结构体,类型参数 T
  4. type Pair[T any] struct {
  5.     First  T
  6.     Second T
  7. }
  8. func main() {
  9.     // 使用泛型结构体,传入 int 类型
  10.     pair1 := Pair[int]{First: 1, Second: 2}
  11.     fmt.Println(pair1) // 输出: {1 2}
  12.     // 使用泛型结构体,传入 string 类型
  13.     pair2 := Pair[string]{First: "hello", Second: "world"}
  14.     fmt.Println(pair2) // 输出: {hello world}
  15. }
复制代码
在这个例子中,Pair[T any] 是一个泛型布局体,T 代表布局体字段的范例。
泛型接口

  1. package main
  2. import "fmt"
  3. // 定义一个泛型接口,支持多种数值类型(int, float64)
  4. type Adder[T int | float64] interface {
  5.     Add(a, b T) T
  6. }
  7. // 泛型类型:支持任意数值类型
  8. type NumberAdder[T int | float64] struct{}
  9. func (na NumberAdder[T]) Add(a, b T) T {
  10.     return a + b
  11. }
  12. func main() {
  13.     // 使用 NumberAdder 支持 int 类型
  14.     intAdder := NumberAdder[int]{}
  15.     fmt.Println(intAdder.Add(3, 4)) // 输出: 7
  16.     // 使用 NumberAdder 支持 float64 类型
  17.     floatAdder := NumberAdder[float64]{}
  18.     fmt.Println(floatAdder.Add(3.0, 4.0)) // 输出: 7.0
  19. }
复制代码
在这里,Adder[T any] 是一个泛型接口,NumberAdder 实现了这个接口。
泛型束缚

泛型支持范例束缚,用于指定范例参数的合法范例范围。范例束缚通过 interface{} 或更具体的接口来实现。
Go 1.18 提供了一些内置的范例束缚,如 any(表现任何范例)和 comparable(表现可以举行比力的范例)。
  1. package main
  2. import "fmt"
  3. // 定义一个泛型函数,约束 T 为可比较类型
  4. func Compare[T comparable](a, b T) bool {
  5.     return a == b
  6. }
  7. func main() {
  8.     fmt.Println(Compare(1, 1))       // 输出: true
  9.     fmt.Println(Compare("a", "b"))   // 输出: false
  10.     // fmt.Println(Compare([]int{1}, []int{1}))  // 编译错误: slices are not comparable
  11. }
复制代码
在这个例子中,T comparable 限定了范例参数 T 必须是可以举行比力的范例。
泛型切片和映射

泛型在 Go 中也支持切片(slices)和映射(maps)等常见数据布局。
2.5.1 泛型切片

  1. package main
  2. import "fmt"
  3. func PrintSlice[T any](s []T) {
  4.     for _, v := range s {
  5.         fmt.Println(v)
  6.     }
  7. }
  8. func main() {
  9.     PrintSlice([]int{1, 2, 3})      // 输出: 1 2 3
  10.     PrintSlice([]string{"a", "b"})  // 输出: a b
  11. }
复制代码
泛型映射

  1. package main
  2. import "fmt"
  3. // 泛型映射,支持任意类型作为键和值
  4. func PrintMap[K comparable, V any](m map[K]V) {
  5.     for k, v := range m {
  6.         fmt.Println(k, v)
  7.     }
  8. }
  9. func main() {
  10.     m1 := map[string]int{"a": 1, "b": 2}
  11.     PrintMap(m1) // 输出: a 1  b 2
  12.     m2 := map[int]string{1: "one", 2: "two"}
  13.     PrintMap(m2) // 输出: 1 one  2 two
  14. }
复制代码
泛型实际应用

Go 中可以用于实现许多常见的算法和数据布局,如链表、栈、队列等。
  1. package main
  2. import "fmt"
  3. type Stack[T any] struct {
  4.     items []T
  5. }
  6. func (s *Stack[T]) Push(item T) {
  7.     s.items = append(s.items, item)
  8. }
  9. func (s *Stack[T]) Pop() T {
  10.     if len(s.items) == 0 {
  11.         panic("stack is empty")
  12.     }
  13.     item := s.items[len(s.items)-1]
  14.     s.items = s.items[:len(s.items)-1]
  15.     return item
  16. }
  17. func main() {
  18.     stack := &Stack[int]{}
  19.     stack.Push(1)
  20.     stack.Push(2)
  21.     fmt.Println(stack.Pop()) // 输出: 2
  22.     fmt.Println(stack.Pop()) // 输出: 1
  23. }
复制代码
workspace

go 1.18 引入了 workspace 功能,旨在简化多个模块(module)的管理和开发。workspace 答应你在一个工作空间中同时管理多个 Go 模块,这对于开发大型项目或依赖多个模块时非常有效。
概念

工作空间(workspace)是 Go 1.18 引入的一个新概念,它可以包罗多个 Go 模块。如许,你可以在同一个目次下处置惩罚多个模块(module),而无需通过 $GOPATH 来管理它们。
在 Go 1.18 版本中,你必要通过创建一个名为 go.work 的文件来启用工作空间。这个文件界说了工作空间内的 Go 模块及其路径。
workspace案例

工作空间文件 go.work 用来指定工作空间中包罗的模块。比方,如果你有多个模块在差异的目次中,可以在 go.work 中列出它们的路径。
假设你有两个模块 moduleA 和 moduleB,它们位于差异的文件夹中,你可以在根目次下创建一个 go.work 文件,将这两个模块包罗在内:
  1. go 1.23
  2. use (
  3.     ./moduleA
  4.     ./moduleB
  5. )
复制代码
这将指示 Go 使用 moduleA 和 moduleB 两个模块举行构建。
一旦设置了 go.work 文件,Go 下令会自动辨认工作空间并处置惩罚模块之间的依赖关系。比方,你可以使用 go build 或 go run 下令时,Go 会自动处置惩罚跨模块依赖。
比方,运行 go run . 时,Go 会处置惩罚 go.work 文件而且可以跨模块找到所需的依赖,而不必要单独实行每个模块的下令。
假设你有以下文件布局:
  1. /workspace
  2.     go.work
  3.     /moduleA
  4.         go.mod
  5.         main.go
  6.     /moduleB
  7.         go.mod
  8.         main.go
复制代码
moduleA/go.mod 文件:
  1. module moduleA
  2. go 1.23
复制代码
moduleB/go.mod 文件:
  1. module moduleB
  2. go 1.23
复制代码
moduleB/utilsB 文件:
  1. package moduleB
  2. import "fmt"
  3. func HelloB() {
  4.     fmt.Println("HelloB")
  5. }
复制代码
moduleA/main 文件:
  1. package main
  2. import "moduleB"
  3. func main() {
  4.     moduleB.HelloB()
  5. }
复制代码
接下来处置惩罚依赖,先创建go.work
  1. go work init ./moduleA
复制代码
再将moduleB加入
  1. go work use ./moduleB
复制代码
Go 会处置惩罚 go.work 中列出的模块路径,并根据需求分析、构建或运行全部模块。
在根目次 /workspace 下运行 go run 或其他 Go 下令。
  1. go run .\moduleA\
复制代码
Go 1.18 引入的工作空间功能使得多模块管理变得更为简单,实用于大型项目或必要调和多个模块的开发场景。通过创建 go.work 文件,可以让 Go 项目更好地管理跨模块依赖,同时进步开发服从和可维护性。
暗昧测试

Go 1.18 引入了 暗昧测试(Fuzzing)功能,它是一种自动化的测试技能,用于发现步调中的埋伏缺陷和安全毛病。暗昧测试通过自动天生大量的随机输入数据来测试步调的坚固性,资助开发者发今世码中的非常活动、瓦解、内存走漏等标题。
在 Go 1.18 中,暗昧测试被集成到了标准库中,你可以直接在 Go 中举行暗昧测试,而不必要额外的工具或库。
概念

暗昧测试是一种通过向步调输入大量随机、偶尔义的(大概故意计划的非常的)数据来检测步调埋伏毛病的技能。它紧张用于:


  • 发今世码中的边界情况、瓦解、未处置惩罚的非常等。
  • 测试步调对非常输入的处置惩罚本领,加强步调的坚固性。
  • 通过大量随机的输入数据测试算法、输入验证、错误处置惩罚等方面。
一样寻常测试

创建add.go
  1. package fuzzDemo
  2. func Add(a, b int) int {
  3.         return a + b
  4. }
复制代码
创建测试用例
  1. package fuzzDemo
  2. import (
  3.         "testing"
  4. )
  5. func TestAdd(f *testing.T) {
  6.         // 预设一些初始的模糊测试用例
  7.         testcases := []struct {
  8.                 a, b int
  9.                 want int
  10.         }{
  11.                 {1, 2, 3},
  12.                 {2, 3, 5},
  13.                 {3, 4, 7},
  14.         }
  15.         for _, tc := range testcases {
  16.                 if got := Add(tc.a, tc.b); got != tc.want {
  17.                         f.Errorf("Add(%d, %d) = %d; want %d", tc.a, tc.b, got, tc.want)
  18.                 }
  19.         }
  20. }
复制代码
在根目次运行
  1. go test
复制代码
输出
  1. PS F:\Code\Golang\TuLing\workPath\fuzzDemo> go test
  2. PASSok      fuzzDemo        0.012s
复制代码
怎样使用暗昧测试

Go 1.18 引入了对暗昧测试的内置支持,答应通过 testing 包来编写暗昧测试函数。暗昧测试的函数以 Fuzz 开头,使用 testing 包中的 Fuzz 范例来界说。
根本步调:

  • 创建暗昧测试函数:在测试代码中界说一个暗昧测试函数,函数的参数是一个范例为 testing.F 的对象,代表暗昧测试框架。
  • 编写测试逻辑:在暗昧测试函数内部,编写逻辑来处置惩罚暗昧输入并验证输出。
  • 运行测试:使用 go test
    下令来运行暗昧测试。
示例:
假设我们有一个函数 Add,它简单地将两个整数相加。
  1. package main
  2. import "fmt"
  3. func Add(a, b int) int {
  4.     return a + b
  5. }
复制代码
我们可以编写一个暗昧测试函数来测试 Add 函数。
界说暗昧测试函数:
  1. package main
  2. import (
  3.     "testing"
  4. )
  5. func FuzzAdd(f *testing.F) {
  6.         testcases := []struct {
  7.                 a, b int
  8.         }{
  9.                 {1, 2},
  10.                 {2, 3},
  11.                 {3, 4},
  12.         }
  13.         for _, tc := range testcases {
  14.                 f.Add(tc.a, tc.b)
  15.         }
  16.         f.Fuzz(func(t *testing.T, a, b int) {
  17.                 result := Add(a, b)
  18.                 if result != a+b {
  19.                         t.Errorf("Add(%d, %d) = %d; want %d", a, b, result, a+b)
  20.                 }
  21.         })
  22. }
复制代码
在这个示例中,我们使用 f.Add 来添加初始的输入值,之后通过 f.Fuzz 来天生随机的输入对,并使用 t.Errorf 来陈诉结果是否符合预期。
运行暗昧测试:
你可以通过 go test
来运行暗昧测试。
  1. go test
  2. -fuzz=FuzzAdd
复制代码
此下令会开始实行暗昧测试并天生随机的测试输入来实行 Add 函数。Go 会根据天生的输入验证函数活动是否精确。
暗昧测试参数


  • f.Add: 用于为暗昧测试提供初始输入。这些输入会在测试过程中作为种子,基于这些种子数据,Go 会天生更多的随机数据。
  • f.Fuzz: 用于界说实际的暗昧测试逻辑。这里你可以编写逻辑来处置惩罚暗昧输入并举行断言。
输出
  1. PS F:\Code\Golang\TuLing\workPath\fuzzDemo> go test
  2. -fuzz=FuzzAddfuzz: elapsed: 0s, gathering baseline coverage: 0/3 completedfuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 12 workersfuzz: elapsed: 3s, execs: 591773 (197041/sec), new interesting: 0 (total: 3)fuzz: elapsed: 6s, execs: 1188444 (199093/sec), new interesting: 0 (total: 3)fuzz: elapsed: 8s, execs: 1615691 (198583/sec), new interesting: 0 (total: 3)PASSok      fuzzDemo        8.178s
复制代码
暗昧测试常见用法

暗昧测试特殊实用于以下几种场景:


  • 函数边界情况测试:比方,测试字符串处置惩罚函数是否能精确处置惩罚空字符串、特殊字符、非常长的字符串等。
  • 错误处置惩罚验证:验证步调在汲取到不合法输入时是否会瓦解或产生错误。
  • 性能和压力测试:通过给定极限的输入来查抄步调在边界条件下的表现。
自界说输入天生

Go 的暗昧测试功能答应开发者对天生的输入举行更细粒度的控制。比方,你可以为暗昧测试提供自界说的输入天生器,来专门生成特定范例的测试数据。
示例:自界说输入天生
  1. func FuzzCustomInput(f *testing.F) {
  2.     f.Add([]byte("initial input"))
  3.     f.Fuzz(func(t *testing.T, data []byte) {
  4.         if len(data) > 100 {
  5.             t.Errorf("input data too long: %v", data)
  6.         }
  7.     })
  8. }
复制代码
运行暗昧测试的其他选项


  • -fuzztime: 设置运行暗昧测试的时间限定。默认情况下,暗昧测试会不绝运行直得手动制止,可以通过 -fuzztime 参数来控制运行时长。
    1. go test
    2. -fuzz=FuzzAdd -fuzztime=30s
    复制代码
    这将限定暗昧测试的运行时间为 30 秒。
  • -fuzzminimize: 实行最小化产生错误的测试用例,如许可以资助开发者快速定位标题。
    1. go test
    2. -fuzz=FuzzAdd -fuzzminimize
    复制代码

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表