1. 标准库database/sql

Kesa...大约 5 分钟golang

day1-sqlopen in new window

1. 使用 SQLite

SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. – SQLite open in new window

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 的驱动
    • 数据库的名称
  • Exec()Query()QueryRow() 接受1或多个入参,第一个入参是 SQL 语句,后面的入参是 SQL 语句中的占位符 ? 对应的值,占位符一般用来防 SQL 注入
  • QueryRow() 的返回值类型是 *sql.Rowrow.Scan() 接受1或多个指针作为参数,可以获取对应列(column)的值,在这个示例中,只有 Name 一列,因此传入字符串指针 &name 即可获取到查询的结果

3. Log 库

详细的日志能够方便的定位问题,而直接使用标准库的log功能过于简单(例如:没有日志分级等)。

基于标准库实现自定义的Log:

  1. 支持日志分级(Info, Error, Disable)
  2. 不同层级的日志用颜色区分
  3. 打印代码所在的文件和行号
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重置颜色
  • errorinfor的颜色分别为红色和蓝色
  • 导出四个日志方法
// 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:
    • dbsql.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相关函数的封装,作用有两点:

  1. 同一进行日志管理,打印执行情况
  2. 执行之后,清空参数,可以复用Session

5. 核心结构 Engine

Session负责和数据库交互,Engine则负责:

  1. 交互前的准备工作,例如:连接/测试数据库
  2. 交互后的首尾工作,例如:关闭数据库连接

并且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
    1. 连接数据库
    2. 通过db.Ping()检测连接
    3. 返回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)
}

image-20231011130400771
image-20231011130400771

Reference

  1. https://geektutu.com/post/geeorm-day1.htmlopen in new window
上次编辑于:
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.2