Просто расскажите о тех вещах, которые касаются программирования баз данных Golang.

Go

Оригинальный автор, публичный аккаунт [программист чтение], прошу обратить внимание на паблик-аккаунт, просьба указывать источник перепечатываемой статьи.

Следует сказать, что программирование баз данных — это базовый функциональный модуль, предоставляемый любым языком программирования, будь то встроенная поддержка языка программирования или реализованная через внешние библиотеки; конечно, API-интерфейсы программирования баз данных, предоставляемые разными языками программирования не одинаковы, и базы данных, которые необходимо поддерживать, также разнообразны, например, часто используемыеMySQL,SQLServer,Postgresбаза данных.

Оставив в стороне другие языки программирования, в этой статье мы поговорим о программировании базы данных на языке Go и узнаем, как использовать стандартную библиотеку, предоставляемую языком Go, для написания кода базы данных общего назначения.

Подключение к базе данных и драйвер

база данных/sql и база данных/sql/драйвер

стандартная библиотекаdatabase/sqlЭто уровень абстракции операций с базами данных языка Go, и конкретная реализация логики операций с базами данных выполняется различными сторонними пакетами, в то время как стандартная библиотекаdatabase/sql/driverПредоставляются стандартные спецификации, реализованные этими сторонними пакетами.

Поэтому для программирования баз данных на языке Go обычно требуется только импорт стандартной библиотеки.database/sqlпакет.Этот пакет предоставляет все необходимые структуры, функции и методы для работы с базой данных.Следующее является оператором для импорта этого пакета:

import database/sql

Преимущество этого в языке Go заключается в том, что при миграции из одной базы данных в другую (например,SQL Serverпереехал вMySQL), вам нужно только изменить пакет драйверов.

Пакеты драйверов баз данных, поддерживаемые Go

Ранее мы говорили, что операции с данными языка Go реализуются различными сторонними пакетами, поэтому, если мы хотим подключиться к базе данных MySQL, как нам реализовать такой пакет? Фактически, стандартная библиотека языка Godatabase/sql/driverОпределяет все интерфейсы, которые реализуют сторонний пакет драйверов, мы импортируем только реализациюdatabase/sql/driverДостаточно соответствующего пакета интерфейсных драйверов.

Ниже приведен список драйверов баз данных, поддерживающих Golang:

GitHub.com/gowaves/go/me…

Установите сторонние пакеты драйверов

В качестве примера возьмем пакет драйвера базы данных MySQL:

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

импортировать пакет драйверов

import database/sql
import _ "github.com/go-sql-driver/mysql"

Структура sql.DB

sql.DBструктураsql/databaseОбъект операции с базой данных, инкапсулированный пакетом, включая основные методы работы с базой данных.

DSN

DSNполное имяData Source Name, указывающий источник подключения к базе данных, используемый для определения способа подключения к базе данных. Формат DSN для разных баз данных отличается, что зависит от реализации драйвера базы данных. Ниже приведеноgo-sql-driver/sqlФормат DSN выглядит следующим образом:

//[用户名[:密码]@][协议(数据库服务器地址)]]/数据库名称?参数列表
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

Инициализировать sql.DB

database/sqlВ пакете есть две функции, которые инициализируют и возвращают*sql.DBСтруктурный объект:

//driverName表示驱动名,如mysql,dataSourceName为上文介绍的DSN
func Open(driverName, dataSourceName string) (*DB, error)
func OpenDB(c driver.Connector) *DB

Ниже показано, как использовать эти две функции:

Как правило, мы используемOpen()функция может инициализировать и возвращать*sql.DBэкземпляр структуры, используйтеOpen()Функция должна только передать имя драйвера и соответствующий DSN.Он очень прост в использовании и очень общий.Когда вам нужно подключиться к разным базам данных, вам нужно только изменить имя драйвера и DSN.

import "database/sql"
import _ "github.com/go-sql-driver/mysql" //注意前面有_

func open(){
    const DRIVER = "mysql"
    var DSN = "root:123456@tcp(localhost:3306)/test?charset=utf8&parseTime=True&loc=Local"
    var err error
    db, err = sql.Open(DRIVER, DSN)
    if err != nil {
        panic(err)
    }
    if err = db.Ping();err != nil{
        panic(err)
    }
}

OpenDB()Функция зависит от реализации пакета драйверовsql/database/driverв упаковкеConnectorинтерфейс, этот метод не является обобщенным и не рекомендуется использовать, в следующей демонстрации используетсяmysqlпакет драйверовdriver.Connectorинициализировать и вернуть*sql.DBПример структуры:

import "database/sql"
import "github.com/go-sql-driver/mysql"//注意前面没有_

