一、数据库和SQL:
-
数据库(Database, DB)是将大量数据保存起来,通过计算机加工而成的可以进行高效访问的数据集合。 关键词:高效访问,数据集合
-
数据库管理系统(Database Management System,DBMS):用来管理数据库的计算机系统
-
SQL(Structured Query Language,结构化查询语言)是用来操作关系数据库的语言。
二、使用database/sql建立连接、使用
func Open(driverName, dataSourceName string) (*DB, error)
!使用一个DSN创建连接
1. 创建sql连接:db, err := sql.Open("mysql", "user:password@/dbname")
db, err := sql.Open("mysql", "root:xxxx@tcp(127.0.0.1:3306)/test")
需要包:
_ "github.com/go-sql-driver/mysql"
使用:
db, err := sql.Open("mysql", "user:password@/dbname")
注意,根据解释
When you use sql.Open()
you get a handle for a database. The database/sql package manages a pool of connections in the background, and doesn't open any connections until you need them. Therefore sql.Open()
doesn't directly open a connection. As a result, sql.Open()
does not return an error, if the server isn't available or the connection data (Username, Password) isn't correct. If you want to check this before making queries (e.g at application startup) you can use db.Ping()
.
sql.Open()返回一个句柄,在后台管理一个连接池。当用户密码错误是会设置err。
关于db的设置【重要!!!】:
db.SetConnMaxLifetime()设置连接过期时间,默认值是0,也就是不限制。例如
db.SetConnMaxLifetime(time.Second * 500) //设置连接超时500秒
db.SetMaxOpenConns()用于设置最大打开的连接数,默认值为0,表示不限制。例如
db.SetMaxOpenConns(10)
这样如果goroutine同时使用一个db句柄访问数据库,数据库会限制打开的连接数量,也就是说如果允许最大两个连接数,那么有十个goroutine,也只能用两个连接去访问数据库,详细看这篇博文关于连接数的测试www.jb51.net/article/238…db.SetMaxIdleConns()设置闲置的连接数,一般是建议和db.SetMaxOpenConns()设置一样的值。
用db.Ping()查看是否处于连接状态,如果这时候连接关闭了,会设置err
err = db.Ping()
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
2. 数据查询:func (db *DB) Query(query string, args ...any) (*Rows, error)
rows, err := db.Query("SELECT * FROM runoob_tbl")
这个命令就是可以直接用mysql后的结果,返回是一个rows,可以理解为mysql查询之后返回的内容。
runnob_tbl是菜鸟教程的MYSQL的一个例子,数据结构如下
+-----------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+------------------+------+-----+---------+----------------+
| runoob_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| runoob_title | varchar(100) | NO | | NULL | |
| runoob_author | varchar(40) | NO | | NULL | |
| submission_date | date | YES | | NULL | |
+-----------------+------------------+------+-----+---------+----------------+
使用SELECT * FROM runoob_tbl;
mysql> SELECT * FROM runoob_tbl;
+-----------+--------------+---------------+-----------------+
| runoob_id | runoob_title | runoob_author | submission_date |
+-----------+--------------+---------------+-----------------+
| 1 | 学习 PHP | 菜鸟教程 | 2022-05-12 |
| 2 | 学习 MySQL | 菜鸟教程 | 2022-05-12 |
+-----------+--------------+---------------+-----------------+
2 rows in set (0.00 sec)
rows, err := db.Query("SELECT * FROM runoob_tbl") 返回的rows可以理解为上边返回的那个表,
使用for rows.Next()对行循环,func (rs *Rows) Next() bool
,如果有下一行就返回true,否则返回false。
使用rows.Scan()对数据采集,把内容复制下来,但是必须保证Scan的参数和rows中的列数是一致的。
例如:rows.Scan(&runoob_id, &runoob_title, &runoob_author, &submission_date)
返回的rows有四列,所以需要四个变量来保存。
for rows.Next() {
var (
runoob_id int64
runoob_title string
runoob_author string
submission_date string
)
if err := rows.Scan(&runoob_id, &runoob_title, &runoob_author, &submission_date); err != nil {
log.Fatal(err)
}
log.Printf("id %d title is %s suthor %s data %s \n", runoob_id, runoob_title, runoob_author, submission_date)
3. 使用Prepare声明:prepare可以带有占位符的mysql语句,之后我们可以通过传入参数来代替占位符,达到构建新语句的方式。
例如查询语句,如果用prepare重写,可以这样
//普通查询语句,如果where后边的参数需要修改,那么就要重写一条
rows, err := db.Query("SELECT * FROM runoob_tbl WHERE runoob_author = "菜鸟教程"")
//使用prepare,其中?是占位符
stmtOut, err := db.Prepare(`SELECT * FROM runoob_tbl WHERE runoob_author = ?`)
rows, err := stmtOut.Query("菜鸟教程");
prepare语句也可以用于插入和删除,下边的例子来自github.com/go-sql-driv…
//插入
stmtIns, err := db.Prepare("INSERT INTO squareNum VALUES( ?, ? )") // ? = placeholder
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
defer stmtIns.Close() // Close the statement when we leave main() / the program terminates
// Insert square numbers for 0-24 in the database
for i := 0; i < 25; i++ {
_, err = stmtIns.Exec(i, (i * i)) // Insert tuples (i, i^2)
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
}
//删除
stmtDel, err := db.Prepare(`Delete FROM runoob_tbl WHERE runoob_author like ?;`) // ? = placeholder
if err != nil {
log.Fatal(err)
}
defer stmtDel.Close()
stmtDel.Exec(`%author%`)
4. 空值处理
可以用一个字符串或者[]byte来接收空值,这样空值NULL会被当成空字符串
例子来自Examples · go-sql-driver/mysql Wiki · GitHub
[...]
var col1, col2 []byte
for rows.Next() {
// Scan the value to []byte
err = rows.Scan(&col1, &col2)
if err != nil {
panic(err.Error()) // Just for example purpose. You should use proper error handling instead of panic
}
// Use the string value
fmt.Println(string(col1), string(col2))
}
5. 使用RawBytes构建多个变量读取
rows.Columns()获取列信息,返回一个[]string,里边是列名
rows, err := db.Query("SELECT * FROM table")
//获取列信息
columns, err := rows.Columns()
//利用sql.RawBytes构建变量,这一点类似于
//var (
// runoob_id int64
// runoob_title string
// runoob_author string
// submission_date string
//)
values := make([]sql.RawBytes, len(columns))
//构建一组interface后来获取信息,并把scanArgs和values进行绑定,让value能够接收到信息
scanArgs := make([]interface{}, len(values))
for i := range values {
scanArgs[i] = &values[i]
}
for rows.Next() {
// get RawBytes from data,读取信息
err = rows.Scan(scanArgs...)
var value string
for i, col := range values {
// Here we can check if the value is nil (NULL value)
if col == nil {
value = "NULL"
} else {
value = string(col)
}
fmt.Println(columns[i], ": ", value)
}
}
完整的例子见Examples · go-sql-driver/mysql Wiki · GitHub
三、DSN:data source name数据源名称
sql.Open()需要一个DSN字符串
1. DSN是什么?
DSN是一个字符串,用于描述数据源的连接。
一个DSN通常包括但不局限于:
- 数据源名称
- 数据源位置
- 可以访问数据源的数据库驱动程序的名称
- 用于数据访问的用户id
- 用于数据访问的用户密码
客户机机器的系统管理员通常为每个相关数据源创建单独的DSN。
2.为什么需要DSN?
设置DSN的目的是便于应用程序访问数据,只要为某个数据库设置了相应的DSN,应用程序就不必理会该数据库存储的位置和驱动程序,可以按DSN直接访问数据库。 没有DSN的连接需要在程序中指定所有必要的信息。
3.怎么设置DSN?
格式:
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
获取包go get -u github.com/go-sql-driver/mysql
导入包imoport "github.com/go-sql-driver/mysql"
构建DSN结构
config := mysql.Config{
User: "homestead",
Passwd: "secret",
Addr: "127.0.0.1:33060",
Net: "tcp",
DBName: "goblog",
AllowNativePasswords: true,
}
使用config.FormatDSN()创建DSN字符串,并查看输出样式,可以看出跟格式是对应的
s := config.FormatDSN()
fmt.Printf("%v\n", s)
//输出为:
//homestead:secret@tcp(127.0.0.1:33060)/goblog?checkConnLiveness=false&maxAllowedPacket=0
4. 参数
参数很多,参考 github.com/go-sql-driv…
可以构造上边类似的结构体,但是要注意参数的首字母要大写
四、对 GORM 有个简单的认知
1.GROM是什么以及为什么需要GROM:
首先,ROM是什么,Object-Relationl Mapping,即对象关系映射,这里的Relationl指的是关系型数据库。借用CSDN的说法,它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了。 而GROM就是用golang写的orm库。
2. 安装
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
3. 使用:
import (
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)