go语言学习之数据库编程、gorm

我爱海鲸 2024-07-12 17:27:26 go语言学习

简介go、mysql、gorm

1、go get github.com/go-sql-driver/mysql 下载mysql驱动包

2、相关包导入

1)、示例代码
import (
 "database/sql"
 "fmt"
 _ "github.com/go-sql-driver/mysql"

2)、代码说明
Golang 提供了database/sql包,⽤于对SQL数据库的访问。
它提供了⼀系列接⼝⽅法,⽤于访问关系数据库。它并不会提供数据库特
有的⽅法,那些特有的⽅法交给数据库驱动去实现。
匿名导包——只导⼊包但是不使⽤包内的类型和数据
使⽤匿名导包的⽅式(在包路径前添加下划线 “_” )导⼊MySQL驱动。
匿名导⼊的包与其他⽅式导⼊包⼀样,会让导⼊包编译到可执⾏⽂件
中。
通常来说, 导⼊包后就能调⽤该包中的数据和⽅法。但是对于数据库操
作来说,我们不应该直接使⽤导⼊的驱动包所提供的⽅法,⽽是应该使
⽤ sql.DB对象所提供的统⼀的⽅法。因此在导⼊ mysql 驱动时,使⽤
了匿名导⼊包的⽅式。当导⼊⼀个数据库驱动后,该驱动会⾃⾏初始化
并注册到Golang的database/sql上下⽂中,这样就可以通过
database/sql 包所提供的⽅法来访问数据库了。

3、连接数据库
1)、sql包中的Open()函数
sql.Open()函数原型如下:

func Open(driverName, dataSourceName string) (*DB, error)

driverName: 使⽤的驱动名. 这个名字其实就是数据库驱动注册到
database/sql 时所使⽤的名字.
dataSourceName: 数据库连接信息,这个连接包含了数据库的⽤户名, 密
码, 数据库主机以及需要连接的数据库名等信息.
db, err := sql.Open("mysql", "⽤户名:密码@tcp(IP:端⼝)/数据库?
charset=utf8")
例如:db, err := sql.Open("mysql",
"root:123456@tcp(127.0.0.1p3306)/testdb?charset=utf8")

2)、sql.Open()函数返回sql.DB对象
作为操作数据库的⼊⼝对象sql.DB, 主要为我们提供了两个重要的功能。
sql.DB 通过数据库驱动为我们提供管理底层数据库连接的打开和关闭操
作。
sql.DB 为我们管理数据库连接池。正在使⽤的连接被标记为繁忙,⽤完后
回到连接池等待下次使⽤。所以,如果你没有把连接释放回连接池,会导致
过多连接使系统资源耗尽。
sql.Open()返回的sql.DB对象是协程并发安全的。
sql.DB的设计就是⽤来作为⻓连接使⽤的。不要频繁Open, Close。⽐较
好的做法是,为每个不同的datastore建⼀个DB对象,保持这些对象Open。
如果需要短连接,那么把DB作为参数传⼊function,⽽不要在function中
Open, Close。

4、增删改数据
1、直接调⽤db对象的Exec()⽅法
1 func Open(driverName, dataSourceName string) (*DB, error)
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
通过db.Exec()插⼊数据,通过返回的err可知插⼊失败的原因,通过返
回的结果可以进⼀步查询本次插⼊数据影响的⾏数RowsAffected和最后插
⼊的Id(如果数据库⽀持查询最后插⼊Id).
示例代码:
result, err := db.Exec("INSERT INTO userinfo (username, departname,
created) VALUES (?, ?, ?)","haijin","你好世界","2023-07-13")

2、预编译语句(Prepared Statement)
预编译语句(PreparedStatement)提供了诸多好处, 因此我们在开发中尽
量使⽤它.
PreparedStatement 可以实现⾃定义参数的查询
PreparedStatement 通常来说, ⽐⼿动拼接字符串 SQL 语句⾼效.
PreparedStatement 可以防⽌SQL注⼊攻击
⼀般⽤Prepared Statements和Exec()完成INSERT, UPDATE,
DELETE操作。

3、使⽤db对象的Prepare()⽅法获得预编译对象stmt,然后调⽤Exec()⽅法
 func (db *DB) Prepare(query string) (*Stmt, error)
