【go】总结笔记05 - 操作数据库


使用 Go 操作 MySQL 等数据库,一般有两种方式:

  • database/sql 接口,直接在代码里硬编码 sql 语句;
  • GORM,以对象关系映射的方式在抽象地操作数据库

database/sql 接口

安装

go get github.com/go-sql-driver/mysql

知识点:Go 语言中,为了使用导入的程序包,必须首先对其进行初始化。初始化始终在单个线程中执行,并且以程序包依赖关系的顺序执行。初始化每个包后,会优先自动执行 init() 函数,并且执行优先级高于主函数的执行优先级

连接数据库

package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/go-sql-driver/mysql"

	"github.com/gorilla/mux"
	// 匿名导入,因为引入的是驱动,操作数据库时我们使用的是 sql 库里的方法,而不会具体使用到 go-sql-driver/mysql 包里的方法,当有未使用的包被引入时,Go 编译器会停止编译。为了让编译器能正常运行,需要使用 匿名导入 来加载
	_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB

func main() {
	// 初始化数据库
	initDB()
}

func initDB() {
	var err error
	config := mysql.Config{
		User:                 "root",
		Passwd:               "000000",
		Addr:                 "127.0.0.1:3306",
		Net:                  "tcp",
		DBName:               "go-blog",
		AllowNativePasswords: true,
	}
	// 数据源信息,用于定义如何连接数据库
	dsn := config.FormatDSN()
	// 准备连接池
	db, err = sql.Open("mysql", dsn)
	checkError(err)

	// 设置连接池最大打开数据库连接数,<= 0 表示无限制,默认为 0
	db.SetMaxOpenConns(25)
	// 设置连接池最大空闲数据库连接数,<= 0 表示不设置空闲连接数,默认为 2
	db.SetMaxIdleConns(25)
	// 设置每个链接的过期时间
	db.SetConnMaxLifetime(5 * time.Minute)

	// 尝试连接,失败会报错
	err = db.Ping()
	checkError(err)
}

创建表

func main() {
	// 初始化数据库
	initDB()
	// 创建数据表
	createTable()
}

func createTable() {
	sql := `CREATE TABLE IF NOT EXISTS articles(
    id bigint(20) PRIMARY KEY AUTO_INCREMENT NOT NULL,
    title varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
    body longtext COLLATE utf8mb4_unicode_ci
); `

	// 处理没有返回结果集的 SQL 语句,insert update delete
	_, err := db.Exec(sql)
	checkError(err)
}

db.Exec

  • 单参数(即只传query和一个参数)为纯文本模式,不使用 Prepare,只发送一条 SQL 查询
// 返回 Result 和 error
func (db *DB) Exec(query string, args ...interface{}) (Result, error) {
	// context.Background() 是默认的上下文,这是一个空的 context ,我们无法对其进行取消、赋值、设置 deadline 等操作
	return db.ExecContext(context.Background(), query, args...)
}

// 可以通过Result拿到id和受影响行数
type Result interface {
	LastInsertId() (int64, error)
	RowsAffected() (int64, error)
}

插入表

func main() {
	// 初始化数据库
	initDB()
	// 创建数据表
	createTable()
	// 插入数据
	id,err := saveArticleToDB("标题", "内容")
	...
}
func saveArticleToDB(title string, body string) (int64, error) {
	// 变量初始化
	var (
		id   int64
		err  error
		res  sql.Result
		stmt *sql.Stmt
	)

	// 1.获取一个prepare声明语句
	stmt, err = db.Prepare("INSERT INTO articles (title, body) VALUES(?,?)")
	if err != nil {
		return 0, err
	}
	// 2.函数运行结束后关闭此语句,防止占用 SQL 连接
	defer stmt.Close()

	// 3.执行请求
	res, err = stmt.Exec(title, body)
	if err != nil {
		return 0, err
	}

	// 4.插入成功
	if id, err = res.LastInsertId(); id > 0 {
		return id, nil
	}

	return 0, err
}

查询单条数据

article := Article{}
query := "SELECT * FROM articles WHERE id=?"
err := db.QueryRow(query, id).Scan(&article.ID, &article.Title, &article.Body)

// 3.如果出现错误
if err != nil {
	if err == sql.ErrNoRows {
		// 数据未找到
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprint(w, "404")
	} else {
		// 数据库错误
		checkError(err)
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprint(w, "服务器出错")
	}
} else {
	fmt.Fprint(w, article.Title)
}

db.QueryRow

func (db *DB) QueryRow(query string, args ...interface{}) *Row {
	return db.QueryRowContext(context.Background(), query, args...)
}

// 扫描结构体
func (r *Row) Scan(dest ...interface{}) error {
	if r.err != nil {
		return r.err
	}
	defer r.rows.Close()
	for _, dp := range dest {
		if _, ok := dp.(*RawBytes); ok {
			return errors.New("sql: RawBytes isn't allowed on Row.Scan")
		}
	}

	if !r.rows.Next() {
		if err := r.rows.Err(); err != nil {
			return err
		}
		return ErrNoRows
	}
	err := r.rows.Scan(dest...)
	if err != nil {
		return err
	}
	// Make sure the query can be processed to completion with no errors.
	return r.rows.Close()
}

