212 lines
13 KiB
Markdown
212 lines
13 KiB
Markdown
# 5.1 Интерфейс database/sql
|
||
|
||
Как я уже говорил, Go не предоставляет официальных драйверов баз данных, как это делает, например, PHP, но у него есть некоторые стандарты интерфейсов драйверов для разработчиков драйверов баз данных. Если ваш код будет разрабатываться в соответствии с этими стандартами, вам не придется его менять в случае изменения базы данных. Давайте посмотрим, что представляют из себя эти стандарты интерфейсов.
|
||
|
||
## sql.Register
|
||
|
||
Эта функция из пакета `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.Driver
|
||
|
||
`Driver` - это интерфейс, который имеет метод `Open(name string)` который возвращает интерфейс `Conn`.
|
||
|
||
type Driver interface {
|
||
Open(name string) (Conn, error)
|
||
}
|
||
|
||
`Conn` - это одноразовое соединение. Это означает, что его можно использовать только один раз в одной горутине. Следующий код вызовет исключение:
|
||
|
||
...
|
||
go goroutineA (Conn) // запрос
|
||
go goroutineB (Conn) // вставка
|
||
...
|
||
|
||
потому, что Go не имеет представления в какой горутине выполняется операция. Операция "запрос" может получить результаты "вставки" и наоборот.
|
||
|
||
Все сторонние драйверы должны реализовывать эту функцию для разбора имени соединения и корректного возврата результатов.
|
||
|
||
## driver.Conn
|
||
|
||
Driver.Conn - это интерфейс подключения базы данных с несколькими методами.
|
||
|
||
type Conn interface {
|
||
Prepare(query string) (Stmt, error)
|
||
Close() error
|
||
Begin() (Tx, error)
|
||
}
|
||
|
||
- `Prepare` - возвращает статус подготовки соответствующих команд SQL для запроса, удаления и т.д.
|
||
- `Close` - закрывает текущее соединение и освобождает ресурсы. Большинство сторонних драйверов реализуют пулы соединений самостоятельно, поэтому вам не стоит подключать кэш соединений если вы не хотите иметь неожиданных ошибок.
|
||
- `Begin` - запускает и возвращает дескриптор новой транзакции `Tx`, вы можете использовать его для запросов, апдейтов, отката транзакций и т.д.
|
||
|
||
## driver.Stmt
|
||
|
||
`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`.
|
||
|
||
## driver.Tx
|
||
|
||
Данный интерфейс, как правило, управляет операциями commit и roll back. Драйвер базы данных должен реализовать эти два метода.
|
||
|
||
type Tx interface {
|
||
Commit() error
|
||
Rollback() error
|
||
}
|
||
|
||
## driver.Execer
|
||
|
||
Это необязательный интерфейс.
|
||
|
||
type Execer interface {
|
||
Exec(query string, args []Value) (Result, error)
|
||
}
|
||
|
||
Если драйвер не реализует этот интерфейс, то при вызове `DB.Exec`, он автоматически вызывает `Prepare` и возвращает `Stmt`, выполняет `Exec` из `Stmt` затем закрывает `Stmt`.
|
||
|
||
## driver.Result
|
||
|
||
Этот интерфейс возвращает результаты операций `update/insert`.
|
||
|
||
type Result interface {
|
||
LastInsertId() (int64, error)
|
||
RowsAffected() (int64, error)
|
||
}
|
||
|
||
- `LastInsertId` - возвращает автоинкрементный Id после операций вставки в базу данных.
|
||
- `RowsAffected` - возвращает номера строк, затронутых операцией `update/insert`.
|
||
|
||
## driver.Rows
|
||
|
||
driver.Rows - это интерфейс для возвращения результатов набора операций запроса.
|
||
|
||
type Rows interface {
|
||
Columns() []string
|
||
Close() error
|
||
Next(dest []Value) error
|
||
}
|
||
|
||
- `Columns` - возвращает информацию о наименовании столбцов таблицы.
|
||
- `Close` - закрывает итератор строк.
|
||
- `Next` - возвращает следующее поле таблицы и связывает с `dest`, все строки должны быть преобразованы к массиву байт. Если доступные данные закончились, возникает ошибка `io.EOF`.
|
||
|
||
## diriver.RowsAffected
|
||
|
||
Данный тип является псевдонимом int64 для одноименного метода реализованного в интерфейсе `Result`.
|
||
|
||
type RowsAffected int64
|
||
|
||
func (RowsAffected) LastInsertId() (int64, error)
|
||
|
||
func (v RowsAffected) RowsAffected() (int64, error)
|
||
|
||
## driver.Value
|
||
|
||
Это пустой интерфейс, который может содержать любой тип данных.
|
||
|
||
type Value interface{}
|
||
|
||
`Value` должно содержать значения, совместимые с драйвером, или быть nil, поэтому оно должно быть одним из следующих типов:
|
||
|
||
int64
|
||
float64
|
||
bool
|
||
[]byte
|
||
string [*] За исключением `Rows.Next`, который не может возвращать строку.
|
||
time.Time
|
||
|
||
## driver.ValueConverter
|
||
|
||
`ValueConverter` - это интерфейс для преобразования наших данных к типам `driver.Value`.
|
||
|
||
type ValueConverter interface {
|
||
ConvertValue(v interface{}) (Value, error)
|
||
}
|
||
|
||
Обычно он используется в драйверах баз данных и предоставляет массу полезных особенностей:
|
||
|
||
- Преобразует `driver.Value` в соответствующее типу поля базы данных. Например преобразует int64 в uint16.
|
||
- Преобразует результаты запросов к `driver.Value`.
|
||
- Преобразует `driver.Value` к определенному пользователем значению в функции `scan`.
|
||
|
||
## driver.Valuer
|
||
|
||
`Valuer` - определяет интерфейс для возврата ` driver.Value`.
|
||
|
||
type Valuer interface {
|
||
Value() (Value, error)
|
||
}
|
||
|
||
Многие типы реализуют этот интерфейс для преобразования типов между собой и driver.Value.
|
||
|
||
На данном этапе вы должны иметь представление о том, как разработать драйвер базы данных. После того, как вы реализуете интерфейсы для различных операций, таких как добавление, удаление, обновление и так далее, останется решить проблему коммуникации с конкретной базой данных.
|
||
|
||
## database/sql
|
||
|
||
`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` и, если она больше нуля, - это означает, что соединение можно повторно использовать и оно напрямую возвращается вам. В противном случае он создает новое соединение и возвращает его.
|
||
|
||
## Ссылки
|
||
|
||
- [Содержание](preface.md)
|
||
- Предыдущий раздел: [Базы данных](05.0.md)
|
||
- Следующий раздел: [MySQL](05.2.md)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|