sqlx库——在go中写sql

sqlx库——在go中写sql

sqlx可以认为是Go语言内置的database/sql的超集,基于内置的连接数据库的库,sqlx做了非常好的拓展,使用起来更方便快捷,对于有sql基础的,使用起来会比gorm更顺手

下载sqlx依赖

在goland终端中输入下面代码,获取sqlx依赖

go get github.com/jmoiron/sqlx

连接数据库

注:本文使用的是MySQL数据库,由于sqlx不支持创建操作,数据库需要提前在database中创建

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

一定要记得导入MySQL数据库的驱动,使用匿名导入,因为我们没使用到库中的方法,只是利用MySQL驱动

var db *sqlx.DB

func initDB() (err error) {
    // 用户名,密码,端口号以及数据库名称,根据自己需要去修改
    // charset指定编码格式
    // parseTime可以自动解析数据库中的时间数据,方便导入到go语言中
	dsn := "用户名:密码.@tcp(127.0.0.1:3306)/数据库名称?charset=utf8mb4&parseTime=True&loc=Local"
    // 也可以使用MustConnect连接不成功就panic
	db, err = sqlx.Connect("mysql", dsn)
	if err != nil {
		fmt.Printf("connect DB failed, err:%v\n", err)
		return err
	}
	// 连接池最大容量设置
	db.SetMaxOpenConns(20) // 与数据库建立连接的最大数目
	db.SetMaxIdleConns(10) // 连接池中的最大闲置连接数
	return err
}

常用操作

查询
建结构体/建表

首先需要先创建一个结构体,结构体的内容要与数据库表中的字段对应起来,数据类型需要保持一致

// user 结构体对应数据库的表
type user struct {
	Id   uint 	`db:"id"`	// 利用结构体标签,将成员名称与数据库字段名称一一对应
	Name string	`db:"name"`
	Age  uint	`db:"age"`
}

**注意:**结构体内的成员名称,首字母必须大写,保证能通过反射取到该字段

查询单条语句

利用db.Get(&结构体, 查询语句, 1)

// QueryRowDemo 查询单条语句
func QueryRowDemo() {
	sqlStr := "select id,name,age from user where id = ?"
	var u user
	err := db.Get(&u, sqlStr, 1) // 查询一条,这里利用反射,直接把查询出来的数据赋值到结构体的字段中
	if err != nil {
		fmt.Println("db get failed,err:", err)
		return
	}
	fmt.Printf("id=%d,name=%s,age=%d\n", u.Id, u.Name, u.Age)
}
查询多条语句

利用db.Select(&结构体, 查询语句, 0)

// 查询多条数据示例
func queryMultiRowDemo() {
	sqlStr := "select id, name, age from user where id > ?"
	var users []user	// 结构体切片,多个数据
	err := db.Select(&users, sqlStr, 0)
	if err != nil {
		fmt.Printf("query failed, err:%v\n", err)
		return
	}
	fmt.Printf("users:%#v\n", users)
}

注意:方法内的第一个参数是结构体的指针,确保能对其进行更改,而不是单纯的值拷贝

插入、更新、删除(基础版exec)

利用sqlx中的exec方法,sqlx中的exec方法与原生内置sql的exec方法基本一致

利用Exec方法可以执行插入、更新和删除操作,主要不同点就在于操作所对应的sql语句

// 插入数据
func insertRowDemo() {
	sqlStr := "insert into user(name, age) values (?,?)"
    // 插入一个名字为“lin”,年龄为20的数据
	ret, err := db.Exec(sqlStr, "lin", 20)
	if err != nil {
		fmt.Printf("insert failed, err:%v\n", err)
		return
	}
	theID, err := ret.LastInsertId() // 获取最新插入数据的id
	if err != nil {
		fmt.Printf("get lastinsert ID failed, err:%v\n", err)
		return
	}
	fmt.Printf("insert success, the id is %d.\n", theID)
}