查询结果集

// 执行查询语句,返回一个结果集
rows, err := db.Query("SELECT * FROM articles")
checkError(err)

// rows 没有自动关闭
defer rows.Close()
var articles []Article

// 读取
// 游标,下一行
for rows.Next() {
	var article Article
	// 扫描每一行的结果并赋值到一个对象中
	err := rows.Scan(&article.ID, &article.Title, &article.Body)
	checkError(err)

	// 加入数组
	articles = append(articles, article)
}

// 检测遍历时是否出错
// 错误集
err = rows.Err()
checkError(err)
  • Query读取多条数据,QueryRow()读取单条数据
  • Query支持单一参数的纯文本模式,以及多个参数的 Prepare 模式

db.Query

func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
	return db.QueryContext(context.Background(), query, args...)
}

删除

rs, err := db.Exec("DELETE FROM articles WHERE id = " + strconv.FormatInt(a.ID, 10))
if err != nil {
	return 0, err
}
if n, _ := rs.RowsAffected(); n > 0 {
	return n, nil
}

事务

func (s Service) DoSomething() (err error) {
    // 1. 创建事务
    tx, err := s.db.Begin()
    if err != nil {
        return
    }
    // 2. 如果请求失败,就回滚所有 SQL 操作,否则提交
    //    defer 会在当前方法的最后执行
    defer func() {
        if err != nil {
            tx.Rollback()
            return err
        }
        err = tx.Commit()
    }()

    // 3. 执行各种请求
    if _, err = tx.Exec(...); err != nil {
        return err
    }
    if _, err = tx.Exec(...); err != nil {
        return err
    }
    // ...
    return nil
}
  • 开启事务
  • 返回一个 sql.Tx 结构体
func (db *DB) Begin() (*Tx, error)
func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)

sql.Tx

func (tx *Tx) Exec(query string, args ...interface{}) (Result, error)
func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error)
func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
func (tx *Tx) QueryRow(query string, args ...interface{}) *Row
func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row

// 预编译 Prepare
func (tx *Tx) Stmt(stmt *Stmt) *Stmt
func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt
func (tx *Tx) Prepare(query string) (*Stmt, error)
func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error)

// 提交和回滚
func (tx *Tx) Commit() error
func (tx *Tx) Rollback() error

Prepare 和 Stmt

  • sql.DB.Prepare() 方法会返回一個 sql.Stmt 对象
  • db.Exec db.Query db.QueryRow,调用在传参一个以上时,底层皆会使用 Prepare 来发送请求
stmt.Exec()
stmt.Query()
stmt.QueryRow()
stmt.Close()

sql.Row

func (rs *Rows) Close() error                            //关闭结果集
func (rs *Rows) ColumnTypes() ([]*ColumnType, error)    //返回数据表的列类型
func (rs *Rows) Columns() ([]string, error)             //返回数据表列的名称
func (rs *Rows) Err() error                      // 错误集
func (rs *Rows) Next() bool                      // 游标,下一行
func (rs *Rows) Scan(dest ...interface{}) error  // 扫描结构体
func (rs *Rows) NextResultSet() bool            

Context 上下文

  • 所有的请求方法底层都是用其上下文版本的方法调用,且传入默认的上下文
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
func (db *DB) QueryRow(query string, args ...interface{}) *Row
func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row

题外话:新手入门,多有不解,理解有偏差请不吝指教

GORM

安装

$ go get -u gorm.io/gorm
// 在安装 GORM 的同时,我们还需要安装 GORM 的 MySQL 的数据库驱动
$ go get -u gorm.io/driver/mysql

通过id获取文章

func Get(idStr string) (Article, error) {
	var article Article
	id := types.StringToUint64(idStr)
	// First() 是 gorm.DB 提供的用以从结果集中获取第一条数据的查询方法,第二个参数可以传参整型或者字符串 ID,但是传字符串会有 SQL 注入的风险
	// .Error 是 GORM 的错误处理机制
	// 找不到记录时,GORM 会返回 ErrRecordNotFound 错误
	if err := model.DB.First(&article, id).Error; err != nil {
		return article, err
	}
	return article, nil
}

获取所有文章

func GetAll() ([]Article, error) {
	var articles []Article
	if err := model.DB.Find(&articles).Error; err != nil {
		return articles, err
	}
	return articles, nil
}

保存文章到数据库

func (article *Article) Create() (err error) {
	if err := model.DB.Create(&article).Error; err != nil {
		logger.LogError(err)
		return err
	}
	return nil
}

更新文章

func (article *Article) Update() (rowsAffected int64, err error) {
	result := model.DB.Save(&article)
	if err := result.Error; err != nil {
		logger.LogError(err)
		return 0, err
	}
	return result.RowsAffected, nil
}

删除文章

func (article *Article) Delete() (rowsAffected int64, err error) {
	result := model.DB.Delete(&article)
	if err = result.Error; err != nil {
		logger.LogError(err)
		return 0, err
	}
	return result.RowsAffected, nil
}

疑问

在使用的过程中对以下两种写法有些混淆,加*和不加的用法感觉很类似,这两种有什么区别呢?

func (article *Article) Delete(){}
func (article Article) Delete(){}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值