Golang数据库编程详解 | 深入浅出Go语言原生数据库编程

 

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。

Golang学习专栏:https://blog.csdn.net/qq_35716689/category_12575301.html

前言

对数据库的CURD是现代应用程序的必备功能,Go语言当然也对数据库的操作提供了非常完善的支持。

尽管在Go语言社区中有很多优秀的ORM库或框架(比如GORM,后面也会发文)能让我们更方便地操作数据库,不过要更好地使用ORM库,掌握Go原生操作数据库database/sql包的使用还是有必要的。

所以,在这篇文章中,我们先来学习一下database/sql包的使用吧。

目录

前言

database/sql简介

连接数据库

MySQL

Postgres

SQLite

关闭连接

连接池设置

示例数据表

sql.DB

sql.Stmt

sql.Tx

小结


database/sql简介

我们可以把标准库中的database/sql包看作一个数据库操作抽象层,因为database/sql并不直接连接并操作数据库,而是为不同的数据库驱动提供了统一的API,database/sql包与驱动包(driver)的关系如下图所示:

对数据库的具体操作由对应数据库的驱动包来完成,访问不同的类型的数据库需要导入不同的驱动包,而所有驱动包都必须实现database/sql/driver包下的相关接口,因此database/sql、database/sql/driver以及驱动包的关系如下图所示:

这样做的好处在于,如果某一天我们想迁移数据库,比如从MySQL切换为Postgres,只需要更换驱动包即可,而不需要把数据库操作的代码重写一遍。

连接数据库

连接到数据库,获取一个数据库操作句柄,简单来说可以分为三步:

  1. 选择对应数据库的驱动,并导入。
  2. 配置连接数据库所需的DSN。
  3. 使用sql.Open()函数打开数据连接,获得*sql.DB对象。

DSN是Data Source Name,包含了连接数据库所需的参数,比如用户,密码、数据编码、数据库名称等。

下面我们以MySQL,Postgres,SQLite为例介绍在Go语言中如何连接到数据库。

MySQL

连接MySQL驱动包推荐用github.com/go-sql-driver/mysql库,其完整的DSN为如以所示,可以看到MySQL的DSN各个部分都是可选的:

[username[:password]@][protocol[host[:port]]]/dbname[?param1=value1&...&paramN=valueN]
  • username:用户名。
  • password:密码,与用户名之间要用冒号分隔。
  • protocol:网络协议,默认用tcp即可。
  • host:数据库地址。
  • port:端口号,默认为3306
  • dbname:数据库名称
  • param1~paramN:可选参数。
package main import ("database/sql"// 匿名导入_ "github.com/go-sql-driver/mysql"
)func main(){//db为*sql.DB//DSN要根据实际替换db, err := sql.Open("mysql", "user:password@/dbname")if err != nil {panic(err)}
}

Postgres

连接Postgres数据库推荐用github.com/lib/pq,其连接DSN为:

host=192.168.0.1 user=postgres dbname=postgres port=5432 password=123456 sslmode=disable
  • user:用户名。
  • password:密码,与用户名之间要用冒号分隔。
  • dbname:数据库名称。
  • sslmode:是否使用ss模式,其值可以是disable,required,verify-ca ,verify-full等。
  • host:主机
  • port:端口号
package main import ("context""fmt""os""github.com/lib/pq"
)func main(){dsn := "user=postgres dbname=postgres password=123456 sslmode=disable"db, err := sql.Open("postgres", dsn)if err != nil {log.Fatal(err)}defer db.Close()
}

SQLite

连接SQLite数据库推荐用github.com/mattn/go-sqlite3,SQLite是嵌入式数据库,因此它的DSN是数据库文件所在的路径:

import ("database/sql"_ "github.com/mattn/go-sqlite3"
)func main(){dsn := "./test.db"db,err := sql.Open("sqlite3",dsn)
}

关闭连接

打开数据库连接要记得关闭数据连接,一般在defer语句后面调用sql.DB的Close()方法:

