我的学习日志 我的学习日志
首页
  • Go

    • Go基础知识
  • Python

    • Python进阶
  • 操作系统
  • 计算机网络
  • MySQL
  • 学习笔记
  • 常用到的算法
其他技术
  • 友情链接
  • 收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

xuqil

一介帆夫
首页
  • Go

    • Go基础知识
  • Python

    • Python进阶
  • 操作系统
  • 计算机网络
  • MySQL
  • 学习笔记
  • 常用到的算法
其他技术
  • 友情链接
  • 收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 环境部署

  • 测试

  • 反射

  • 数据库操作

    • Go 数据库操作
      • 导入 driver
      • 初始化 DB
      • 增删改查
        • 增删改
        • 查
      • 事务 API
        • 事务隔离级别
      • Prepare Statement
      • driver.Valuer和sql.Scanner接口
  • 并发编程

  • 内存管理

  • Go 技巧

  • 《go基础知识》
  • 数据库操作
Xu Qil
2022-12-12
0
目录

Go 数据库操作

# Go 数据库操作

# 导入 driver

import (
	"database/sql"
	_ "github.com/mattn/go-sqlite3"
)
1
2
3
4
  1. 导入database/sql包

  2. 导入数据库驱动

    使用数据库时,除了database/sql包本身,还需要引入想使用的特定数据库驱动。我们这里使用sqlite数据,所以导入了"github.com/mattn/go-sqlite3",但由于没有直接用到该包,所以我们使用_别名来匿名导入驱动。导入时,驱动的初始化函数会调用sql.Register将自己注册在database/sql包的全局变量sql.drivers中,以便以后通过sql.Open访问。

    var driverName = "sqlite3"
    
    func init() {
    	if driverName != "" {
    		sql.Register(driverName, &SQLiteDriver{})
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7

# 初始化 DB

  • Open:使用Open函数初始化DB:

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

    参数:

    • driverName:驱动的名字,例如mysql 、sqlite3
    • dataSourceName:简单理解就是数据库链接信息

    常见错误:忘记匿名引入driver包。

  • OpenDB:一般用于接入一些自定义的驱动,例如将分库分表做成一个驱动。

    func OpenDB(c driver.Connector) *DB
    
    1

执行sql.Open()并未实际建立起到数据库的连接,也不会验证驱动参数。第一个实际的连接会惰性求值,延迟到第一次需要时建立。用户应该通过db.Ping()来检查数据库是否实际可用。

func main() {
	db, err := NewDB()
	if err != nil {
		log.Fatalln(err)
	}
	// 记得 Close DB
	defer func(db *sql.DB) {
		err = db.Close()
		if err != nil {
			log.Fatalln(err)
		}
	}(db)
}

func NewDB() (*sql.DB, error) {
	db, err := sql.Open("sqlite3", "file:test.db?cache=shared&mode=memory")
	if err != nil {
		return nil, err
	}

	// 验证数据库是否可用
	if err = db.Ping(); err != nil {
		return nil, err
	}
	fmt.Println("数据库连接成功")
	return db, err
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 增删改查

# 增删改

  • Exec 或 ExecContext

    func (db *DB) ExecContext(ctx context.Context, query string, args ...any) (Result, error)
    
    func (db *DB) Exec(query string, args ...any) (Result, error) {
    	return db.ExecContext(context.Background(), query, args...)
    }
    
    1
    2
    3
    4
    5
  • 可以用ExecContext来控制超时

    ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
    res, err := db.ExecContext(ctx, sql)
    
    1
    2
  • 同时检查error和sql.Result

注意:注意参数传递,一般的 SQL 都是使用?作为参数占位符。不要把参数拼接进去 SQL 本身,容易引起注入。

增删改的使用:

  • 创建一个表

    func main() {
    	db, err := NewDB()
    	//...
    
    	CreateTable(db)
    }
    
    func CreateTable(db *sql.DB) {
    	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
    
    	// 除了 SELECT 语句,都是使用 ExecContext
    	_, err := db.ExecContext(ctx, `
    CREATE TABLE IF NOT EXISTS user(
        id INTEGER PRIMARY KEY,
        first_name TEXT NOT NULL,
        age INTEGER,
        last_name TEXT NOT NULL
    )
    `)
    	if err != nil {
    		log.Fatalf("建表失败: %v", err)
    	}
    	log.Println("建表成功")
    	cancel()
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    执行结果:

    2022/12/12 21:14:04 建表成功
    
    1
  • 插入数据

    func main() {
    	db, err := NewDB()
    	//...
    
    	CreateTable(db)
        InsertValue(db)
    }
    
    func InsertValue(db *sql.DB) {
    	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
    	// 使用 ? 作为查询的参数的占位符,防止 SQL 注入
    	res, err := db.ExecContext(ctx, "INSERT INTO `user`(`id`, `first_name`, `age`, `last_name`) VALUES (?, ?, ?, ?)",
    		1, "Tom", 18, "Jerry")
    	if err != nil {
    		log.Fatalf("插入数据失败:%v", err)
    	}
    	affected, err := res.RowsAffected()
    	if err != nil {
    		log.Fatalf("获取 受影响行数 失败:%v", err)
    	}
    	log.Println("受影响行数", affected)
    	lastId, err := res.LastInsertId()
    	if err != nil {
    		log.Fatalf("获取 最后插入的ID 失败:%v", err)
    	}
    	log.Println("最后插入的ID", lastId)
    	cancel()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    执行结果:

    2022/12/12 21:14:04 受影响行数 1
    2022/12/12 21:14:04 最后插入的ID 1
    
    1
    2
  • 删除数据

    func main() {
    	//...
    
    	CreateTable(db)
    	InsertValue(db)
    	DeleteValue(db)
    }
    
    func DeleteValue(db *sql.DB) {
    	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
    	res, err := db.ExecContext(ctx, "DELETE FROM `user` WHERE `id` = ?", 1)
    	if err != nil {
    		log.Fatalf("删除数据失败:%v", err)
    	}
    	affected, err := res.RowsAffected()
    	if err != nil {
    		log.Fatalf("获取 受影响行数 失败:%v", err)
    	}
    	log.Println("受影响行数", affected)
    	cancel()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    执行结果:

    2022/12/12 21:16:52 受影响行数 1
    
    1
  • 修改数据

    func main() {
    	//...
    
    	CreateTable(db)
    	InsertValue(db)
    	UpdateValue(db)
    }
    
    func UpdateValue(db *sql.DB) {
    	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
    	res, err := db.ExecContext(ctx, "UPDATE `user` SET first_name = ? WHERE `id` = ?",
    		"Smith", 1)
    	if err != nil {
    		log.Fatalf("更新数据失败:%v", err)
    	}
    	affected, err := res.RowsAffected()
    	if err != nil {
    		log.Fatalf("获取 受影响行数 失败:%v", err)
    	}
    	log.Println("受影响行数", affected)
    	cancel()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    执行结果:

    2022/12/12 23:01:38 受影响行数 1
    
    1

# 查

  • QueryRow 和 QueryRowContext:查询单行数据。预期只有一行,如果没有数据就会报错,如果多于一行则只取第一行。

    func (db *DB) QueryRowContext(ctx context.Context, query string, args ...any) *Row
    
    func (db *DB) QueryRow(query string, args ...any) *Row {
    	return db.QueryRowContext(context.Background(), query, args...)
    }
    
    1
    2
    3
    4
    5
  • Query 和 QueryContext:查询多行数据。没有数据不报错。

    func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)
    
    func (db *DB) Query(query string, args ...any) (*Rows, error) {
    	return db.QueryContext(context.Background(), query, args...)
    }
    
    1
    2
    3
    4
    5

Row 和 Rows

  • QueryRow 和QueryRowContext返回*Row

    • Row:可以理解为只有一行的Rows,而且是必须要有一行。没有的话,在调用Row的Scan的时候会返回sql.ErrNoRow。
    • 如果查询发生错误,错误会延迟到调用Scan()时统一返回,减少了一次错误处理判断。同时QueryRow也避免了手动操作结果集的麻烦。
    • 通过row.Scan获取结果集。
  • Query 和 QueryContext返回*Rows和error。

    • Rows:迭代器设计,需要在使用前调用Next方法。
    • error:每个驱动返回的error都不一样,用错误字符串来判断错误类型并不是明智的做法,更好的方法是对抽象的错误做Type Assertion,利用驱动提供的更具体的信息来处理错误。

下面使用上面创建的user表进行测试:

  1. 创建一个User结构体

    type User struct {
    	ID        int64
    	FirstName string
    	Age       int8
    	LastName  *sql.NullString
    }
    
    func (u User) String() string {
    	return fmt.Sprintf("ID: %d FirstName: %s Age: %d LastName: %s",
    		u.ID, u.FirstName, u.Age, u.LastName.String)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  2. 使用QueryRowContext查询单行结果

    func main() {
    	//...
    
    	CreateTable(db)
    	InsertValue(db)
    	QueryRow(db)
    }
    
    func QueryRow(db *sql.DB) {
    	ctx, cancel := context.WithTimeout(context.Background(), time.Second*1)
    	// 查询一行数据(预期只有一行)
    	row := db.QueryRowContext(ctx,
    		"SELECT `id`, `first_name`, `age`, `last_name` FROM `user` WHERE `id` = ?", 1)
    	if row.Err() != nil {
    		log.Fatalf("查询一行数据失败:%v", row.Err())
    	}
    	u := User{}
    	// 通过 Scan 方法从结果集中获取一行结果
    	// 查询不到,会在 Scan 时返回 sql.ErrNoRows
    	err := row.Scan(&u.ID, &u.FirstName, &u.Age, &u.LastName)
    	if err != nil {
    		log.Fatalf("获取结果集失败:%v", err)
    	}
    	log.Println("结果:", u.String())
    	cancel()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    执行结果:

    2022/12/12 21:43:28 结果: ID: 1 FirstName: Tom Age: 18 LastName: Jerry
    
    1
  3. 尝试查询不存在的数据,将查询条件的ID改为 10

    row := db.QueryRowContext(ctx,
    		"SELECT `id`, `first_name`, `age`, `last_name` FROM `user` WHERE `id` = ?", 10)
    
    1
    2

    执行结果:

    2022/12/12 21:45:30 获取结果集失败:sql: no rows in result set
    
    1
  4. 尝试向不存在的表查询数据

    row := db.QueryRowContext(ctx,
    		"SELECT `id`, `first_name`, `age`, `last_name` FROM `user_not_exists` WHERE `id` = ?", 1)
    
    1
    2

    执行结果:

    2022/12/12 21:47:04 查询一行数据失败:no such table: user_not_exists
    
    1
  5. 使用QueryContext进行批量查询

    func QueryRows(db *sql.DB) {
    	ctx, cancel := context.WithTimeout(context.Background(), time.Second*1)
    	// 批量查询
    	rows, err := db.QueryContext(ctx,
    		"SELECT `id`, `first_name`, `age`, `last_name` FROM `user` WHERE `id` = ?", 1)
    	if err != nil {
    		log.Fatalf("批量查询数据失败:%v", err)
    	}
    	users := make([]User, 0)
    	for rows.Next() { // 标准迭代器设计
    		u := User{}
    		// 这里没有数据不会返回 sql.ErrNoRows
    		// Scan 支持传入的类型:
    		//	*string
    		//	*[]byte
    		//	*int, *int8, *int16, *int32, *int64
    		//	*uint, *uint8, *uint16, *uint32, *uint64
    		//	*bool
    		//	*float32, *float64
    		//	*interface{}
    		//	*RawBytes
    		//	*Rows (cursor value)
    		//	any type implementing Scanner (see Scanner docs)
    		if err = rows.Scan(&u.ID, &u.FirstName, &u.Age, &u.LastName); err != nil {
    			log.Fatalf("获取结果集失败:%v", err)
    		}
    		users = append(users, u)
    		log.Println("结果:", u.String())
    	}
    	log.Println("最终结果:", users, "长度:", len(users))
    	cancel()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    执行结果:

    2022/12/12 22:00:05 结果: ID: 1 FirstName: Tom Age: 18 LastName: Jerry
    2022/12/12 22:00:05 最终结果: [ID: 1 FirstName: Tom Age: 18 LastName: Jerry] 长度: 1
    
    1
    2
  6. 查询结果集为空的数据

    rows, err := db.QueryContext(ctx,
    		"SELECT `id`, `first_name`, `age`, `last_name` FROM `user` WHERE `id` = ?", 10)
    
    1
    2

    执行结果:

    2022/12/12 22:01:26 最终结果: [] 长度: 0
    
    1

# 事务 API

  • Begin和BeginTx开始事务

    • TxOptions设置Isolation字段。大多数时候都不需要设置这个,需要确认自己使用的数据库支持该级别,并且弄清楚效果。

      type TxOptions struct {
      	// Isolation is the transaction isolation level.
      	// If zero, the driver or database's default level is used.
      	Isolation IsolationLevel
      	ReadOnly  bool
      }
      
      1
      2
      3
      4
      5
      6
    func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error) 
    
    func (db *DB) Begin() (*Tx, error) {
    	return db.BeginTx(context.Background(), nil)
    }
    
    1
    2
    3
    4
    5
  • Commit提交事务

  • Rollback回滚事务

已插入数据为例:

func main() {
	//...

	CreateTable(db)
	Tx(db)
}

func Tx(db *sql.DB) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)

	// 开始一个事务
	tx, err := db.BeginTx(ctx, &sql.TxOptions{})
	if err != nil {
		log.Fatalf("事务开始失败:%v", err)
	}

	// 使用 ? 作为查询的参数的占位符,防止 SQL 注入
	res, err := tx.ExecContext(ctx, "INSERT INTO `user`(`id`, `first_name`, `age`, `last_name`) VALUES (?, ?, ?, ?)",
		1, "Tom", 18, "Jerry")

	if err != nil {
		log.Println("事务中插入数据失败,开始回滚")
		// 回滚
		err = tx.Rollback()
		if err != nil {
			log.Printf("事务回滚失败:%v", err)
		}
		cancel()
		return
	}

	affected, err := res.RowsAffected()
	if err != nil {
		log.Fatalf("获取 受影响行数 失败:%v", err)
	}
	log.Println("受影响行数", affected)
	lastId, err := res.LastInsertId()
	if err != nil {
		log.Fatalf("获取 最后插入的ID 失败:%v", err)
	}
	log.Println("最后插入的ID", lastId)

	// 提交事务
	err = tx.Commit()
	if err != nil {
		log.Fatalf("提交事务失败:%v", err)
	}
	cancel()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

执行结果:

2022/12/12 22:14:18 受影响行数 1
2022/12/12 22:14:18 最后插入的ID 1
1
2

# 事务隔离级别

// IsolationLevel is the transaction isolation level used in TxOptions.
type IsolationLevel int

// Various isolation levels that drivers may support in BeginTx.
// If a driver does not support a given isolation level an error may be returned.
//
// See https://en.wikipedia.org/wiki/Isolation_(database_systems)#Isolation_levels.
const (
	LevelDefault IsolationLevel = iota
	LevelReadUncommitted
	LevelReadCommitted
	LevelWriteCommitted
	LevelRepeatableRead
	LevelSnapshot
	LevelSerializable
	LevelLinearizable
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

MySQL 的事务隔离级别

  • 序列化(SERIALIZABLE)

    • 上一个事务与下一个事务是严格顺序执行的(最严格)。
  • 可重复读(REPEATABLE READ)

    • A 事务无法看到 B 事务的修改。即 A 事务内同一个 SELECT 语句执行的结果总是相同的。
  • 已提交读(READ COMMITTED)

    • A 事务无法看到 B 事务未提交的修改,但是可以看到已提交的修改
  • 未提交读(READ UNCOMMITTED)

    • A 事务能看到 B 事务未提交的修改。

MySQL 默认的级别是可重复读。

事务的问题:

  • 脏读:A 事务能看到 B 事务未提交的修改。
    • 隔离级别:未提交读
  • 不可重复读:A 事务内同一个 SQL 读到了不同的数据。
    • 隔离级别:未提交读和已提交读
  • 幻读:A 事务内读到了 B 事务新插入的数据。
    • 隔离级别:未提交读、已提交读和可重复读(理论上)。注意 InnoDB 引擎的可重复读并不会引起幻读。
隔离级别 脏读 不可重复读 幻读
未提交读 Yes Yes Yes
已提交读 No Yes Yes
可重复读 No No Yes
序列化 No No No

# Prepare Statement

Prepare Statement 表示准备一个需要多次使用的语句,供后续执行用。Prepare Statement 的生命周期和整个应用的生命周期一致。

在查询前进行准备是Go语言中的惯用法,多次使用的查询语句应当进行准备(Prepare)。准备查询的结果是一个准备好的语句(prepared statement),语句中可以包含执行时所需参数的占位符(即绑定值)。准备查询比拼字符串的方式好很多,它可以转义参数,避免SQL注入。同时,准备查询对于一些数据库也省去了解析和生成执行计划的开销,有利于性能。

func main() {
	//...

	CreateTable(db)
	InsertValue(db)
	PrepareStat(db)
}

func PrepareStat(db *sql.DB) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	// 提前准备好 SQL 语句和占位符
	stmt, err := db.PrepareContext(ctx, "SELECT `id`, `first_name`, `age`, `last_name` FROM `user` WHERE `id`=?")
	if err != nil {
		log.Fatalf("Prepare error: %v", err)
	}
	// 不用 stmt 时需要关闭
	defer func(stmt *sql.Stmt) {
		err = stmt.Close()
		if err != nil {
			log.Fatalln(err)
		}
	}(stmt)

	// 执行查询语句,id = 1
	rows, err := stmt.QueryContext(ctx, 1)
	if err != nil {
		log.Fatalf("查询失败:%v", err)
	}
	for rows.Next() { // 标准迭代器设计
		u := User{}
		if err = rows.Scan(&u.ID, &u.FirstName, &u.Age, &u.LastName); err != nil {
			log.Fatalf("获取结果集失败:%v", err)
		}
		log.Println("结果:", u.String())
	}
	cancel()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

执行结果:

2022/12/12 22:33:28 结果: ID: 1 FirstName: Tom Age: 18 LastName: Jerry
1

# driver.Valuer和sql.Scanner接口

场景:SQL 默认支持的类型就是基础类型。如果我们使用自定义类型,比如支持json类型,应该怎么处理?

  • driver.Valuer:读取,实现该接口的类型可以作为查询参数使用(Go类型到数据库类型)
  • sql.Scanner:写入,实现该接口的类型可以作为接收器用于Scan方法(数据库类型到Go类型)

自定义类型一般是实现这两个接口。

以自定义json类型数据为例。

  1. 创建一个json列(数据库的列类型为json)

    json.go

    type JsonColumn[T any] struct {
    	Val T
    
    	// NULL 的问题 (sql.NullString)
    	Valid bool
    }
    
    1
    2
    3
    4
    5
    6

    可参考sql.NullString的定义:

    type NullString struct {
       String string
       Valid  bool // Valid is true if String is not NULL
    }
    
    // Scan implements the Scanner interface.
    func (ns *NullString) Scan(value any) error {
       if value == nil {
          ns.String, ns.Valid = "", false
          return nil
       }
       ns.Valid = true
       return convertAssign(&ns.String, value)
    }
    
    // Value implements the driver Valuer interface.
    func (ns NullString) Value() (driver.Value, error) {
       if !ns.Valid {
          return nil, nil
       }
       return ns.String, nil
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
  2. 实现driver.Value和sql.Scan

    json.go

    // Value 实现 driver.Valuer
    // Go 类型到数据库类型(json)
    func (j JsonColumn[T]) Value() (driver.Value, error) {
    	// NULL
    	if !j.Valid {
    		return nil, nil
    	}
    	return json.Marshal(j.Val)
    }
    
    // Scan 实现 sql.Scanner
    // 数据库类型(json)到 Go 类型
    func (j *JsonColumn[T]) Scan(src any) error {
        //  Scan 默认支持的类型   
    	//    int64
    	//    float64
    	//    bool
    	//    []byte
    	//    string
    	//    time.Time
    	//    nil - for NULL values
    
    	var bs []byte
    	switch data := src.(type) {
    	case string:
    		// 可以考虑额外处理空字符串
    		bs = []byte(data)
    	case []byte:
    		// 可以考虑额外处理 []byte{}
    		bs = data
    	case nil:
    		// 说明数据库存的就是 NULL
    		return nil
    	default:
    		return errors.New("不支持类型")
    	}
    
    	err := json.Unmarshal(bs, &j.Val)
    	if err == nil {
    		j.Valid = true
    	}
    	return err
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
  3. 创建JsonColumn的测试

    json_test.go

    package sql_column
    
    import (
    	"context"
    	"database/sql"
    	"fmt"
    	"github.com/stretchr/testify/assert"
    	"github.com/stretchr/testify/require"
    	"log"
    	"testing"
    	"time"
    )
    
    // 测试 Value。Go 类型到数据库类型
    func TestJsonColumn_Value(t *testing.T) {
    	js := JsonColumn[User]{Valid: true, Val: User{Name: "Tom"}}
    	value, err := js.Value()
    	assert.Nil(t, err)
    	assert.Equal(t, []byte(`{"Name":"Tom"}`), value)
    	js = JsonColumn[User]{}
    	value, err = js.Value()
    	assert.Nil(t, err)
    	assert.Nil(t, value)
    }
    
    // 测试 Scan。数据库类型到 Go 类型
    func TestJsonColumn_Scan(t *testing.T) {
    	testCases := []struct {
    		name    string
    		src     any
    		wantErr error
    		wantVal User
    		valid   bool
    	}{
    		{
    			name: "nil",
    		},
    		{
    			name:    "string",
    			src:     `{"Name":"Tom"}`,
    			wantVal: User{Name: "Tom"},
    			valid:   true,
    		},
    		{
    			name:    "bytes",
    			src:     []byte(`{"Name":"Tom"}`),
    			wantVal: User{Name: "Tom"},
    			valid:   true,
    		},
    	}
    	for _, tc := range testCases {
    		t.Run(tc.name, func(t *testing.T) {
    			js := &JsonColumn[User]{}
    			err := js.Scan(tc.src)
    			assert.Equal(t, tc.wantErr, err)
    			if err != nil {
    				return
    			}
    			assert.Equal(t, tc.wantVal, js.Val)
    			assert.Equal(t, tc.valid, js.Valid)
    		})
    	}
    }
    
    // 测试 Scan。测试可以转为 JSON 的 Go 类型
    func TestJsonColumn_ScanTypes(t *testing.T) {
    	jsSlice := JsonColumn[[]string]{}
    	err := jsSlice.Scan(`["a", "b", "c"]`)
    	assert.Nil(t, err)
    	assert.Equal(t, []string{"a", "b", "c"}, jsSlice.Val)
    	val, err := jsSlice.Value()
    	assert.Nil(t, err)
    	assert.Equal(t, []byte(`["a","b","c"]`), val)
    
    	jsMap := JsonColumn[map[string]string]{}
    	err = jsMap.Scan(`{"a":"a value"}`)
    	assert.Nil(t, err)
    	val, err = jsMap.Value()
    	assert.Nil(t, err)
    	assert.Equal(t, []byte(`{"a":"a value"}`), val)
    }
    
    type User struct {
    	Name string
    }
    
    // JsonColumn Value 方法的例子
    func ExampleJsonColumn_Value() {
    	js := JsonColumn[User]{Valid: true, Val: User{Name: "Tom"}}
    	value, err := js.Value()
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Print(string(value.([]byte)))
    	// Output:
    	// {"Name":"Tom"}
    }
    
    // JsonColumn Scan 方法的例子
    func ExampleJsonColumn_Scan() {
    	js := JsonColumn[User]{}
    	err := js.Scan(`{"Name":"Tom"}`)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Print(js.Val)
    	// Output:
    	// {Tom}
    }
    
    type UserJson struct {
    	ID   int
    	Name string
    }
    
    // 测试 JsonColumn 到真实数据库的 CRUD
    func TestJsonColumn_CRUD(t *testing.T) {
    	db, err := sql.Open("sqlite3", "file:test.db?cache=shared&mode=memory")
    	require.NoError(t, err)
    	defer db.Close()
    	db.Ping()
    
    	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    	// 创建一个表,其中 name 字段的类型为 json
    	_, err = db.ExecContext(ctx, `
    CREATE TABLE IF NOT EXISTS user_json(
        id INTEGER PRIMARY KEY,
        name JSON
    )
    `)
    	//	完成了建表
    	require.NoError(t, err)
    
    	js := JsonColumn[UserJson]{Valid: true, Val: UserJson{ID: 1, Name: "Tom"}}
    	// 将 JsonColumn 插入数据库
    	res, err := db.ExecContext(ctx, "INSERT INTO `user_json`(`id`, `name`) VALUES (?, ?)",
    		js.Val.ID, js)
    	require.NoError(t, err)
    	affected, err := res.RowsAffected()
    	require.NoError(t, err)
    	log.Println("受影响行数", affected)
    	lastId, err := res.LastInsertId()
    	log.Println(affected)
    	log.Println("最后插入的ID", lastId)
    
    	// 查询一行数据(预期只有一行)
    	row := db.QueryRowContext(ctx,
    		"SELECT `name` FROM `user_json` WHERE `id` = ?", 1)
    	require.NoError(t, row.Err())
    	js2 := JsonColumn[UserJson]{}
    	// 主要要用指针
    	var data string
    	err = row.Scan(&data)
    	require.NoError(t, err)
    	err = js2.Scan(data)
    	require.NoError(t, err)
    	assert.Equal(t, `{"ID":1,"Name":"Tom"}`, data)
    	log.Println(data) // {"Name":"Tom"}
    	assert.Equal(t, UserJson{ID: 1, Name: "Tom"}, js2.Val)
    	log.Println(js2.Val)
    	cancel()
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163

    执行结果:

    # go test -v --run="Json"
    === RUN   TestJsonColumn_Value
    --- PASS: TestJsonColumn_Value (0.00s)
    === RUN   TestJsonColumn_Scan
    === RUN   TestJsonColumn_Scan/nil     
    === RUN   TestJsonColumn_Scan/string
    === RUN   TestJsonColumn_Scan/bytes
    --- PASS: TestJsonColumn_ScanTypes (0.00s)
    === RUN   TestJsonColumn_CRUD
    2022/12/12 23:20:03 受影响行数 1
    2022/12/12 23:20:03 1
    2022/12/12 23:20:03 最后插入的ID 1
    2022/12/12 23:20:03 {"ID":1,"Name":"Tom"}
    2022/12/12 23:20:03 {1 Tom}
    --- PASS: TestJsonColumn_CRUD (0.00s)
    === RUN   ExampleJsonColumn_Value
    --- PASS: ExampleJsonColumn_Value (0.00s)
    === RUN   ExampleJsonColumn_Scan
    --- PASS: ExampleJsonColumn_Scan (0.00s)
    PASS
    ok      leanring-go/orm/sql_demo        0.003s
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

评论

#Go 数据库操作
上次更新: 2022/12/28, 17:24:41
反射
解密 go Context 包

← 反射 解密 go Context 包→

最近更新
01
Golang 逃逸分析
03-22
02
深入理解 channel
03-04
03
深入理解 sync.WaitGroup
03-01
更多文章>
Theme by Vdoing | Copyright © 2018-2023 FeelingLife | 粤ICP备2022093535号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式