示例代码:
stmt, err := db.Prepare("INSERT userinfo SET
username=?,departname=?,created=?")
result, err := stmt.Exec("Jackson", "研发部", "2017-10-1")
获取影响数据库的⾏数,可以根据该数值判断是否操作(插⼊、删除或修
改)成功。
count, err := result.RowsAffected()
获得刚刚添加数据的⾃增ID
id, err := result.LastInsertId()

5、查询数据
1、数据库查询的⼀般步骤如下
调⽤ db.Query()⽅法执⾏ SQL 语句, 此⽅法返回⼀个 Rows 作为查询结
果;
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
将rows.Next()⽅法的返回值作为for循环的条件,迭代查询数据;
func (rs *Rows) Next() bool
在循环中,通过 rows.Scan()⽅法读取每⼀⾏数据;
func (rs *Rows) Scan(dest ...interface{}) error
调⽤db.Close()关闭查询。

2、通过QueryRow()⽅法查询单条数据
func (db *DB) QueryRow(query string, args ...interface{}) *Row
示例代码:
var username, departname, created string
err := db.QueryRow("SELECT username,departname,created FROM
user_info WHERE uid=?", 3).Scan(&username, &departname,
&created)

3、查询多⾏数据

stmt, err := db.Prepare("SELECT * FROM user_info WHERE uid<?")
rows, err := stmt.Query(10)
user := new(UserTable)
for rows.Next() {
 err := rows.Scan(&user.Uid, &user.Username, &user.Department,
&user.Created)
if err != nil {
 panic(err)
 continue
 }
 fmt.Println(*user)
}

4、查询数据时需要注意的细节
rows.Scan()⽅法的参数顺序很重要,必须和查询结果的column相对应
(数量和顺序都需要⼀致)。
例如 “SELECT * From user_info where age >=20 AND age <
30” 查询的column 顺序是 “id, name, age” 和插⼊操作顺序相同, 因
此 rows.Scan() 也需要按照此顺序 rows.Scan(&id, &name, &age),
不然会造成数据读取的错位。

因为golang是强类型语⾔,所以查询数据时先定义数据类型。
查询数据库中的数据存在三种可能:存在值、存在零值、未赋值NULL
三种状态,因此可以将待查询的数据类型定义为sql.NullString 、
sql.NullInt64类型等。
可以通过判断Valid值来判断查询到的值是否为赋值状态还是未赋值
NULL状态。
每次db.Query操作后, 都建议调⽤rows.Close()。
因为 db.Query() 会从数据库连接池中获取⼀个连接, 这个底层连接
在结果集(rows)未关闭前会被标记为处于繁忙状态。当遍历读到最后⼀
条记录时,会发⽣⼀个内部EOF错误,⾃动调⽤rows.Close()。但如果
出现异常,提前退出循环,rows不会关闭,连接不会回到连接池中,连
接也不会关闭, 则此连接会⼀直被占⽤。

因此通常使⽤ defer rows.Close() 来确保数据库连接可以正确放回
到连接池中。阅读源码发现rows.Close()操作是幂等操作,⽽⼀个幂等操作的特点
是:其任意多次执⾏所产⽣的影响与⼀次执⾏的影响相同。所以即便对
已关闭的rows再执⾏close()也没关系。

5、完整的代码操作

ddl:

CREATE TABLE user_info (
 uid int(10) NOT NULL AUTO_INCREMENT,
 username varchar(64) DEFAULT NULL,
 departname varchar(64) DEFAULT NULL,
 created date DEFAULT NULL,
 PRIMARY KEY (uid)
) ;
package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
)

// DbConn 定义数据库连接信息
type DbConn struct {
	Dsn string //数据库驱动连接字符串
	Db  *sql.DB
}

// UserTable user_info表的映射对象
type UserTable struct {
	Uid        int
	Username   string
	Department string
	Created    string
}

// PreExecData 2、封装增删改数据的函数,该函数使⽤预编译语句加Exec()⽅法实现增删改数据
func (dbConn *DbConn) PreExecData(sqlString string, args ...interface{}) (count, id int64, err error) {
	stmt, err := dbConn.Db.Prepare(sqlString)
	defer stmt.Close()
	if err != nil {
		panic(err)
		return
	}
	result, err := stmt.Exec(args...)
	if err != nil {
		panic(err)
		return
	}
	if id, err = result.LastInsertId(); err != nil {
		panic(err)
		return
	}
	if count, err = result.RowsAffected(); err != nil {
		panic(err)
		return
	}
	return count, id, nil
}

// ⼀、测试封装的ExecData()函数
func execData(dbConn *DbConn) {
	count, id, err := dbConn.ExecData("INSERT user_info(username , departname ,created) VALUES ('Josh','business group','2023-07-13')")
	//count , err := execData("UPDATE user_info SET created='2024-06-30' WHERE uid=14")
	//count , err := execData("DELETE FROM user_info WHERE uid=10")
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println("受影响⾏数:", count)
		fmt.Println("新添加数据的id:", id)
	}
}

