1. 标准库database/sql
...大约 5 分钟
1. 使用 SQLite
SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. – SQLite
SQLite 是一款轻量级的,遵守 ACID 事务原则的关系型数据库。SQLite 可以直接嵌入到代码中,不需要像 MySQL、PostgreSQL 需要启动独立的服务才能使用。SQLite 将数据存储在单一的磁盘文件中,方便进行开发和测试。
连接数据库,不存在则自动创建:
sqlite3 gee.db
SQLite version 3.32.2 2021-07-12 15:00:17
Enter ".help" for usage hints.
sqlite> CREATE TABEL User(Name text, Age integer);
Error: near "TABEL": syntax error
sqlite> CREATE TABLE User(Name text, Age integer);
2. database/sql 标准库
Golang 标准库database/sql
提供了和数据库交互的功能。
安装驱动:
go get github.com/mattn/go-sqlite3
package main
import (
"database/sql"
"log"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "gee.db")
check(err)
defer func() {
_ = db.Close()
}()
_, err = db.Exec("DROP TABLE IF EXISTS User;")
check(err)
_, err = db.Exec("CREATE TABLE User(Name text);")
check(err)
res, err := db.Exec("INSERT INTO User(`Name`) values (?), (?)", "Alice", "Ai")
check(err)
affected, _ := res.RowsAffected()
log.Printf("%d rows affected\n", affected)
row := db.QueryRow("SELECT Name FROM User LIMIT 1")
var name string
err = row.Scan(&name)
check(err)
log.Println("name:", name)
}
func check(err error) {
if err != nil {
log.Fatal(err)
}
}
// output
2023/10/11 08:51:05 2 rows affected
2023/10/11 08:51:05 name: Alice
sql.Open
:连接数据库,有两个参数- 驱动名称,import 语句
_ "github.com/mattn/go-sqlite3"
包导入时会注册 sqlite3 的驱动 - 数据库的名称
- 驱动名称,import 语句
Exec()
、Query()
、QueryRow()
接受1或多个入参,第一个入参是 SQL 语句,后面的入参是 SQL 语句中的占位符?
对应的值,占位符一般用来防 SQL 注入QueryRow()
的返回值类型是*sql.Row
,row.Scan()
接受1或多个指针作为参数,可以获取对应列(column)的值,在这个示例中,只有Name
一列,因此传入字符串指针&name
即可获取到查询的结果
3. Log 库
详细的日志能够方便的定位问题,而直接使用标准库的log
功能过于简单(例如:没有日志分级等)。
基于标准库实现自定义的Log:
- 支持日志分级(Info, Error, Disable)
- 不同层级的日志用颜色区分
- 打印代码所在的文件和行号
package log
import (
"io"
"log"
"os"
"sync"
)
var (
errorLog = log.New(os.Stdout, "\033[31m[error]\033[0m ", log.LstdFlags|log.Lshortfile)
infoLog = log.New(os.Stdout, "\033[34m[info ]\033[0m ", log.LstdFlags|log.Lshortfile)
loggers = []*log.Logger{errorLog, infoLog}
mu sync.Mutex
)
var (
Error = errorLog.Println
Errorf = errorLog.Printf
Info = infoLog.Println
Infof = infoLog.Printf
)
\033[31m[error]\033[0m
:\033[31m
设置颜色为 红色,\033[0m
重置颜色error
和infor
的颜色分别为红色和蓝色- 导出四个日志方法
// SetLevel set log level for logger
func SetLevel(level int) {
mu.Lock()
defer mu.Unlock()
for _, logger := range loggers {
logger.SetOutput(os.Stdout)
}
if ErrorLevel < level {
errorLog.SetOutput(io.Discard)
}
if InfoLevel < level {
infoLog.SetOutput(io.Discard)
}
}
- 定义四个常量来控制日志等级
io.Discard
:表示不输出任何内容
4. 核心结构 Session
Session
用于和数据库的交互。
package session
import (
"database/sql"
"geeorm/log"
"strings"
)
type Session struct {
db *sql.DB
sql strings.Builder
sqlVars []any
}
func New(db *sql.DB) *Session {
return &Session{db: db}
}
func (s *Session) Clear() {
s.sql.Reset()
s.sqlVars = nil
}
func (s *Session) DB() *sql.DB {
return s.db
}
func (s *Session) Raw(sql string, vals ...any) *Session {
s.sql.WriteString(sql)
s.sql.WriteString(" ")
s.sqlVars = append(s.sqlVars, vals...)
return s
}
Session
:db
:sql.DB
实例的指针sql
:SQL 语句sqlVars
:SQ占位符参数
Clear
:重置SQL语句和参数Raw
:设置SQL语句和参数
// Exec raw sql with sqlVars
func (s *Session) Exec() (sql.Result, error) {
defer s.Clear()
log.Info(s.sql.String(), s.sqlVars)
res, err := s.DB().Exec(s.sql.String(), s.sqlVars...)
if err != nil {
log.Error(err)
}
return res, err
}
// QueryRow gets a record from db
func (s *Session) QueryRow() *sql.Row {
defer s.Clear()
log.Info(s.sql.String(), s.sqlVars)
return s.DB().QueryRow(s.sql.String(), s.sqlVars...)
}
// QueryRows gets a list of records from db
func (s *Session) QueryRows() (*sql.Rows, error) {
defer s.Clear()
log.Info(s.sql.String(), s.sqlVars)
rows, err := s.DB().Query(s.sql.String(), s.sqlVars...)
if err != nil {
log.Error(err)
}
return rows, err
}
三个函数均为对sql
相关函数的封装,作用有两点:
- 同一进行日志管理,打印执行情况
- 执行之后,清空参数,可以复用
Session
5. 核心结构 Engine
Session
负责和数据库交互,Engine
则负责:
- 交互前的准备工作,例如:连接/测试数据库
- 交互后的首尾工作,例如:关闭数据库连接
并且Engine
是框架的入口。
package geeorm
import (
"database/sql"
"geeorm/log"
"geeorm/session"
)
type Engine struct {
db *sql.DB
}
func NewEngine(driver, source string) (*Engine, error) {
db, err := sql.Open(driver, source)
if err != nil {
log.Error(err)
return nil, err
}
// Send ping to make sure the database connection is alive
if err := db.Ping(); err != nil {
log.Error(err)
return nil, err
}
e := &Engine{db: db}
log.Info("Connect database success")
return e, nil
}
func (engine *Engine) Close() {
if err := engine.db.Close(); err != nil {
log.Error("Failed to close database connection")
return
}
log.Info("Close database connection success")
}
func (engine *Engine) NewSession() *session.Session {
return session.New(engine.db)
}
NewEngine
:- 连接数据库
- 通过
db.Ping()
检测连接 - 返回
Engine
实例
NewSession
:创建新的会话
6. 测试
6.1 TestMain
在单元测试时,有时需要在所有的测试开始前和结束后进行操作,此时就需要定义TestMain(m *testing.M)
函数。
m.Run()
:启动单元测试并返回状态码,此状态码将作为os.Exit
的参数
package session
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
"os"
"testing"
)
var testDB *sql.DB
func TestMain(m *testing.M) {
testDB, _ = sql.Open("sqlite3", "../gee.db")
code := m.Run()
_ = testDB.Close()
os.Exit(code)
}
func newSession() *Session {
return New(testDB)
}
func TestSessionExec(t *testing.T) {
s := newSession()
_, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec()
_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()
res, _ := s.Raw("INSERT INTO User(`Name`) VALUES (?), (?)", "Alice", "Ai").Exec()
if c, err := res.RowsAffected(); err != nil || c != 2 {
t.Fatal("Expected 2, but got", c)
}
}
func TestSession_QueryRows(t *testing.T) {
s := newSession()
_, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec()
_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()
row := s.Raw("SELECT count(*) FROM User").QueryRow()
var count int
if err := row.Scan(&count); err != nil || count != 0 {
t.Fatal("failed to query db", err)
}
}
6.2 cmd_test
将geeorm
作为三方库进行调用:
package main
import (
"fmt"
"geeorm"
_ "github.com/mattn/go-sqlite3"
)
func main() {
engine, _ := geeorm.NewEngine("sqlite3", "gee.db")
defer engine.Close()
s := engine.NewSession()
_, _ = s.Raw("DROP TABLE IF EXISTS User;").Exec()
_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()
_, _ = s.Raw("CREATE TABLE User(Name text);").Exec()
result, _ := s.Raw("INSERT INTO User(`Name`) values (?), (?)", "Tom", "Sam").Exec()
count, _ := result.RowsAffected()
fmt.Printf("Exec success, %d affected\n", count)
}
Reference
Powered by Waline v2.15.2