// 更新数据
func updateRowDemo() {
	sqlStr := "update user set age=? where id = ?"
    // 将id = 6的年龄更新为18
	ret, err := db.Exec(sqlStr, 18, 6)
	if err != nil {
		fmt.Printf("update failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("update success, affected rows:%d\n", n)
}

// 删除数据
func deleteRowDemo() {
	sqlStr := "delete from user where id = ?"
    // 删除id = 6的数据
	ret, err := db.Exec(sqlStr, 6)
	if err != nil {
		fmt.Printf("delete failed, err:%v\n", err)
		return
	}
	n, err := ret.RowsAffected() // 操作影响的行数
	if err != nil {
		fmt.Printf("get RowsAffected failed, err:%v\n", err)
		return
	}
	fmt.Printf("delete success, affected rows:%d\n", n)
}
NamedExec(命名版exec)

由于普通的exec操作,需要在sql中使用“?”作为占位符,当变量很多时,容易出现顺序错误变量遗漏等问题

使用NamedExec可以用来绑定sql语句中的**“?”结构体或者map中的同名字段**

使用起来更为变量,且一 一对应

使用**“:变量名”的形式取代“?”,可以更清晰呈现需要操作的字段,保证一比一对应**关系

func insertUserDemo()(err error){
	sqlStr := "INSERT INTO user (name,age) VALUES (:name,:age)"
	_, err = db.NamedExec(sqlStr,
		map[string]interface{}{
			"name": "Tai",
			"age": 20,
		})
	return
}
NamedQuery(命名版查询)

与NamedExec一致,只不过这里换成了查询操作

使用map做命名查询
sqlStr := "SELECT * FROM user WHERE name=:name"
rows, err := db.NamedQuery(sqlStr, map[string]interface{}{"name": "lin"})
if err != nil {
	fmt.Printf("db.NamedQuery failed, err:%v\n", err)
	return
}
defer rows.Close()
使用结构体做命名查询
u := user{
	Name: "lin",
}
rows, err := db.NamedQuery(sqlStr, u)
if err != nil {
	fmt.Printf("db.NamedQuery failed, err:%v\n", err)
	return
}
defer rows.Close()
遍历查询结果
for rows.Next(){
	var u user
	err := rows.StructScan(&u)	// 不能直接使用Scan去映射扫描,
    							// 因为我们传入的只是user中的部分字段,并不是所有字段
	if err != nil {
		fmt.Printf("scan failed, err:%v\n", err)
		continue
	}
	fmt.Printf("user:%#v\n", u)
}
事务操作

利用sqlx中的db.Beginx()tx.Exec()方法

例子

func transactionDemo2()(err error) {
	tx, err := db.Beginx() // 开启事务
	if err != nil {
		fmt.Printf("begin trans failed, err:%v\n", err)
		return err
	}
    
    // 利用defer来进行最终事务的提交和回滚判断
	defer func() {
        // 利用recover捕获当前函数可能出现的panic,然后进行恢复
		if p := recover(); p != nil {
			tx.Rollback()
			panic(p) // 先进行事务回滚,再panic
		} else if err != nil {
			fmt.Println("rollback")
			tx.Rollback() // 如果当前函数出现错误,也会进行事务回滚操作
		} else {
			err = tx.Commit() // 如果没panic也没有错误,事务提交
			fmt.Println("commit")
		}
	}()

    // 更改两个人的年龄,必须保证两个人的年龄都更改成功,才提交事务
	sqlStr1 := "Update user set age=20 where id=?"
	rs, err := tx.Exec(sqlStr1, 1)
	if err!= nil{
		return err
	}
	n, err := rs.RowsAffected()	// 受到影响的行数
	if err != nil {
		return err
	}
	if n != 1 {
		return errors.New("exec sqlStr1 failed")
	}
    
	sqlStr2 := "Update user set age=50 where i=?"
	rs, err = tx.Exec(sqlStr2, 5)
	if err!=nil{
		return err
	}
	n, err = rs.RowsAffected()	// 受到影响的行数
	if err != nil {
		return err
	}
	if n != 1 {
		return errors.New("exec sqlStr1 failed")
	}
	return err	//返回的这些错误,会在函数即将结束时,在defer中去进行判断
}

强大的sqlx.In

使用 sqlx.In可以实现批量操作数据

**前提:**需要我们的结构体实现一个 driver.Valuer接口

func (u user) Value() (driver.Value, error) {
	return []interface{}{u.Name, u.Age}, nil	// 返回名称和年龄以及nil
}
使用sqlx.In实现批量插入
// BatchInsertUsers 使用sqlx.In帮我们拼接语句和参数, 注意传入的参数是[]interface{}
func BatchInsertUsers(users []interface{}) error {
	query, args, _ := sqlx.In(
		"INSERT INTO user (name, age) VALUES (?), (?), (?)",
		users..., // 如果arg实现了 driver.Valuer, sqlx.In 会通过调用 Value()来展开它
	)
	fmt.Println(query) // 查看生成的querystring
	fmt.Println(args)  // 查看生成的args
	_, err := db.Exec(query, args...)
	return err
}
使用NamedExec实现批量插入

这个方法极其方便,推荐使用

// BatchInsertUsers2 使用NamedExec实现批量插入
func BatchInsertUsers2(users []*user) error {
	_, err := db.NamedExec("INSERT INTO user (name, age) VALUES (:name, :age)", users)
	return err
}

以上两个插入方法示例

u1 := User{Name: "a", Age: 10}
u2 := User{Name: "b", Age: 20}
u3 := User{Name: "c", Age: 30}

// 方法1
users1 := []interface{}{u1, u2, u3}
err = BatchInsertUsers1(users1)
if err != nil {
	fmt.Printf("BatchInsertUsers1 failed, err:%v\n", err)
}

// 方法2
users2 := []*user{&u1, &u2, &u3}
err = BatchInsertUsers2(users2)
if err != nil {
	fmt.Printf("BatchInsertUsers2 failed, err:%v\n", err)
}
sqlx.In查询

一次性给定多个id,将这些id对应的数据都查出来

// QueryByIDs 根据给定ID查询
// 传入一个待查找的id切片,返回user切片代表多个数据和错误信息err
func QueryByIDs(ids []int)(users []user, err error){
	// 动态填充id
    // 返回query查询语句和args参数
	query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?)", ids)
	if err != nil {
		return
	}    
	// sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定。
	query = db.Rebind(query)

	err = db.Select(&users, query, args...)
	return
}
按照指定顺序的sqlx.In查询

sqlx.In默认是按照升序进行查询返回结果,如果需要按照指定顺序进行查询返回结果,可以使用FIND_IN_SET函数

这是利用MySQL中的ORDER BY FIND_IN_SET函数去进行排序

第一个参数是 按什么字段排序

第二个参数是 按什么顺序排序

// QueryAndOrderByIDs 按照指定id查询并维护顺序
func QueryAndOrderByIDs(ids []int)(users []user, err error){
	// 动态填充id
	strIDs := make([]string, 0, len(ids))
	for _, id := range ids {
		strIDs = append(strIDs, fmt.Sprintf("%d", id))
	}
	query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?) ORDER BY FIND_IN_SET(id, ?)", ids, strings.Join(strIDs, ","))
	if err != nil {
		return
	}

	// sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它
	query = db.Rebind(query)

	err = db.Select(&users, query, args...)
	return
}

本文介绍了sqlx库的基本使用方法,希望可以对大家有所帮助

  • 21
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值