func openConnector() {
    Connector, err := mysql.NewConnector(&mysql.Config{
        User:   "root",
        Passwd: "123456",
        Net:    "tcp",
        Addr:   "localhost:3306",
        DBName: "test",
        AllowNativePasswords:true,
        Collation:"utf8_general_ci",
        ParseTime:true,
        Loc:time.Local,
    })
    if err != nil {
        panic(err)
    }
    db = sql.OpenDB(Connector)
    if err = db.Ping();err != nil{
        panic(err)
    }
}

Используя метод, определенный ранее, инициализируйте*sql.DBструктура указателя:

var db *sql.DB

//在init方法初始化`*sql.DB`
func init(){
    open()
    //或者
    openConnector()
}

Вот что я хочу сказать,sql.DBдаsql/databaseСтруктура, инкапсулированная пакетом, но не представляющая собой объект подключения к базе данных.sql.DBВ качестве простого пула соединений с базой данных мы устанавливаем соответствующие параметры пула соединений с базой данных следующими методами:

func (db *DB) SetMaxIdleConns(n int)//设置连接池中最大空闲数据库连接数,<=0表示不保留空闲连接,默认值2
func (db *DB) SetMaxOpenConns(n int)//设置连接池最大打开数据库连接数,<=表示不限制打开连接数,默认为0
func (db *DB) SetConnMaxLifetime(d time.Duration)//设置连接超时时间

демонстрация кода

db.SetMaxOpenConns(100)//设置最多打开100个数据连连接
db.SetMaxIdleConns(0)//设置为0表示
db.SetConnMaxLifetime(time.Second * 5)//5秒超时

Основные операции с базой данных

Ниже мы демонстрируем основные операции добавления, удаления, модификации и запроса базы данных (CURD) на языке Go.usersтаблица данных, которая создалаSQLЗаявление выглядит следующим образом:

CREATE TABLE users(
    id       INT         NOT NULL AUTO_INCREMENT COMMENT 'ID',
    username VARCHAR(32) NOT NULL                COMMENT '用户名',
    moeny    INT         DEFAULT 0               COMMENT '账户余额',
    PRIMARY KEY(id)
);
INSERT INTO users VALUES(1,'小明',1000);
INSERT INTO users VALUES(2,'小红',2000);
INSERT INTO users VALUES(3,'小刚',1400);

Запрос

Запрос — самая основная функция работы с базой данных.В языке Go вы можете использоватьsql.DBсерединаQuery()илиQueryContext()методы, которые определяются следующим образом:

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

Query()иQueryContext()метод возвращаетsql.Rowsструктура, представляющая набор результатов запроса,sql.RowsОпределение и содержащиеся в нем методы следующие:

type Rows struct {
    //contains filtered or unexported fields
}
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) NextResultSet() bool
func (rs *Rows) Scan(dest ...interface{}) error //扫描结构体

использоватьsql.RowsизNext()иScanметод, но он может проходить по возвращаемому набору результатов, ниже приведен пример кода:

func query() {
    selectText := "SELECT * FROM users WHERE id = ?"
    rows, _ := db.Query(selectText, 2)
    defer rows.Close()
    for rows.Next() {
        var (
            id       int
            username string
            money    int
        )
        _ = rows.Scan(&id, &username,&money)
        fmt.Println(id, username,money)
    }
}

также можно использоватьsql.DBсерединаQueryRow()илиQueryRowContext()метод, определения этих двух методов следующие:

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

QueryRowиQueryRowContextвернутьsql.Rowструктура, представляющая строку таблицы данных,sql.Rowопределяется следующим образом, видно, чтоsql.Rowтолько одна структураScan()способ сканированияsql.Rowданные в структуре.

type Row struct{
}
func (r *Row) Scan(dest ...interface{}) error

демонстрация кода

func queryRow(){
    selectText := "SELECT * FROM users WHERE id = ?"
    row := db.QueryRow(selectText, 2)
    var (
        id       int
        username string
        money    int
    )
    _ = row.Scan(&id, &username,&money)
    fmt.Println(id, username,money)
}

