当前位置: 首页 > 科技观察

Golang配合PostgreSQL“增删改查+快速扫描字段无需写结构”快速上手

时间:2023-03-12 02:28:41 科技观察

PostgreSQL(也称为postgres)是一个功能强大的开源对象关系数据库系统(ORDBMS)。经过30多年的打磨,具有高可靠性、坚固耐用、高性能等优点。详情请见官网。本文主要使用github.com/lib/pq包,它是为Go语言定制的database/sql包(sql包是go定义的一套围绕SQL或类SQL数据库的通用接口,需要与特定的数据库驱动一起使用),由Go开发的纯PostgreSQL驱动。什么是不写结构?在本文中,可以理解为查询数据库配置直接返回键值对类型,或者查询数据返回多行数据。列值不需要声明字段,分别使用Map和Map数组来接收单行和多行数据。详细原理可以参考上一篇博文:Golang连接MySQL执行查询和解析——告别结构体接下来我们来实现PostgreSQLGolang版CRUD和单行、多行快速扫描分析行字段!驱动安装执行以下命令安装postgresql驱动gogetgithub.com/lib/pqpqpackagesupportsfunctionsSSL作为驱动,结合database/sql处理连接相关操作scantimetime.时间类型,如timestamp[tz],time[tz],dateScanbinaryblobsobjects(Blob是内存中的数据缓冲区,用于匹配strign-typeID和byteslice),如:byteaPostgreSQL的hstore数据类型支持COPYFROMpq.ParseURL方法使用将urls转换为sql.Open连接字符串支持很多libpq库兼容环境变量支持Unixsocket支持Notifications,例如:LISTEN/NOTIFY支持pgpassGSS(Kerberos)认证连接字符串参数pq包类似于libpq(libpq是一个low-用C编写的级别接口,它为C++、Perl、Python、Tcl和ECPG等其他高级语言提供低级PostgreSQL支持)。建立连接时需要提供连接参数。一些支持libpq的参数也支持pq,另外,pq允许在连接字符串中指定运行时参数(例如:search_pathorwork_mem),libpq不能在连接字符串中指定运行时参数,只能在option参数中指定。pq包兼容libpq包,支持以下连接参数*dbname-要连接的数据库名*user-要使用的用户名*password-用户的密码*host-postgresql主机连接,unix域名socket以/开头,默认是localhost*port-postgresql绑定端口(默认5432)*sslmode-是否使用SSL(默认是开启(require),libpq包默认不开启SSL)*fallback_application_name-当它失败时,您可以提供一个应用程序名称来跟踪。*connect_timeout-等待连接的最大秒数,0或不指定,表示不确定等待时间*sslcert-certificate文件位置,文件必须包含PEM编码的数据*sslkey-key文件位置,文件必须包含PEM-encodeddata*sslrootcert-根证书文件位置,文件中必须包含PEM-encoded数据sslmodesupport以下模式*disable-禁用SSL*require-alwaysuseSSL(skipverification)*verify-ca-alwaysuseSSL(verify服务器提供的证书由受信任的CA签名)*verify-full-always使用SSL(验证服务器提供的证书是否由受信任的CA签名,并验证服务器主机名是否与证书中的主机名匹配).更多连接字符串参数请参考官方文档。对于包含空格的参数,需要使用单引号,如:"user=pqgotestpassword='withspaces'"使用反斜杠转义,如:"user=space\manpassword='it\'svalid'"注意:如果如果要设置client_encoding连接参数(设置连接编码),必须设置为“UTF8”才能匹配Postgres,设置为其他值会报错。除了上述参数外,还可以通过后台在连接字符串中设置运行时参数。详细运行时参数请参考runtime-config支持libpq和pq包的大部分环境变量,详细环境变量请参考libpq-envars。如果没有设置环境变量并且连接字符串不提供参数,程序会panic并退出,字符串参数的优先级高于环境变量。完整的“增删改查”示例代码包mainimport("database/sql""encoding/json""fmt"_"github.com/lib/pq""log")const(//Initializeconnectionconstants.HOST="172.16.xx.xx"PORT=31976DATABASE="postgres"USER="postgres"PASSWORD="xxx")funccheckError(errerror){iferr!=nil{panic(err)}}typeDbstruct{db*sql.DB}//建表func(this*Db)CreateTable(){//以水果盘点表inventory为例//Dropprevioustableofsamenameifoneexists.如果list表之前存在,删除表_,err:=this.db.Exec("DROPTABLEIFEXISTSinventory;")checkError(err)fmt.Println("Finisheddroppingtable(ifexisted)")//Createtable。创建表,指定id、name、quantity(数量)字段,其中id为主键_,err=this.db.Exec("CREATETABLEinventory(idserialPRIMARYKEY,nameVARCHAR(50),quantityINTEGER);")checkError(err)fmt。println("Finishedcreatingtable")}//删除表func(this*Db)DropTable(){//以水果库存表inventory为例//删除previoustableofsamenameifoneexists.如果inventory表之前存在,删除table_,err:=this.db.Exec("DROPTABLEIFEXISTSinventory;")checkError(err)fmt.Println("Finisheddroppingtable(ifexisted)")}//添加数据func(this*Db)Insert(){//插入一些数据到表中。插入3条水果数据sql_statement:="INSERTINTOinventory(name,quantity)VALUES($1,$2);"_,err:=this.db.Exec(sql_statement,"banana",150)checkError(err)_,err=this.db.Exec(sql_statement,"orange",154)checkError(err)_,err=this.db.Exec(sql_statement,"apple",100)checkError(err)fmt.Println("Inserted3rowsofdata")}//读数据/查数据func(this*Db)Read(){//读数据//Readrowsfromtable.varidintvarnamestringvarquantityintsql_statement:="SELECT*frominventory;"rows,err:=this.db.Query(sql_statement)checkError(err)deferrows.Close()forrows.Next(){switcherr:=rows.Scan(&id,&name,&quantity);err{casesql.ErrNoRows:fmt.Println("Norowswerreturned")casenil:fmt.Printf("Datarow=(%d,%s,%d)\n",id,name,quantity)default:checkError(err)}}}//更新数据func(this*Db)Update(){//Modifysomedataintable.sql_statement:="UPDATEinventorySETquantity=$2WHEREname=$1;"_,err:=this.db.Exec(sql_statement,"banana",200)checkError(err)fmt.Println("Updated1rowofdata")}//删除数据func(this*Db)Delete(){//Deletesomedatafromtable.sql_statement:="DELETEFROMinventoryWHEREname=$1;"_,err:=this.db.Exec(sql_statement,"orange")checkError(err)fmt.Println("Deleted1rowofdata")}//数据序列化为Json字符串,供人工查看funcData2Json(anyDatainterface{})string{JsonByte,err:=json.Marshal(anyData)iferr!=nil{log.Printf("数据序列化为json出错:\n%s\n",err.Error())return""}returnstring(JsonByte)}//多行数据分析funcQueryAndParseRows(Db*sql.DB,queryStrstring)[]map[string]string{rows,err:=Db.Query(queryStr)deferrows.Close()iferr!=nil{log.Printf("查询错误:\nSQL:\n%s,错误详情\n",queryStr,err.Error())returnnil}cols,_:=rows.Columns()//列名iflen(cols)>0{varret[]map[string]string//定义返回的映射切片变量retforrows.Next(){buff:=make([]interface{},len(cols))data:=make([][]byte,len(cols))//database里面的NULL值可以扫描到bytefori,_:=rangebuff{buff[i]=&data[i]}rows.Scan(buff...)//扫描到buff接口,其实是字符串类型datamiddle//将每一行数据存储到一个数组中printf("%30s:\t%s\n",cols[k],col)dataKv[cols[k]]=string(col)}ret=append(ret,dataKv)}log.Printf("返回多个elementsArray:\n%s",Data2Json(ret))returnret}else{returnil}}//单行数据分析查询数据库,分析查询结果,支持动态行数分析funcQueryAndParse(Db*sql.DB,queryStrstring)map[string]string{rows,err:=Db.Query(queryStr)deferrows.Close()iferr!=nil{log.Printf("查询错误:\nSQL:\n%s,错误详情\n",queryStr,err.Error())returnnil}//rows,_:=Db.Query("SHOWVARIABLELIKE'%data%'")cols,_:=rows.Columns()iflen(cols)>0{buff:=make([]interface{},len(cols))//临时切片data:=make([][]byte,len(cols))//保存数据slicedataKv:=make(map[string]string,len(cols))fori,_:=rangebuff{buff[i]=&data[i]}forrows.Next(){rows.Scan(buff...)//...需要}fork,col:=rangedata{dataKv[cols[k]]=string(col)//fmt.Printf("%30s:\t%s\n",cols[k],col)}log.Printf("返回单行数据Map:\n%s",Data2Json(dataKv))returndataKv}else{returnnil}}funcmain(){//初始化连接字符串。初始化连接字符串,参数包括主机、端口、用户名、密码、数据库名、SSL模式(禁用)、超时时间varconnectionStringstring=fmt.Sprintf("host=%sport=%duser=%spassword=%sdbname=%ssslmode=disableconnect_timeout=3",HOST,PORT,USER,PASSWORD,DATABASE)//初始化连接对象。初始化连接对象,驱动名称为postgresdb,err:=sql.Open("postgres",connectionString)deferdb.Close()checkError(err)postgresDb:=Db{db:db,}err=postgresDb.db.Ping()//连通性检查checkError(err)fmt.Println("Successfullycreatedconnectiontodatabase")postgresDb.CreateTable()//创建表postgresDb.Insert()//插入数据postgresDb.Read()//查询数据QueryAndParseRows(postgresDb.db,"SELECT*frominventory;")//直接查询解析多行数据QueryAndParse(postgresDb.db,"SHOWDateStyle;")//直接查询解析单行数据postgresDb.Update()//修改/更新数据postgresDb.Read()postgresDb.Delete()//删除数据postgresDb.Read()postgresDb.DropTable()}执行gorunmain.go结果如下:成功createdconnectiontodatabaseFinisheddroppingtable(ifexisted)FinishedcreatingtableInserted3rowsofdataDatarow=(1,banana,150)Datarow=(2,orange,154)Datarow=(3,apple,100)2020/12/1522:13:33返回多元素数组:[{"id":"1","name":"banana","quantity":"150"},{"id":"2","name":"orange","quantity":"154"},{"id":"3","name":"apple","quantity":"100"}]2020/12/1522:13:33返回单行数据Map:{"DateStyle":"ISO,MDY"}Updated1rowofdataDatarow=(2,orange,154)Datarow=(3,apple,100)Datarow=(1,banana,200)Deleted1rowofdataDatarow=(3,apple,100)Datarow=(1,banana,200)finisheddroppingtable(ifexisted)本文对pq驱动包的总结以及连接字符串参数的介绍。示例代码将连接/建表/添加行数据/更新行数据/删除行数据封装成不同的方法,方便灵活使用单行查询或者多行数据,可以直接使用封装的方法,直接传入Db指针和查询语句,参考文档https://docs.microsoft.com/en-us/azure/postgresql/connect-gohttps://pkg.go.dev/github.com/lib/pqhttps://www.postgresql.org/https://www.postgresql.org/docs/current/libpq.html