// ⼆、测试封装的PreExecData()函数
func preExecData(dbConn *DbConn) {
	count, id, err := dbConn.PreExecData("INSERT user_info(username , departname ,created) VALUES (?,?,?)", "Jackson", "Education Department", "2017-10-8")
	//count, id, err := PreExecData("Delete from user_info WHERE uid<?", 4)
	//count, id, err := PreExecData("UPDATE user_info set departname=? WHERE departname = ?", "BC Group", "blockchain")
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println("受影响⾏数:", count)
		fmt.Println("新添加数据的id:", id)
	}
}

// ExecData 1、封装增删改数据的函数,该函数直接使⽤DB的Exec()⽅法实现数据操作
func (dbConn *DbConn) ExecData(sqlString string) (count, id int64, err error) {
	result, err := dbConn.Db.Exec(sqlString)
	if err != nil {
		panic(err)
		return
	}
	if id, err = result.LastInsertId(); err != nil {
		panic(err)
		return
	}
	if count, err = result.RowsAffected(); err != nil {
		panic(err)
		return
	}
	return count, id, nil
}

// QueryRowData 3、查询当⾏数据
func (dbConn *DbConn) QueryRowData(sqlString string) (data UserTable) {
	user := new(UserTable)
	err := dbConn.Db.QueryRow(sqlString).Scan(&user.Uid, &user.Username,
		&user.Department, &user.Created)
	if err != nil {
		panic(err)
		return
	}
	return *user
}

// QueryData 4、未使⽤预编译,直接查询多⾏数据
func (dbConn *DbConn) QueryData(sqlString string) (resultSet map[int]UserTable) {
	rows, err := dbConn.Db.Query(sqlString)
	defer rows.Close()
	if err != nil {
		panic(err)
		return
	}
	resultSet = make(map[int]UserTable)
	user := new(UserTable)
	for rows.Next() {
		err := rows.Scan(&user.Uid, &user.Username, &user.Department, &user.Created)
		if err != nil {
			panic(err)
			continue
		}
		resultSet[user.Uid] = *user
	}
	return resultSet
}

// PreQueryData 5、使⽤预编译语句进⾏查询多⾏数据
func (dbConn *DbConn) PreQueryData(sqlString string, args ...interface{}) (resultSet map[int]UserTable) {
	stmt, err := dbConn.Db.Prepare(sqlString)
	defer stmt.Close()
	if err != nil {
		panic(err)
		return
	}
	rows, err := stmt.Query(args...)
	defer rows.Close()
	if err != nil {
		panic(err)
		return
	}
	resultSet = make(map[int]UserTable)
	user := new(UserTable)
	for rows.Next() {
		err := rows.Scan(&user.Uid, &user.Username, &user.Department, &user.Created)
		if err != nil {
			panic(err)
			continue
		}
		resultSet[user.Uid] = *user
	}
	return resultSet
}

// PreQueryData22 ⽆返回值,只打印输出,⽤于测试
func (dbConn *DbConn) PreQueryData22(sqlString string, args ...interface{}) {
	stmt, err := dbConn.Db.Prepare(sqlString)
	defer stmt.Close()
	if err != nil {
		panic(err)
		return
	}
	rows, err := stmt.Query(args...)
	defer rows.Close()
	if err != nil {
		panic(err)
		return
	}
	user := new(UserTable)
	for rows.Next() {
		err := rows.Scan(&user.Uid, &user.Username, &user.Department, &user.Created)
		if err != nil {
			panic(err)
			continue
		}
		fmt.Println(*user)
	}
}

func main() {
	var err error
	dbConn := DbConn{
		Dsn: "root:123456@tcp(192.168.204.130:3306)/go_db?charset=utf8",
	}
	dbConn.Db, err = sql.Open("mysql", dbConn.Dsn)
	if err != nil {
		panic(err)
		return
	}
	defer dbConn.Db.Close()

	//1、测试封装的ExecData()⽅法
	//dbConn.execData(&dbConn)
	execData(&dbConn)

	//2、测试封装的PreExecData()⽅法
	//dbConn.preExecData(&dbConn)
	//3、查询单⾏数据
	//查询最后⼀条数据的信息
	//result := dbConn.QueryRowData("select * from user_info where uid=(select max(uid) from user_info)")
	//fmt.Println(result)

	//4、查询多⾏数据
	//result := dbConn.QueryData("select * from user_info where uid<10")
	//fmt.Println(len(result))
	////遍历查询的结果集
	//for k, v := range result {
	// fmt.Println("uid:", k, v)
	//}

	//5、查询多⾏数据
	//result := dbConn.PreQueryData("select * from user_info where uid<? order by uid desc", 1000)
	//fmt.Println(len(result))
	////遍历查询的结果集
	//for k, v := range result {
	//	fmt.Println("uid:", k, v)
	//}
}

2024-07-12 start

Go 语言 ORM:使用 GORM 操作数据库 - 掘金 (juejin.cn)

end

你好:我的2025