Go Web 学习笔记(三)数据库操作


Go Web 数据库操作

Go 语言中的 database/sql 包定义了对数据库的一系列操作,database/sql/driver 包定义了应被数据库驱动实现的接口,这些接口被 sql 包使用。

Go 语言中没有提供任何官方的数据库驱动,所以需要导入第三方的数据库驱动。在完成数据库连接驱动之后,针对数据库的处理操作都在 database/sql 中。

Mysql 数据库使用

使用 Go 语言来连接 mysql 数据库需要首先为当前项目导入对应的驱动:

go get github.com/go-sql-driver/mysql

然后对于数据库的操作都是通过 database/sql 下的 DB 结构体中的方法进行的:

type DB struct {
    // 内含隐藏或非导出字段
}

DB 是一个数据库(操作)句柄,代表一个具有零到多个底层连接的连接池,它可以安全的被多个go程同时使用。

sql 包会自动创建和释放连接;它也会维护一个闲置连接的连接池

如果数据库具有单连接状态的概念,该状态只有在事务中被观察时才可信。

一旦调用了 BD.Begin,返回的 Tx 会绑定到单个连接。

当调用事务 TxCommitRollback 后,该事务使用的连接会归还到 DB 的闲置连接池中。

连接池的大小可以用 SetMaxIdleConns 方法控制。

Mysql 数据库使用案例

在项目的 utils 包下创建对应的数据库连接工具文件 (db.go):

package utils

import (
	"database/sql"

	_ "github.com/go-sql-driver/mysql"// 导入该包但并不进行使用
)

var (
	Db  *sql.DB
	err error
)

// 初始化操作,连接数据库
func init() {
    // 打开指定数据库连接
	Db, err = sql.Open("mysql", "root:123456@tcp(localhost:3306)/go_web")
	if err != nil {
		panic(err.Error())
	}
}

该工具文件创建好之后便可以在其他文件中进行引入从而实现相应的数据库操作。

首先在测试数据库 go_web 中创建对应的测试表 users

create table users(
    id int primary key auto_increment,
    username varchar(100) not null unique ,
    password varchar(100) not null ,
    email varchar(100)
)

然后在项目的 models 文件夹下创建 users 表对应的实体类(user.go):

package model

import (
	"fmt"
	"webapptest/utils"
)

// go_web 数据库 User 表对应结构体
type User struct {
	Id       int
	Username string
	Password string
	Email    string
}

// AddUser: 添加 User 的方法一(预编译版本)
func (user *User) AddUser() error {
	// sql语句
	sqlStr := "insert into users(username,password,email) values(?,?,?)"
	// 预编译
	inStmt, err := utils.Db.Prepare(sqlStr)
	if err != nil {
		fmt.Println("预编译出现异常:", err)
	}
	// 执行sql语句
	_, err2 := inStmt.Exec("admin", "123456", "admin@test.com")
	if err2 != nil {
		fmt.Println("执行出现异常", err2)
	}
	return nil
}

// AddUser2: 添加 User 的方法二(直接执行版本)
func (user *User) AddUser2() error {
	// sql语句
	sqlStr := "insert into users(username,password,email) values(?,?,?)"
	// 直接执行sql语句
	_, err := utils.Db.Exec(sqlStr, "admin2", "123456", "admin2@test.com")
	if err != nil {
		fmt.Println("执行出现异常", err)
	}
	return nil
}

// GetUserById 根据用户的id从数据库中查询一条数据
func (user *User) GetUserById() (*User, error) {
	// sql语句
	sqlStr := "select id,username,password,email from users where id = ?"
	// 执行
	row := utils.Db.QueryRow(sqlStr, user.Id)
	// 声明变量
	var id int
	var username string
	var password string
	var email string
	err := row.Scan(&id, &username, &password, &email)
	if err != nil {
		return nil, err
	}
	u := &User{
		Id:       id,
		Username: username,
		Password: password,
		Email:    email,
	}
	return u, nil
}

// GetUsers 获取数据库中的所有记录
func (user *User) GetUsers() ([]*User, error) {
	// sql语句
	sqlStr := "select id,username,password,email from users"
	// 执行
	rows, err := utils.Db.Query(sqlStr)
	if err != nil {
		return nil, err
	}
	// 返回用户切片
	var users []*User
	// 遍历查询结果
	for rows.Next() {
		// 声明变量
		var id int
		var username string
		var password string
		var email string
		err := rows.Scan(&id, &username, &password, &email)
		if err != nil {
			return nil, err
		}
		u := &User{
			Id:       id,
			Username: username,
			Password: password,
			Email:    email,
		}
		// 将查询到的用户添加到切片中
		users = append(users, u)
	}
	// 返回最终结果
	return users, nil
}

对其中的方法进行单元测试,在同文件夹下创建对应的单元测试文件(user_test.go):

package model

import (
	"fmt"
	"testing"
)

func TestAddUser(t *testing.T) {
	fmt.Println("测试添加用户方法:")
	user := &User{}
	// 调用添加用户的方法
	user.AddUser()
	user.AddUser2()
}

// 函数的首字母没有大写的话则可以作为子测试函数运行
func testAddUser(t *testing.T) {
	fmt.Println("测试添加用户方法子测试函数:")
	user := &User{}
	// 调用添加用户的方法
	user.AddUser()
	user.AddUser2()
}

// 测试获取一个User
func testGetUserById(t *testing.T) {
	fmt.Println("测试查询一条数据")
	user := User{
		Id: 1,
	}
	u, err := user.GetUserById()
	if err != nil {
		fmt.Println("查询错误")
	}
	fmt.Println("查询到的数据:", u)
}

// 测试查询所有的User
func TestUser_GetUsers(t *testing.T) {
	fmt.Println("测试查询所有用户数据")
	user := &User{}
	users, err := user.GetUsers()
	if err != nil {
		fmt.Println("查询失败", err)
	}
	// 打印查询结果
	for k, v := range users {
		fmt.Printf("第%v个用户是%v\n", k+1, v)
	}
}

// 利用子测试程序进行测试,之后常用方法
func TestUser(t *testing.T) {
	fmt.Println("测试user的相关方法")
	//t.Run("测试添加用户", testAddUser)
	//t.Run("测试查询一条用户方法", testGetUserById)
}

// TestMain 函数在测试函数执行之前做一些其他操作
func TestMain(m *testing.M) {
	fmt.Println("测试开始...")
	// 得使用 m.Run() 调用测试函数
	m.Run()
}