import ("database/sql"_ "github.com/mattn/go-sqlite3"
)func main(){dsn := "./test.db"db,err := sql.Open("sqlite3",dsn)if err != nil{panic(err)}//关闭数据库连接defer db.Close()
}

连接池设置

sql.DB对象内包含一个数据库连池,并支持通过以下几个方法设置连接池的相关配置:

	db.SetConnMaxLifetime(0)  //最大打开的连接时间db.SetMaxIdleConns(50) //最大闲置连接数db.SetMaxOpenConns(50) //最大打开的连接数

示例数据表

为了后面更好的进行实例演示,我们需要一个演示数据库,这里以Mysql数据库为例,数据库名称为test,创建一个users数据表:

CREATE DATABASE IF NOT EXISTS test;USE test;CREATE TABLE IF NOT EXISTS users(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
gender TINYINT NOT NULL DEFAULT 0,
mobile VARCHAR(255) NOT NULL DEFAULT ''
);

sql.DB

前面我们调用sql.Open()获得了sql.DB对象,这是操作数据的句柄,也可以理解为一个数据库连接池,可以通过sql.DB的Conn()可以获得数据库连接池里的单个连接对象sql.Conn:

ctx, _ := context.WithCancel(context.Background())
conn, err := db.Conn(ctx)

无论是sql.DB,还是sql.Conn,其对数据库执行CURD操作的方法是类似,一般推荐使用sql.DB。

执行insert、update、delete用Exec()和ExecContext()方法,其签名如下:

func (db *DB) Exec(query string, args ...any) (Result, error)
func (db *DB) ExecContext(ctx context.Context, query string, args ...any) (Result, error)

上面的方法执行后返回sql.Result接口的实例,这个接口只有两个方法:

type Result interface {//执行insert语句时,返回自增idLastInsertId() (int64, error)//影响行数RowsAffected() (int64, error)
}

示例代码:

package mainimport ("database/sql""fmt"_ "github.com/mattn/go-sqlite3"
)func main() {db, err := sql.Open("sqlite3", "../test.db")if err != nil {panic(err)}defer db.Close()insertSql := "INSERT INTO users(name,gender,mobile) VALUES(?,?,?),(?,?,?)"insertResult, err := db.Exec(insertSql, "小白", 2, "166xxxxxxxx", "小张", 1, "13493023333")if err != nil {panic(err)}lastInsertId, _ := insertResult.LastInsertId()fmt.Printf("最新记录id:%d\n", lastInsertId)rowsAffected, _ := insertResult.RowsAffected()fmt.Println("影响行数:", rowsAffected)updateSql := "UPDATE users SET mobile = ? WHERE id = ?"updateResult, err := db.Exec(updateSql, "136xxxxxxxx", 1)if err != nil {panic(err)}rowsAffected, _ = updateResult.RowsAffected()fmt.Printf("影响行数:%d\n", rowsAffected)deleteSql := "DELETE FROM users WHERE id = ?"deleteResult, err := db.Exec(deleteSql, 14)if err != nil {panic(err)}rowsAffected, _ = deleteResult.RowsAffected()fmt.Printf("影响行数:%d\n", rowsAffected)
}

如果你只想从数据表查询一行数据,可以调用QueryRow()和QueryRowContext()方法:

func (db *DB) QueryRow(query string, args ...any) *Row
func (db *DB) QueryRowContext(ctx context.Context, query string, args ...any) *Row

上面的方法返回的是sql.Row对象,代表查询的那一行数据,这个对象只有Scan方法可以获取对象里的数据,调用Scan方法时传进去参数个数必须与查询返回的数据列数一致:

package mainimport ("database/sql""fmt"_ "github.com/mattn/go-sqlite3"
)func main() {db, err := sql.Open("sqlite3", "./test.db")if err != nil {panic(err)}defer db.Close()selectOne := "SELECT * FROM users WHERE id = ?"row := db.QueryRow(selectOne, 1)var (id     intname   stringgender uint8mobile string)//扫描数据err = row.Scan(&id, &name, &gender, &mobile)if err != nil {panic(err)}genderText := "未知"switch gender {case 1:genderText = "男"case 2:genderText = "女"}fmt.Printf("用户:%s | 性别:%s | 手机:%s\n", name, genderText, mobile)
}

