GoWeb 操作数据库

go语言中的database/sql包定义了对数据库的一系列操作。database/sql/driver包定义了应被数据库驱动实现的接口,这些接口会被sql包使用。但是go语言没有提供任何官方的数据库驱动,所以需要导入第三方的数据库驱动。不过连接数据库之后对数据库操作的大部分代码都使用sql包

获取数据库连接

  1. 创建一个db.go文件,导入database/sql包以及第三方驱动包
import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
)
  1. 定义两个全局变量
var (
	Db *sql.DB
	err error
)
  • DB结构体说明

type DB

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

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

sql包会自动创建和释放连接;它也会维护一个闲置连接的连接池。如果数据库具有单连接状态的概念,该状态只有在事务中被观察时才可信。一旦调用了BD.Begin,返回的Tx会绑定到单个连接。当调用事务Tx的Commit或Rollback后,该事务使用的连接会归还到DB的闲置连接池中。连接池的大小可以用SetMaxIdleConns方法控制

  1. 创建init函数,在函数体中调用sql包的Open函数获取连接
func init() {
	Db, err = sql.Open("mysql", "root:root@tcp(10.0.0.51:3306)/test")
	if err != nil {
		panic(err.Error())
	}
}
  • Open函数说明
    func Open
func Open(driverName, dataSourceName string) (*DB, error)

Open打开一个dirverName指定的数据库,dataSourceName指定数据源,一般包至少括数据库文件名和(可能的)连接信息

  • dataSourceName格式
数据库用户名:数据库密码@[tcp(localhost:3306)]/数据库名
  • Open函数可能只是验证其参数,而不创建与数据库的连接。如果要检查数据源的名称是否合法,需调用返回值的Ping方法

func (*DB) Ping

func (db *DB) Ping() error

Ping检查与数据库的连接是否仍有效,如果需要会创建连接

  • 返回的DB可以安全的被多个go程同时使用,并会维护自身的闲置连接池。这样,Open函数只需调用一次,很少需要关闭DB

mysql数据库设置

设置远程登录用户

grant all on *.* to 'root'@'%' identified by 'root';

登录

mysql -h 10.0.0.51 -uroot -proot

增删改操作

  1. 在连接的test数据库中创建一个users表
create table users(
id int primary key auto_increment,
username varchar(100) unique not null,
password varchar(100) not null,
email varchar(100)
);
  1. 向users表中插入一条记录
    user.go
  • Prepare方法说明
    func (*DB) Prepare
func (db *DB) Prepare(query string) (*Stmt, error)

Prepare创建一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令

  • Stmt结构体及其方法说明

type Stmt

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

Stmt是准备好的状态。Stmt可以安全的被多个go程同时使用。

  • 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方法会扫描结果第一行并丢弃其余行

  • DB结构体中Exec、Query和QueryRow方法

func (*DB) Exec

func (db *DB) Exec(query string, args ...interface{}) (Result, error)

Exec执行一次命令(包括查询、删除、更新、插入等),不返回任何执行结果。参数args表示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方法被调用时,才会返回被延迟的错误。(如:未找到结果)

代码
在这里插入图片描述

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:root@tcp(10.0.0.51:3306)/test")
	if err != nil {
		panic(err.Error())
	}
}

model/user.go

package model

import (
	"fmt"
	"goweb/web01_db/utils"
)

//User 结构体
type User struct {
	ID int
	Username string
	Password string
	Email string
}

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

//AddUser2 添加User方法2
func (user *User) AddUser2() error {
	//1.写sql语句
	sqlStr := "insert into users(username,password,email) values(?,?,?)"

	//2.执行
	_, err := utils.Db.Exec(sqlStr, "admin2", "666666", "admin2@sina.com")
	if err != nil {
		fmt.Println("执行出现异常: err2=", err)
		return err
	}
	return nil
}

model/user_test.go

package model

import (
	"fmt"
	"testing"
)

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

单元测试(子测试函数)

package model

import (
	"fmt"
	"testing"
)

func TestUser(t *testing.T)  {
	fmt.Println("开始测试User中相关方法")
	//通过t.Run()来执行子测试函数
	t.Run("测试添加用户:", testAddUser)
}
//如果函数名不是以Test开头,那么该函数默认不执行,可以将它设置成一个子测试函数
func testAddUser(t *testing.T)  {
	fmt.Println("子测试函数执行:")
	//user := &User{}
	//调用添加用户的方法
	//user.AddUser()
	//user.AddUser2()
}
  • TestMain(m *testing.M)函数可以在测试之前和之后做一些其他操作
    a. 测试文件中有TestMain函数时,执行go test 命令将直接运行TestMain函数,不直接运行测试函数,只有在TestMain函数中执行m.Run()时才会执行测试函数
package model

import (
	"fmt"
	"testing"
)

