首页 > 数据库 >sqlx库——在go中写sql

sqlx库——在go中写sql

时间:2024-07-10 13:27:10浏览次数:13  
标签:sqlx return err fmt db user go 中写

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库的基本使用方法,希望可以对大家有所帮助

标签:sqlx,return,err,fmt,db,user,go,中写
From: https://blog.csdn.net/qq_73933965/article/details/140321369

相关文章

  • go并发模式 o-channel
    packagemainimport("fmt""time")funcmain(){varorfunc(channels...<-chaninterface{})<-chaninterface{}or=func(channels...<-chaninterface{})<-chaninterface{}{switchlen(channels)......
  • mongodb数据库恢复
    一、从备份中恢复使用mongodump和mongorestoremongodump:MongoDB官方提供的备份工具,可以将MongoDB数据库中的数据导出为BSON格式的文件。通过该工具,可以备份整个数据库、指定的集合或查询的数据。mongorestore:MongoDB官方提供的恢复工具,用于将mongodump导出的BSON文件恢复为Mong......
  • go并发模式 or-do-channel + bridge
    packagemainimport("context""fmt")//orDonefuncorDone(ctxcontext.Context,value<-chanint)<-chanint{ordoneStream:=make(chanint)gofunc(){deferclose(ordoneStream)for{......
  • go并发模式 tee-channel
    packagemainimport("context""fmt""time")functeeChannel(ctxcontext.Context,value<-chanint)(<-chanint,<-chanint){ch1:=make(chanint)ch2:=make(chanint)gofunc(){......
  • go并发模式 pipeline
    packagemainimport("fmt""math/rand")funcmain(){pFn:=func(done<-chaninterface{},fnfunc()int)<-chanint{valueStream:=make(chanint)gofunc(){deferclose(valueStream)......
  • go并发模式 错误处理
    packagemainimport("fmt""net/http")typeResultsstruct{ErrorerrorResponse*http.Response}funcmain(){checkStatus:=func(done<-chaninterface{},urls...string)<-chanResults{re......
  • go并发模式 扇入扇出
    扇入扇出寻找素数:packagemainimport("fmt""math/rand""runtime""sync""time")varrepeatFn=func(done<-chaninterface{},fnfunc()interface{})<-chaninterface{}{valueSt......
  • AI绘画comfyui工作流,商业海报设计、Logo设计,一个comfyui工作流就能搞定!
    前言创新设计工作流:轻松打造LOGO和海报本文涉及的工作流和插件,需要的朋友请扫描免费获取哦—HAPPYNEWYEAR—大家好!今天我要分享的是一个高效且创新的设计工作流,这一工具由国外的网友无私分享,适用于LOGO设计和海报创作。这不仅是对开源精神的致敬,也为我们的设计工......
  • 最近很火的Vue Vine是如何实现一个文件中写多个组件
    前言在今年的VueConf2024大会上,沈青川大佬(维护Vue/Vite中文文档)在会上介绍了他的新项目VueVine。VueVine提供了全新Vue组件书写方式,主要的卖点是可以在一个文件里面写多个vue组件。相信你最近应该看到了不少介绍VueVine的文章,这篇文章我们另辟蹊径来讲讲VueVine是如何实现......
  • 21、Django-缓存(强缓存和协商缓存)-@cache-page()装饰器
    定义:缓存是一类可以更快的读取数据的介质的统称、也指其它可以加快数据读取的存储方式、一般用来存储临时数据、常用介质的是读取速度很快的内存意义:视图渲染有一定的成本、数据库的频繁查询过高、所以对于低频变动的页面可以考虑使用缓存技术、减少实际渲染的次数、用户拿到相......