然而,更多的时候,我们需要查询多行数据,可以调用Query()和QueryContext()方法:

func (db *DB) Query(query string, args ...any) (*Rows, error)
func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)

上面两个方法返回的是sql.Rows对象,代表查询回来的多行数据,可以在for循环语句中通过Next()和Scan()方法扫描每一行数据,传给Scan方法的参数数量必须与查询回来的列数相同,另外,要记得调用Close()方法关闭sql.Rows对象:

package mainimport ("database/sql""fmt""log"_ "github.com/mattn/go-sqlite3"
)func main() {db, err := sql.Open("sqlite3", "./test.db")if err != nil {panic(err)}defer db.Close()selectMany := "SELECT * FROM users"rows, err := db.Query(selectMany)if err != nil {panic(err)}defer rows.Close()for rows.Next() {var (id     intname   stringgender uint8mobile string)if err := rows.Scan(&id, &name, &gender, &mobile); err != nil {log.Fatal(err)}genderText := "未知"switch gender {case 1:genderText = "男"case 2:genderText = "女"}fmt.Printf("用户:%s | 性别:%s | 手机:%s\n", name, genderText, mobile)}}

sql.Stmt

SQL语句预编译机制允许先把带有参数占位符的SQL语句发送给数据库,数据库会提前对SQL语句进行编译,之后我们再发送对应占位符的参数给数据库 。

SQL语句的好处在于:

  • 预防SQL注入攻击。
  • 复杂的SQL语句,提前编译提高SQL语句执行效率。

调用sql.DB、sql.Conn和sql.Tx(下面会讲到)的Prepare()方法或者PrepareContext()会返回一个sql.Stmt:

stmt, err := db.Prepare("INSERT INTO users(name,) VALUES(?,?,?),(?,?,?)")

因为SQL语句已经预发送给数据库,因此调用sql.Stmt的ExecXXX()和QueryXXX()时,只需要发送参数即可:

Stmt.Exec("张三",2,"10086","李四",1,"10086111")

sql.Stmt对象里的方法返回值与sql.DB一样是sql.Result,具体使用参考前面的例子。

sql.Tx

sql.Tx表示一个数据库事务对象,调用sql.DB或者sql.Conn对象的Begin()或者BeginTx()方法会返回一个sql.Tx:

tx,err := db.Begin()

sql.Tx与sql.DB执行CURD的方法基本相同,所不同的是,通过sql.Tx执行的语句,最后要调用sql.Tx的Commit()方法提交事务,如果执行时有错误发生,则应该调用Rollback()方法回滚事务:

package mainimport ("database/sql"_ "github.com/mattn/go-sqlite3"
)func main() {db, err := sql.Open("sqlite3", "./test.db")if err != nil {panic(err)}defer db.Close()tx, err := db.Begin()if err != nil {panic(err)}if _, err := tx.Exec("INSERT INTO users VALUES(?,?,?,?)", 1, "小龙", 1, "137xxxxxxxx"); err != nil {tx.Rollback()}if _, err := tx.Exec("INSERT INTO users VALUES(?,?,?,?)", 2, "小明", 1, "137xxxxxxxx"); err != nil {tx.Rollback()}tx.Commit()
}

总结

通过本文的学习,我们能够掌握Go语言数据库编程的基本技能,并能够独立进行数据库应用程序的开发。Go语言数据库编程是一个实践性很强的领域,需要不断地学习和实践。希望本文能够为你的学习之路提供指导和帮助。