Кроме того, используйтеsql.DBсерединаPrepare()илиPrepareContext()метод, который возвращаетsql.Stmtструктура`.

Примечание. Опыт работы со структурой sql.Stmt сначала помещается вPrepare()илиPrepareContext()Определенный оператор SQL отправляется в базу данных для выполнения, параметры, требуемые в операторе SQL, отправляются в базу данных, и возвращается результат обработки.

func (db *DB) Prepare(query string) (*Stmt, error)
func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error)

sql.Stmtпредставлено сsql.DBМетод запроса и возврата набора результатов, см. пример ниже:

func queryStmt(){
    stmt,err := db.Prepare("SELECT * FROM users WHERE id = ?")
    if err != nil{
        return
    }
    defer stmt.Close()
    rows,err := stmt.Query(2)
    defer rows.Close()
    for rows.Next() {
        var (
            id       int
            username string
            money    int
        )
        _ = rows.Scan(&id, &username,&money)
        fmt.Println(id, username,money)
    }
}

Добавить к

Добавьте записи базы данных, вы можете использоватьsql.DBсерединаExec()илиExecContext()методы, эти два метода определяются следующим образом:

func (db *DB) Exec(query string, args ...interface{}) (Result, error)
func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)

Пример кода:

func insert(){
    insertText := "INSERT INTO users values(?,?,?)"
    rs,err := db.Exec(insertText,4,"juejin",1000)
    if err != nil{
        fmt.Println(err)
        return
    }
    if id,_ := rs.LastInsertId();id > 0 {
        fmt.Println("插入成功")
    }
    /*也可以这样判断是否插入成功
    if n,_ := rs.RowsAffected();n > 0 {
        fmt.Println("插入成功")
    }
    */
}

Exec()илиExecContext()Первое возвращаемое значение метода является реализованнымsql.ResultТип интерфейса,sql.Resultопределяется следующим образом:

УведомлениеLastInsertId()Метод возвращает значение идентификатора автоинкремента только тогда, когда используется оператор INSERT и таблица данных имеет идентификатор автоинкремента, в противном случае он возвращает 0.

type Result interface {
    LastInsertId() (int64, error)//使用insert向数据插入记录,数据表有自增id时,该函数有返回值
    RowsAffected() (int64, error)//表示影响的数据表行数
}

мы можем использоватьsql.ResultсерединаLastInsertId()метод илиRowsAffected()судитьSQLБыл ли оператор выполнен успешно.

Помимо использованияsql.DBсерединаExec()иExecContext()метод, вы также можете использоватьPrepare()илиPrepareContext()возвращениеsql.Stmtструктуру, затем пройтиsql.StmtсерединаExec()способ записи данных в таблицу данных.

использоватьsql.StmtДемонстрация записи данных в таблицу данных:

func insertStmt(){
    stmt,err := db.Prepare("INSERT INTO users VALUES(?,?,?)")
    defer stmt.Close()
    if err != nil{
        return
    }
    rs,err := stmt.Exec(5,"juejin",1000)
    if id,_ := rs.LastInsertId(); id > 0 {
        fmt.Println("插入成功")
    }
}

Обратите внимание, что использованиеsql.StmtсерединаExec()илиExecContext()воплощать в жизньSQLОн также подходит для операторов обновления и удаления и не будет демонстрироваться при обновлении и удалении, описанном ниже.

возобновить

Точно так же, как добавление данных в таблицу данных, вы можете использоватьsql.DBизExec()илиExecContext()метод, но с использованием базы данныхUPDATEКогда оператор обновляет данные, мы можем передать толькоsql.Resultв структуреRowsAffected()метод, чтобы определить количество затронутых строк данных, а затем определить, успешно ли выполнено выполнение.

func update()  {
    updateText := "UPDATE users SET username = ? WHERE id = ?"
    rs,err := db.Exec(updateText,"database",2)
    if err != nil{
    	fmt.Println(err)
    	return
    }
    if n,_ := rs.RowsAffected();n > 0 {
        fmt.Println("更新成功")
    }
}

Удалить

использоватьDELETEОперация оператора по удалению записей таблицы данных такая же, как и в приведенном выше операторе обновления, см. следующую демонстрацию:

func del()  {
    delText := "DELETE FROM users WHERE id = ?"
    rs,err := db.Exec(delText,1)
    if err != nil{
        fmt.Println(err)
        return
    }
    fmt.Println(rs.RowsAffected())
}

дела

В предыдущих примерах мы не открывали транзакцию, если транзакция не открыта, то по умолчанию каждый отправленный товар будетSQLОператоры рассматриваются как транзакция.Если несколько операторов выполняются вместе, когда один из операторов выполняется неправильно, ранее выполненныйSQLЗаявление нельзя откатить.

Для некоторой бизнес-логики со строгими требованиями (такими как оплата заказа, перевод пользователя и т. д.) в одной и той же транзакции должно быть представлено несколько записей.SQLзаявление, чтобы избежать ситуации, когда транзакция не может быть отменена из-за ошибки выполнения.

открытая транзакция

Как начать новую транзакцию? можно использоватьsql.DBв структуреBegin()илиBeginTx()методы, которые определяются следующим образом:

func (db *DB) Begin() (*Tx, error)
func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)

BeginTx()Второй параметр методаTxOptions, который определяется следующим образом:

type TxOptions struct {
    // Isolation is the transaction isolation level.
    // If zero, the driver or database's default level is used.
    Isolation IsolationLevel
    ReadOnly  bool
}

TxOptionsизIsolationПоле используется для определения уровня изоляции транзакции и его типа.IsolationLevel,IoslationДиапазон значений может быть следующими константами:

const (
    LevelDefault IsolationLevel = iota
    LevelReadUncommitted
    LevelReadCommitted
    LevelWriteCommitted
    LevelRepeatableRead
    LevelSnapshot
    LevelSerializable
    LevelLinearizable
)

Begin()иBeginTxt()метод возвращаетsql.Txструктура, использованиеsql.TxОперации с базой данных будут зафиксированы в той же транзакции.Следующий демонстрационный код:

tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})

//Begin方法实际上是调用BeginTx()方法,db.BeginTx(context.Background(), nil)
tx, err := db.Begin()

Основные операции, поддерживаемые sql.Tx

Ниже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

фиксация транзакции

когда используешьsql.TxПосле обработки данных нам нужно использоватьsql.TxизCommit()метод явно фиксирует транзакцию, и если есть ошибка, вы можете использоватьsql.TxсерединаRollback()Метод откатывает транзакцию и поддерживает согласованность данных.Ниже приведены определения этих двух методов:

func (tx *Tx) Commit() error
func (tx *Tx) Rollback() error

предварительно скомпилировано

sql.Txв структуреStmt()иStmtContext()можетsql.StmtИнкапсулирован как поддерживающий транзакциюsql.StmtСтруктура и возврат, эти два метода определяются следующим образом:

func (tx *Tx) Stmt(stmt *Stmt) *Stmt
func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt

при использованииsql.TxсерединаPrepare()иPrepareContext()метод может напрямую возвращать транзакционныйsql.Stmtструктура

func (tx *Tx) Prepare(query string) (*Stmt, error)
func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error)

Пример

//修改
func txUpdate(){
    tx,_ := db.Begin()
    rs,err := tx.Exec("UPDATE users SET username = ? WHERE id = ?","sssss",2)
    if err != nil{
        panic(err)
    }
    err = tx.Commit()
    if err != nil{
        panic(err)
    }
    if n,_ := rs.RowsAffected();n > 0{
        fmt.Println("成功")
    }
}
//使用Stmt修改
func txStmt(){
    tx,err := db.Begin()
    if err != nil{
        panic(err)
    }
    stmt,err := db.Prepare("UPDATE users SET username = ? WHERE id = ?")
    stmtTx := tx.Stmt(stmt)
    defer stmtTx.Close()
    rs,_ := stmtTx.Exec("test",2)
    _ = tx.Commit()
    if n,_ := rs.RowsAffected();n > 0{
        fmt.Println("成功")
    }
}

Связанная структура ORM

Ранее мы представили нативную поддержку программирования баз данных на языке Go, однако более удобно, что мы можем напрямую использовать некоторые открытые исходные коды.ORM(Object Relational Mapping)Рамка,ORMФреймворки могут инкапсулировать базовыеSQLоператор и сопоставляется непосредственно сStruct,Mapи другие типы данных, избавьте нас от необходимости писать напрямуюSQLОператорская работа, очень простая и удобная.

Ниже приведены некоторые из наиболее часто используемых фреймворков ORM:

GORM

GORMЭто очень полная структура ORM.Помимо основной поддержки изменения и проверки, она также поддерживает ассоциации, включая одну, несколько, принадлежащие и многие-ко-многим таблицы данных.Кроме того, ее также можно создавать/сохранять/ обновляется/удаляется/искается до или затем пишет обратные вызовы ловушек, а также поддерживает транзакции.

В настоящее время GORM поддерживает работу с базой данных.MySQL,SQLite3,SQL Server,Postgres.

Xorm

XormЭто также простая и мощная структура ORM, и ее функции такжеGORMпохож, но поддерживает больше драйверов баз данных, чемGORMбольше, поддержкаMySQL,SQL Server,SQLite3,Postgres,MyMysql,Tidb,Oracleи т. д. управляемая базой данных.

Beego ORM

Beego ORMЭто веб-фреймворк, разработанный китайскимиBeegoмодуль в , хотя этоBeegoМодуль , но может использоваться самостоятельно, но в настоящее времяBeego ORMтолько поддержкаMySQL,SQLite3,Postgresи т. д. управляемая базой данных.

В дополнение к трем фреймворкам ORM, которые мы представили выше, на самом деле существует много хороших фреймворков ORM, вы можете взглянуть на них, когда у вас будет время.

резюме

Язык Go абстрагирует и инкапсулирует работу базы данных вsql/databaseВ пакете он предоставляет нам единый API для работы с разными базами данных, что очень удобно, как мы объясняли в этой статье.sql/databaseв упаковкеsql.DB,sql.Rows,sql.Stmt,sql.TxИспользование других структур, я думаю, вы сможете освоить метод работы с базой данных на языке Go с помощью приведенных выше примеров.


Ваше внимание — самое большое поощрение на моем писательском пути!