go语言中的database/sql包定义了对数据库的一系列操作。database/sql/driver包定义了应被数据库驱动实现的接口,这些接口会被sql包使用。但是go语言没有提供任何官方的数据库驱动,所以需要导入第三方的数据库驱动。不过连接数据库之后对数据库操作的大部分代码都使用sql包
获取数据库连接
- 创建一个db.go文件,导入database/sql包以及第三方驱动包
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
- 定义两个全局变量
var (
Db *sql.DB
err error
)
- DB结构体说明
type DB
type DB struct {
// 内含隐藏或非导出字段
}
DB是一个数据库(操作)句柄,代表一个具有零到多个底层连接的连接池。它可以安全的被多个go程同时使用。
sql包会自动创建和释放连接;它也会维护一个闲置连接的连接池。如果数据库具有单连接状态的概念,该状态只有在事务中被观察时才可信。一旦调用了BD.Begin,返回的Tx会绑定到单个连接。当调用事务Tx的Commit或Rollback后,该事务使用的连接会归还到DB的闲置连接池中。连接池的大小可以用SetMaxIdleConns方法控制
- 创建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
增删改操作
- 在连接的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)
);
- 向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方法