简单总结一下:

  • sql.DB对象表示一个数据库句柄,其中包含一个数据库连接池。
  • sql.Conn对象表示一个连接池里的普通数据库连接。
  • sql.Tx表示一个数据库事务对象,通过该对象执行的SQL语句要调用Commit()方法才会生效,如果执行过程发生错误,要调用Rollback()方法回滚事务。
  • sql.Stmt表示一个预编译对象,通过这个对象执行的SQL语句会先发送给数据库,之后再发送参数,这样可以避免SQL注入以及提前编译语句,提高执行效率。
  • sql.Rows是调用QueryXXX这类方法返回的表示多行数据的对象,内置迭代器,可以调用for语句迭代查询回来的数据。
  • sql.Row是调用QueryRowXXX这类方法返回的对象,代表一行数据。
  • sql.Result是调用ExecXXX这类方法进行update,delete,insert操作之后返回的结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/254065.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue3编写简单的App组件(二)

一、Vue3页面渲染基本流程 1、入口文件 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><link rel"icon" href"/favicon.ico"><meta name"viewport" content"widthde…

es6中标签模板

之所以写这篇文章&#xff0c;是因为标签模板是一个很容易让人忽略的知识点 首先我们已经非常熟悉模板字符串的使用方法 const name "诸葛亮" const templateString hello, My name is ${name}标签模板介绍 这里的标签模板其实不是模板&#xff0c;而是函数调用…

【CV论文精读】【MVDet】Multiview Detection with Feature Perspective Transformation

0.论文摘要 合并多个摄像机视图进行检测减轻了拥挤场景中遮挡的影响。在多视图检测系统中&#xff0c;我们需要回答两个重要问题。首先&#xff0c;我们应该如何从多个视图中聚合线索&#xff1f;第二&#xff0c;我们应该如何从空间上相邻的位置聚集信息&#xff1f;为了解决…

Java项目管理01-Maven基础

一、Maven的常用命令和生命周期 1.Maven的常用命令使用方式 complie&#xff1a;编译&#xff0c;将java文件编译为class字节码文件 clean&#xff1a;清理&#xff0c;删除字节码文件 test&#xff1a;测试&#xff0c;运行项目中的test类 package&#xff1a;打包&#x…

Leetcode 45. 跳跃游戏 II

本题与55. 跳跃游戏十分类似&#xff0c;区别在于本题是要求出最小的跳跃次数。 在55. 跳跃游戏的框架上&#xff0c;我们需要增加一些东西&#xff1a; 既然要计算最小跳跃次数&#xff0c;就需要用一个变量计数跳跃的次数&#xff1b;需要一次前瞻&#xff0c;来计算之后那次…

3.1 Verilog 连续赋值

关键词&#xff1a;assign&#xff0c; 全加器 连续赋值语句是 Verilog 数据流建模的基本语句&#xff0c;用于对 wire 型变量进行赋值。&#xff1a; 格式如下 assign LHS_target RHS_expression &#xff1b; LHS&#xff08;left hand side&#xff09; 指赋值操作…

圣诞节酷炫特效合集【含十几个HTML+CSS前端特效+34个桌面酷炫圣诞程序】

写在前面 ❤️源码获取:订阅后见文末 ❤️内容介绍:包含HTML+CSS等十几个圣诞特效;以及三十四个桌面酷炫圣诞树合集 ❤️订阅后所得如下: ❤️HTML圣诞+桌面圣诞程序效果如下: 下方展示代码仅举例其中几个 所有效果源码及文件订阅后找博主获取即可 🎄css3圣诞雪人动…

大模型2024规模化场景涌现,加速云计算走出第二增长曲线

导读&#xff1a;2024&#xff0c;大模型第一批规模化应用场景已出现。 如果说“百模大战”是2023年国内AI产业的关键词&#xff0c;那么2024年我们将正式迈进“应用为王”的新阶段。 不少业内观点认为&#xff0c;2024年“百模大战”将逐渐收敛甚至洗牌&#xff0c;而大模型在…

PHPExcel导出excel

PHPExcel下载地址 https://gitee.com/mirrors/phpexcelhttps://github.com/PHPOffice/PHPExcel 下载后目录结构 需要的文件如下图所示 将上面的PHPExcel文件夹和PHPExcel.php复制到你需要的地方 这是一个简单的示例代码 <?php$dir dirname(__FILE__); //require_once …

