🌟 使用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.CommitTx.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如果之前学过 SpringBootdjango就会懂,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类型,它是已经准备好的执行状态,可以直接调用,和直接使用增删改查类似,但是如果使用 StmtExec,只需要数据占位字段即可

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️⃣ 在 usermodel类型中创两个用户数据的方法,一个使用预编译一个不使用,它们两个都接收占位字段和一个 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方法被调用时,才会返回被延迟的错误。(如:未找到结果)

⭕️ 它们两个返回的都是 ROWROWS类型

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)
	}