Как я уже говорил, Go не предоставляет официальных драйверов баз данных, как это делает, например, PHP, но у него есть некоторые стандарты интерфейсов драйверов для разработчиков драйверов баз данных. Если ваш код будет разрабатываться в соответствии с этими стандартами, вам не придется его менять в случае изменения базы данных. Давайте посмотрим, что представляют из себя эти стандарты интерфейсов.
Эта функция из пакета database/sql
обеспечивает регистрацию драйверов баз данных сторонних разработчиков. Все сторонние драйвера должны вызывать функцию Register(name string, driver driver.Driver)
в функции init()
для того чтобы обеспечить регистрацию драйвера.
Давайте посмотрим на соответствующий код в драйверах mymysql и sqlite3:
//https://github.com/mattn/go-sqlite3 driver
func init() {
sql.Register("sqlite3", &SQLiteDriver{})
}
//https://github.com/mikespook/mymysql driver
// Driver automatically registered in database/sql
var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"}
func init() {
Register("SET NAMES utf8")
sql.Register("mymysql", &d)
}
Мы видим, что все сторонние драйвера вызывают данную функцию для саморегистрации. При этом Go использует карту для сохранения информации о пользовательском драйвере внутри database/sql
.
var drivers = make(map[string]driver.Driver)
drivers[name] = driver
Таким образом, данная функция может зарегистрировать любое количество драйверов с разными именами.
Если посмотреть примеры использования сторонних драйверов, мы всегда увидим следующий код:
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
Бланк _
, в примере, приводит в замешательство многих начинающих программистов, но он является примером отличного дизайна языка Go. Как вы знаете, этот идентификатор предназначен для удаления значений из функции return. Так же вы должны знать, что вы должны использовать все импортированные пакеты в коде. В данном случае, использование бланка при импорте означает, что вам необходимо исполнить функцию init() данного пакета без его прямого использования, которая, в свою очередь, используется для регистрации драйвера базы данных.
Driver
- это интерфейс, который имеет метод Open(name string)
который возвращает интерфейс Conn
.
type Driver interface {
Open(name string) (Conn, error)
}
Conn
- это одноразовое соединение. Это означает, что его можно использовать только один раз в одной горутине. Следующй код вызовет исключение:
...
go goroutineA (Conn) // запрос
go goroutineB (Conn) // вставка
...
потому, что Go не имеет представления в какой горутине выполняется операция. Операция "запрос" может получить результаты "вставки" и наоборот.
Все сторонние драйверы должны реализовывать эту функцию для разбора имени соединения и корректного возврата результатов.
Driver.Conn - это интерфейс подключения базы данных с несколькими методами.
type Conn interface {
Prepare(query string) (Stmt, error)
Close() error
Begin() (Tx, error)
}
Prepare
- возвращает статус подготовки соответствующих команд SQL для запроса, удаления и т.д.Close
- закрывает текущее соединение и освобождает ресурсы. Большинство сторонних драйверов реализуют пулы соединений самостоятельно, поэтому вам не стоит подключать кэш соединений если вы не хотите иметь неожиданных ошибок.Begin
- запускает и возвращает дескриптор новой транзакцииTx
, вы можете использовать его для запросов, апдейтов, отката транзакций и т.д.
Stmt
- это интерфейс состояния готовности, соответствующий данному соединению, поэтому он может быть использован только в одной горутине, как и Conn
.
type Stmt interface {
Close() error
NumInput() int
Exec(args []Value) (Result, error)
Query(args []Value) (Rows, error)
}
Close
- закрывает текущее соединение, но по прежнему возвращает строки данных, если запрос продолжает выполнятся.NumInput
- возвращает число обязательных аргументов, драйвер базы данных должен проверить аргументы вызывающей стороны и вернуть результат больше 0 и -1 в случае, если драйвер не смог определить количество аргументов.Exec
выполняет SQL - командыupdate/insert
, которые были подготовлены вPrepare
. ВозвращаетResult
.Query
- выполняет SQL - командыselect
, которые были подготовлены вPrepare
. ВозвращаетResult
.
Данный интерфейс, как правило, управляет операциями commit и roll back. Драйвер базы данных должен реализовать эти два метода.
type Tx interface {
Commit() error
Rollback() error
}
Это необязательный интерфейс.
type Execer interface {
Exec(query string, args []Value) (Result, error)
}
Если драйвер не реализует этот интерфейс, то при вызове DB.Exec
, он автоматически вызывает Prepare
и возвращает Stmt
, выполняет Exec
из Stmt
затем закрывает Stmt
.
Этот интерфейс возвращает результаты операций update/insert
.
type Result interface {
LastInsertId() (int64, error)
RowsAffected() (int64, error)
}
LastInsertId
- возвращает автоинкрементный Id после операций вставки в базу данных.RowsAffected
- возвращает номера строк, затронутых операциейupdate/insert
.
driver.Rows - это интерфейс для возвращения результатов набора операций запроса.
type Rows interface {
Columns() []string
Close() error
Next(dest []Value) error
}
Columns
- возвращает информацию о наименовании столбцов таблицы.Close
- закрывает итератор строк.Next
- возвращает следующее поле таблицы и связывает сdest
, все строки должны быть преобразованы к массиву байт. Если доступные данные закончились, возникает ошибкаio.EOF
.
Данный тип является псевдонимом int64 для одноименного метода реализованного в интерфейсе Result
.
type RowsAffected int64
func (RowsAffected) LastInsertId() (int64, error)
func (v RowsAffected) RowsAffected() (int64, error)
Это пустой интерфейс, который может содержать любой тип данных.
type Value interface{}
Value
должно быть содержать значения, совместимые с драйвером, или быть nil, поэтому оно должно быть одним из следующих типов:
int64
float64
bool
[]byte
string [*] За исключением `Rows.Next`, который не может возвращать строку.
time.Time
ValueConverter
- это интерфейс для преобразования наших данных к типам driver.Value
.
type ValueConverter interface {
ConvertValue(v interface{}) (Value, error)
}
Обычно он используется в драйверах баз данных и предоставляет массу полезных особенностей:
- Преобразует
driver.Value
в соответствующее типу поля базы данных. Например преобразует int64 в uint16. - Преобразует результаты запросов к
driver.Value
. - Преобразует
driver.Value
к определенному пользователем значению в функцииscan
.
Valuer
- определяет интерфейс для возврата driver.Value
.
type Valuer interface {
Value() (Value, error)
}
Многие типы реализуют этот интерфейс для преобразования типов между собой и driver.Value.
На данном этапе вы должны иметь представление о том, как разработать драйвер базы данных. После того, как вы реализуете интерфейсы для различных операций, таких как добавление, удаление, обновление и так далее, останется решить проблему коммуникации с конкретной базой данных.
database/sql
определяет высокоуровневые методы для более удобной работы с базами данных (выше чем драйвера) и предлагает вам реализовать пул соединений.
type DB struct {
driver driver.Driver
dsn string
mu sync.Mutex // защищает и закрывает freeConn
freeConn []driver.Conn
closed bool
}
Как вы видите, функция Open
возвращает тип DB
, который содержит freeConn, - это и есть простой пулл. Его реализация очень проста и несколько уродлива. Он использует defer db.putConn(ci, err)
в функции Db.prepare
для помещения соединения в пулл соединений. Каждый раз, когда вызывается функция Conn
, он проверяет длину freeConn
и, если она больше нуля, - это означает, что соединение можно повторно использовать и оно напрямую возвращается вам. В противном случае он создает новое соединение и возвращает его.
- Содержание
- Предыдущий раздел: Базы данных
- Следующий раздел: MySQL