YOLOv8改进 | 利用训练好权重文件计算YOLOv8的FPS、推理每张图片的平均时间(科研必备)

一、本文介绍 本文给大家带来的改进机制是利用我们训练好的权重文件计算FPS,同时打印每张图片所利用的平均时间,模型大小(以MB为单位),同时支持batch_size功能的选择,对于轻量化模型的读者来说,本文的内容对你一定有帮助,可以清晰帮你展示出模型速度性能的提升以及轻量…

基于Chrome插件的Chatgpt对话无损导出markdown格式(Typora完美显示)

刚刚提交插件到Chrome插件商店正在审核&#xff0c;想尝试的可以先使用&#xff1a; https://github.com/thisisbaiy/ChatGPT-To-Markdown-google-plugin/tree/main 我将源代码上传至了GitHub&#xff0c;欢迎star, IssueGoogle插件名称为&#xff1a;ChatGPT to MarkDown plus…

黑马头条 Kafka

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 知…

ArcGIS学习(六)地理数据库

ArcGIS学习(六)地理数据库 上个任务我们讲了一个非常重要的知识点一一坐标系。这个任务我们带来另外一个很重要的知识点一一地理数据库。 地理数据库的内容相比于坐标系简单很多! 首先,先让我们来学习下地理数据库的理论。 ArcGIS 中的地理数据库(Geodatabase)是一个用…

蓝桥杯训练-Huffman树(哈夫曼树)(day14)

一、题目 Huffman树在编码中有着广泛的应用&#xff0c;在这里&#xff0c;只关心Huffman树的构造过程。 给出一列数{pi}{p0,p1,...pn-1},用这列数构造Huffman树的过程如下&#xff1a; 1.找出{pi}中最小的两个数&#xff0c;设为pa和pb,将pa和pb从{pi}中删除&#xff0c;然…

【C#】.net core 6.0 设置根目录下某个文件夹可访问,访问创建的图片等资源

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握。…

Fink CDC数据同步(六)数据入湖Hudi

数据入湖Hudi Apache Hudi(简称&#xff1a;Hudi)使得您能在hadoop兼容的存储之上存储大量数据&#xff0c;同时它还提供两种原语&#xff0c;使得除了经典的批处理之外&#xff0c;还可以在数据湖上进行流处理。这两种原语分别是&#xff1a; Update/Delete记录&#xff1a;H…

信创ARM架构QT应用开发环境搭建

Linux ARM架构QT应用开发环境搭建 前言交叉工具链Ubuntu上安装 32 位 ARM 交叉工具链Ubuntu上安装 64 位 ARM 交叉工具链 交叉编译 QT 库下载 QT 源码交叉编译 QT 源码 Qt Creator交叉编译配置配置 Qt Creator Kits创建一个测试项目 小结 前言 有没有碰到过这种情况&#xff1…

一文讲透Python函数中的形式参数和实际参数

函数参数包括形式参数和实际参数&#xff0c;简称形参和实参。其中形式参数即是在定义函数时函数后面括号中的参数列表&#xff08;parameterlist&#xff09;&#xff0c;比如上一个帖子的示例中的width, length&#xff1b;实际参数则是调用函数时函数后面括号中的参数值&…

Docker配置Portainer容器管理界面

目录 一、Portainer 简介 优点&#xff1a; 缺点&#xff1a; 二、环境配置 1. 拉取镜像 2. 创建启动容器 三、操作测试 1. 进入容器 2. 拉取镜像并部署 3. 访问测试 一、Portainer 简介 Portainer 是一个开源的轻量级容器管理界面&#xff0c;用于管理 Docker 容器…

开源免费的物联网网关 IoT Gateway

1. 概述 物联网网关&#xff0c;也被称为IOT网关&#xff0c;是一种至关重要的网络设备。在物联网系统中&#xff0c;它承担着连接和控制各种设备的重要任务&#xff0c;将这些设备有效地连接到云端、本地服务器或其他设备上。它既能够在广域范围内实现互联&#xff0c;也能在…