Go语言sqlx简单介绍

sql操作的系统函数 如果你觉得用起来比较啰嗦的话 你还可以选择开源库,这里简单介绍下sqlx这个开源库

sqlx 的连接数据库

// 定义一x全局对象 这是并发安全的对象 注意这个是sqlx了 不是sql
var db *sqlx.DB

func initMysql() (err error) {
   dsn := "root:1234qwer@tcp(127.0.0.1:3306)/go_test"
   // 原生的 是open 这里直接用connect 就行了 里面包含了ping的操作
   db, err = sqlx.Connect("mysql", dsn)
   if err != nil {
      panic(err)
   }

   db.SetConnMaxLifetime(time.Second * 10)
   // 设置最大的连接数 默认是无限制 如果超出限制了 就会排队等待
   db.SetMaxOpenConns(200)
   // 设置最大的空闲连接数 默认是无限制 业务量小的时候 可以把多余的连接释放掉,只保留一定数量的连接数
   db.SetMaxIdleConns(10)
   return nil
}
复制代码

sqlx的查询

// 这里注意首字母要大写了
// 因为sqlx是用的反射来对结构体进行赋值,所以是跨包的,
// 那自然这个结构体的 field 要首字母大写了
type user struct {
   Id   int    `db:"id"`
   Age  int    `db:"age"`
   Name string `db:"name"`
}

func queryRow() {
   sqlStr := "select id,age,name from user where id=?"
   var u user
   //queryRow 之后 一定要执行scan  否则 持有的数据库连接 不会释放
   err := db.Get(&u, sqlStr, 1)
   if err != nil {
      fmt.Println(err)
   }
   fmt.Println("result:", u)
}
复制代码

可以看出来,这查询就比之前的原生的查询要简单很多,使用很方便。
其实这里的原理和java中的json序列化很像,都是利用反射。

只不过在java中 我们一般是利用注解 来标识 field和 json中key的关系
而 这里是用的`` 符号 仅此而已

查询多行也是一样的

func queryRow() {
   sqlStr := "select id,age,name from user where id=?"
   var u user
   //queryRow 之后 一定要执行scan  否则 持有的数据库连接 不会释放
   err := db.Get(&u, sqlStr, 1)
   if err != nil {
      fmt.Println(err)
   }
   fmt.Println("result:", u)
}
复制代码

也是能简化不少 我们的操作

crud 操作

sqlx的crud操作和sql里面的操作是一样的 这里不再重复演示

func insert() {
   sqlStr := "insert into user(name,age) values(?,?)"
   ret, err := db.Exec(sqlStr, "李彦宏", 99)
   if err != nil {
      fmt.Println(err)
      return
   }
   id, err2 := ret.LastInsertId()
   if err2 != nil {
      fmt.Println("get lastid error:", err2)
      return
   }
   fmt.Println("insert success,id: ", id)
}
复制代码

简化操作

在之前每次插入或者更新数据的时候 如果参数过多,那我们的占位符和真正的数据 很有可能就写错了,而且可读性不佳

sqlx提供了NamesExec操作 可以简化我们的操作 以kv的形式 来操作数据

func betterInsert() {
   sqlStr := `insert into user(name,age) values(:name,:age) `
   _, err := db.NamedExec(sqlStr, map[string]interface{}{
      "name": "大强子",
      "age":  17,
   })
   if err != nil {
      panic(err)
   }
   return
}
复制代码

同样的 查询语句也一样有类似的方法NamedXXX 有兴趣的可以自行查找sqlx 相关的文档。这里只是抛砖引玉一下

大家要谨记,这些api没必要一个个过,只需要记住一个类似的,然后其余的时候只要知道用的时候 去哪里查询文档 即可

事务的操作

sqlx的事物操作 与原生的 事务操作 有所不同,主要区别就是 sqlx 依赖defer 来做回滚和提交的操作

func transactionDemo() error {
   tx, err := db.Begin()
   if err != nil {
      fmt.Println("begin trans failed")
      return err
   }

   defer func() {
      // 如果这个函数有panic 则 回滚以后再panic
      if p := recover(); p != nil {
         tx.Rollback()
         panic(p)
      } else if err != nil {
         // 如果有错误 也回滚
         fmt.Println("rollback")
         tx.Rollback()
      } else {
         err = tx.Commit()
         fmt.Println("commit")
      }
   }()

   sqlstr1 := "update user set age=130 where id=?"
   _, err = tx.Exec(sqlstr1, 1)
   if err != nil {
      return err
   }

   sqlstr2 := "update user set age=130 where id=?"
   _, err = tx.Exec(sqlstr2, 2)
   if err != nil {
      return err
   }

   err = tx.Commit()
   if err != nil {
      return err
   }
   return nil

}
复制代码

我个人是比较喜欢这种defer的写法的 因为不容易出错 特别是当你的事务特别复杂的时候

sqlx的批量插入

有时候我们会经常碰到批量插入数据的场景,最简单的做法 当然是 写一个for循环 每次都插入一条

直到循环结束,但是这样会重复连接数据库 造成不必要的压力。

所以通常我们会一次性插入

sql语句 形如:

image.png

当然在程序里面我们要使用占位符来处理,那他的参数如下:

image.png

显然我们要 完成类似的操作 是要做一些 字符串拼接的操作的

这样写起来很麻烦


func insertUsers(users []user) error {
   // 存放 (?, ?) 的slice
   valueStrings := make([]string, 0, len(users))
   // 存放values的slice
   valueArgs := make([]interface{}, 0, len(users)*2)
   // 遍历users准备相关数据
   for _, u := range users {
      // 此处占位符要与插入值的个数对应
      valueStrings = append(valueStrings, "(?, ?)")
      valueArgs = append(valueArgs, u.Name)
      valueArgs = append(valueArgs, u.Age)
   }
   // strings.Join(valueStrings, ",")  ===》(?, ?),(?, ?),(?, ?)
   // 自行拼接要执行的具体语句 形如:INSERT INTO user (name, age) VALUES (?, ?),(?, ?),(?, ?)
   stmt := fmt.Sprintf("INSERT INTO user (name, age) VALUES %s",
      strings.Join(valueStrings, ","))
   fmt.Println(valueArgs)
   _, err := db.Exec(stmt, valueArgs...)
   return err
}
复制代码
users := []user{
   {
      Name: "kk1",
      Age:  11,
   },
   {
      Name: "kk2",
      Age:  12,
   },
   {
      Name: "kk1",
      Age:  13,
   },
}
insertUsers(users)
复制代码

简化版的写法

func insertMoreUsers(users []user) error {
   _, err := db.NamedExec("INSERT INTO user (name, age) VALUES (:name, :age)", users)
   return err
}
复制代码

in函数简单介绍

func searchByIDs(ids []int)(users []user, err error){
   //把id 填到sql语句中
   query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?)", ids)
   if err != nil {
      return
   }
   // sqlx.In 返回带 `?` 的查询语句, 记得要用rebind 重新绑定下
   query = db.Rebind(query)

   err = db.Select(&users, query, args...)
   return
}
复制代码