//TestMain函数可以在测试函数执行之前做一些其他操作
func TestMain(m *testing.M)  {
	fmt.Println("测试开始:")
	//通过m.Run()来执行测试函数
	m.Run()
}
func TestUser(t *testing.T)  {
	fmt.Println("开始测试User中相关方法")
	//通过t.Run()来执行子测试函数
	t.Run("测试添加用户:", testAddUser)
}
//如果函数名不是以Test开头,那么该函数默认不执行,可以将它设置成一个子测试函数
func testAddUser(t *testing.T)  {
	fmt.Println("子测试函数执行:")
	//user := &User{}
	//调用添加用户的方法
	//user.AddUser()
	//user.AddUser2()
}

Main
测试程序有时需要在测试之前或之后进行额外的设置(setup)或拆卸(teardown)。有时, 测试还需要控制在主线程上运行的代码。为了支持这些和其他一些情况, 如果测试文件包含函数:

func TestMain(m *testing.M)

那么生成的测试将调用 TestMain(m),而不是直接运行测试。TestMain 运行在主 goroutine 中, 可以在调用 m.Run 前后做任何设置和拆卸。应该使用 m.Run 的返回值作为参数调用 os.Exit。在调用 TestMain 时, flag.Parse 并没有被调用。所以,如果 TestMain 依赖于 command-line 标志 (包括 testing 包的标记), 则应该显示的调用 flag.Parse。

一个简单的 TestMain 的实现:

func TestMain(m *testing.M) {
	// call flag.Parse() here if TestMain uses flags
    // 如果 TestMain 使用了 flags,这里应该加上 flag.Parse()
	os.Exit(m.Run())
}

获取一条记录

  • 根据用户的id从数据库中获取一条记录

user.go

package model

import (
	"fmt"
	"goweb/web01_db/utils"
)

//User 结构体
type User struct {
	ID int
	Username string
	Password string
	Email string
}

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

//AddUser2 添加User方法2
func (user *User) AddUser2() error {
	//1.写sql语句
	sqlStr := "insert into users(username,password,email) values(?,?,?)"

	//2.执行
	_, err := utils.Db.Exec(sqlStr, "admin2", "666666", "admin2@sina.com")
	if err != nil {
		fmt.Println("执行出现异常: err2=", err)
		return 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
}

user_test.go

package model

import (
	"fmt"
	"testing"
)

//TestMain函数可以在测试函数执行之前做一些其他操作
func TestMain(m *testing.M)  {
	fmt.Println("测试开始:")
	//通过m.Run()来执行测试函数
	m.Run()
}
func TestUser(t *testing.T)  {
	fmt.Println("开始测试User中相关方法")
	//通过t.Run()来执行子测试函数
	//t.Run("测试添加用户:", testAddUser)
	t.Run("测试获取用户:", testGetUserByID)
}
//如果函数名不是以Test开头,那么该函数默认不执行,可以将它设置成一个子测试函数
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,
	}
	//调用获取User的方法
	u, _ := user.GetUserById()
	fmt.Println("得到的User信息: ", u)
}
  • Row结构体及Scan方法说明

type Row

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

QueryRow方法返回Row,代表单行查询结果。

func (*Row) Scan

func (r *Row) Scan(dest ...interface{}) error

Scan将该行查询结果各列分别保存进dest参数指定的值中。如果该查询匹配多行,Scan会使用第一行结果并丢弃其余各行。如果没有匹配查询的行,Scan会返回ErrNoRows

获取多条记录

从数据库中查询出所有的记录

user.go

package model

import (
	"fmt"
	"goweb/web01_db/utils"
)

//User 结构体
type User struct {
	ID int
	Username string
	Password string
	Email string
}

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

//AddUser2 添加User方法2
func (user *User) AddUser2() error {
	//1.写sql语句
	sqlStr := "insert into users(username,password,email) values(?,?,?)"

	//2.执行
	_, err := utils.Db.Exec(sqlStr, "admin2", "666666", "admin2@sina.com")
	if err != nil {
		fmt.Println("执行出现异常: err2=", err)
		return 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
	}
	//创建User切片
	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"
)

//TestMain函数可以在测试函数执行之前做一些其他操作
func TestMain(m *testing.M)  {
	fmt.Println("测试开始:")
	//通过m.Run()来执行测试函数
	m.Run()
}
func TestUser(t *testing.T)  {
	fmt.Println("开始测试User中相关方法")
	//通过t.Run()来执行子测试函数
	//t.Run("测试添加用户:", testAddUser)
	//t.Run("测试获取用户:", testGetUserByID)
	t.Run("测试获取所有用户:", testGetUsers)
}
//如果函数名不是以Test开头,那么该函数默认不执行,可以将它设置成一个子测试函数
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,
	}
	//调用获取User的方法
	u, _ := user.GetUserById()
	fmt.Println("得到的User信息: ", u)
}

//测试获取所有User
func testGetUsers(t *testing.T)  {
	fmt.Println("测试查询所有记录:")
	user := &User{}
	//调用获取所有User的方法
	us, _ := user.GetUsers()
	//遍历切片
	for k, v := range us {
		fmt.Printf("第%v个用户是%v\n", k + 1, v)
	}
}
  • 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
...
  • Next方法和Scan方法说明

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方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuxingge

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值