🌟 使用Go语言来操作数据库
⭐️ 在Go中使用 database/sql
这个包来操作数据库,其中/driver
定义了数据据看驱动实现的接口,但是go语言并没有提供任何官方的数据库驱动,需要自己导入
⭐️ 获取数据库连接
🍅 导入 sql
包,创建untils文件夹,写入如下代码
/**
* @Author tanchang
* @Description //TODO
* @Date 2024/6/23 21:39
* @File: db
* @Software: GoLand
**/
package untils
import (
"database/sql"
"fmt"
//导入Mariadb驱动
_ "github.com/go-sql-driver/mysql"
)
var (
DB *sql.DB
err error
)
func DbConnect(driverName, param string) error {
DB, err = sql.Open(driverName, param)
if err != nil {
panic(err)
return err
}
errPing := DB.Ping()
if errPing != nil {
panic(err)
return err
}
return nil
}
1️⃣ 首先定义一个 DB
类型的变量,代表零个或多个底层连接池。多个 goroutine
可以安全地并发使用它。sql
包会自动创建和释放连接;它还维护一个空闲连接池。如果数据库具有每个连接状态的概念,则可以在事务 ( Tx ) 或连接 ( Conn ) 中可靠地观察到这种状态。一旦调用 DB.Begin ,返回的Tx
将绑定到单个连接。一旦在事务上调用Tx.Commit或 Tx.Rollback,该事务的连接将返回到DB的空闲连接池。可以使用DB.SetMaxIdleConns控制池大小。当然这两个变量也可以不需要定义
var (
DB *sql.DB
err error
)
2️⃣ 创建一个工具方法,作用是用于连接数据库,这里接收了两个参数,分别是驱动名和连接地址
func DbConnect(driverName, param string) error {
DB, err = sql.Open(driverName, param)
if err != nil {
panic(err)
return err
}
errPing := DB.Ping()
if errPing != nil {
panic(err)
return err
}
return nil
}
使用
Open
函数接收那两个参数,这里着重讲一下连接地址,它的写法有点怪 数据库名:数据库密码@tcp(localhost:3306)/数据库名
它是这样写的,但是Open并不是创建数据库连接,它知识验证参数的,如果要检查数据库的源的名字是否合法,就使用 Ping
方法,它会检查数据库的连接是否有效,如果有效就创建连接
返回的DB对象可以安全的被多个
Go
程使用,并且会维护自身的闲置连接池
3️⃣ 在 main
函数中调用,注意数据库连接地址要填对,确定有那个表(这里需要在项目主目录下创建一个 mian.go
)
func main() {
errConnect := DbConnect("mysql", "root:000000@tcp(localhost:3306)/gotest")
if errConnect == nil {
fmt.Println("连接成功!!")
}
}
⭐️增删改查
🍅 在你的数据库中,创建一个 user
数据表,结构为如下
CREATE TABLE `user` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`user` varchar(100) DEFAULT NULL,
`password` varchar(100) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
另外还需要创建一个 model
如果之前学过 SpringBoot
和 django
就会懂,model
也就是你数据表中字段的实体类,创建 model
目录存放 model
type user struct {
Id int64
Name string
Password string
Email string
}
🍵 增删改
⭕️增是用的 Exec
方法
func (*DB) Exec()
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
Exec执行一次命令(包括查询、删除、更新、插入等),不返回任何执行结果。参数 args
表示 query
中的占位参数,到时候会写 sql
语句占位符为**?
**号
⭕️有时候也会用预编译执行 Exec
方法,它返回了一个 Stmt
类型,它是已经准备好的执行状态,可以直接调用,和直接使用增删改查类似,但是如果使用 Stmt
的 Exec
,只需要数据占位字段即可
func (*DB) Prepare
func (db *DB) Prepare(query string) (*Stmt, error)
Prepare创建一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令。
⭕️ Stmt
是准备好的状态。Stmt
可以安全的被多个go程同时使用,它内部也提供了方法
type [Stmt]
type Stmt struct {
// 内含隐藏或非导出字段
}
func (*Stmt) Exec
func (s *Stmt) Exec(args ...interface{}) (Result, error)
Exec使用提供的参数执行准备好的命令状态,返回Result类型的该状态执行结果的总结。
func (*Stmt) Query
func (s *Stmt) Query(args ...interface{}) (*Rows, error)
Query使用提供的参数执行准备好的查询状态,返回Rows类型查询结果。
func (*Stmt) QueryRow
func (s *Stmt) QueryRow(args ...interface{}) *Row
QueryRow使用提供的参数执行准备好的查询状态。如果在执行时遇到了错误,该错误会被延迟,直到返回值的Scan方法被调用时才释放。返回值总是非nil的。如果没有查询到结果,*Row类型返回值的Scan方法会返回ErrNoRows;否则,Scan方法会扫描结果第一行并丢弃其余行。
示例用法:
var name string
err := nameByUseridStmt.QueryRow(id).Scan(&name)
func (*Stmt) Close
func (s *Stmt) Close() error
Close关闭状态。
1️⃣ 在 user
的 model
类型中创两个用户数据的方法,一个使用预编译一个不使用,它们两个都接收占位字段和一个 DB
类型用于执行 sql
// addUserPrepare 添加用户方法,使用了预编译
func (This *User) addUserPrepare(user, password, email string, DB *sql.DB) error {
//Sql语句
sqlStr := "insert into user(user,password,email) values(?,?,?)"
//预处理
inStmt, err := DB.Prepare(sqlStr)
if err != nil {
return err
}
//执行Sql
_, errExec := inStmt.Exec(user, password, email)
if errExec != nil {
return errExec
}
return nil
}
// addUser 添加用户方法,使用了预编译
func (This *User) addUser(user, password, email string, DB *sql.DB) error {
//Sql语句
sqlStr := "insert into user(user,password,email) values(?,?,?)"
//执行Sql
_, errExec := DB.Exec(sqlStr, user, password, email)
if errExec != nil {
return errExec
}
return nil
}
// deleteUser 删除用户
func (This *User) deleteUser(user, password string, DB *sql.DB) error {
//SQL语句
sqlStr := "delete from user where user= ? and password= ? "
_, errExec := DB.Exec(sqlStr, user, password)
if errExec != nil {
fmt.Println("执行S去了语句错误", errExec)
return errExec
}
return nil
}
// 修改用户
func (This *User) updateUser(user, password, email string, DB *sql.DB) (error, sql.Result) {
//SQL语句
var sqlStr string
if email == "" {
sqlStr = "update user set password= ? where user= ? "
} else if password == "" {
sqlStr = "update user set email= ? where user= ? "
} else if password == "" && email == "" {
err := errors.New("未修改任何值")
return err, nil
} else {
sqlStr = "update user set password= ?,email= ? where user= ? "
}
res, errExec := DB.Exec(sqlStr, password, email, user)
if errExec != nil {
fmt.Println("执行sql语句错误", errExec)
return errExec, res
}
return nil, res
}
2️⃣ go中的单元测试
在 go
中也有测试方法 testing
包,要编写一个新的测试套件,需要创建一个名称以 _test.go 结尾的文件,该文件包含 TestXxx
函数,如上所述。 将该文件放在与被测试的包相同的包中。该文件将被排除在正常的程序包之外,但在运行 “go test” 命令时将被包含。 有关详细信息,请运行 “go help test” 和 “go help testflag” 了解。
它的命名规则为 TestXxxx
func TestXxx(*testing.T)
创建 user_test.go
文件编写测试代码
package model
import (
"GoWeb/Go_DB/untils"
"fmt"
"testing"
)
func TestAddUser(t *testing.T) {
errConnect := untils.DbConnect("mysql", "root:000000@tcp(localhost:3306)/gotest")
db := untils.DB
if errConnect == nil {
fmt.Println("连接成功!!")
}
user := &User{
Name: "tanchang",
Password: "123456",
Email: "tanchang@163.com",
}
errAddPre := user.addUserPrepare(user.Name, user.Password, user.Email, db)
if errAddPre != nil {
panic(errAddPre)
}
errAdd := user.addUser("test01", "1234567", "xxx02@email.com", db)
if errAdd != nil {
panic(errAdd)
}
//修改用户
errUpdate, res := user.updateUser("test01", "1234567", "test", db)
if errUpdate != nil {
fmt.Println("修改失败", errUpdate)
}
fmt.Println("修改成功", res)
//删除用户
errDelete := user.deleteUser(user.Name, user.Password, db)
if errDelete != nil {
panic(errDelete)
}
fmt.Println("删除成功")
}
运行即可测试
🍵 查询
⭕️ 查询何增删改不是使用的同一个方法,因为查询是需要有返回值的,使用 Query
方法查询
func (*DB) Query
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
Query执行一次查询,返回多行结果(即 Rows
),一般用于执行select命令。参数 args
表示 query
中的占位参数。
func (*DB) QueryRow
func (db *DB) QueryRow(query string, args ...interface{}) *Row
QueryRow
执行一次查询,并期望返回最多一行结果(即Row)。QueryRow
总是返回非nil的值,直到返回值的Scan方法被调用时,才会返回被延迟的错误。(如:未找到结果)
⭕️ 它们两个返回的都是 ROW
和 ROWS
类型
type Rows
type Rows struct {
// 内含隐藏或非导出字段
}
Rows
是查询的结果。它的游标指向结果集的第零行,使用 Next
方法来遍历各行结果:
rows, err := db.Query("SELECT ...")
...
defer rows.Close()
for rows.Next() {
var id int
var name string
err = rows.Scan(&id, &name)
...
}
err = rows.Err() // get any error encountered during iteration
...
func (*Rows) Columns
func (rs *Rows) Columns() ([]string, error)
Columns
返回列名。如果 Rows
已经关闭会返回错误。
func (*Rows) Scan
func (rs *Rows) Scan(dest ...interface{}) error
Scan
将当前行各列结果填充进 dest
指定的各个值中。
如果某个参数的类型为*[]byte,Scan
会保存对应数据的拷贝,该拷贝为调用者所有,可以安全的,修改或无限期的保存。如果参数类型为*RawBytes
可以避免拷贝;参见 RawBytes
的文档获取其使用的约束。
如果某个参数的类型为*interface{},Scan会不做转换的拷贝底层驱动提供的值。如果值的类型为[]byte,会进行数据的拷贝,调用者可以安全使用该值。
func (*Rows) Next
func (rs *Rows) Next() bool
Next准备用于Scan方法的下一行结果。如果成功会返回真,如果没有下一行或者出现错误会返回假。Err应该被调用以区分这两种情况。
每一次调用Scan方法,甚至包括第一次调用该方法,都必须在前面先调用Next方法。
func (*Rows) Close
func (rs *Rows) Close() error
Close关闭Rows,阻止对其更多的列举。 如果Next方法返回假,Rows会自动关闭,满足。检查Err方法结果的条件。Close方法时幂等的(多次调用无效的成功),不影响Err方法的结果。
func (*Rows) Err
func (rs *Rows) Err() error
Err返回可能的、在迭代时出现的错误。Err需在显式或隐式调用Close方法后调用。
1️⃣ 编写代码
// 查询用户
func (This *User) queryUser(user, password string, DB *sql.DB) error {
//SQL语句
sqlStr := "select * from user where user= ? and password= ? "
//执行Sql
rows, errExec := DB.Query(sqlStr, user, password)
if errExec != nil {
fmt.Println("执行S去了语句错误", errExec)
return errExec
}
defer rows.Close()
fmt.Println("查询成功!")
//遍历结果集
for rows.Next() {
var u User
err := rows.Scan(&u.Id, &u.Name, &u.Password, &u.Email)
if err != nil {
return err
}
fmt.Println(u)
}
return nil
}
2️⃣ 测试
//查询用户
errQuery := user.queryUser("test01", "1234567", db)
if errQuery != nil {
panic(errQuery)
}