Я не большой поклонник ОРМ. Не поймите меня неправильно, я не собираюсь начинать кампанию против них. Я знаю, что многие люди считают их отличным уровнем абстракции в своей базе данных. Но я обычно чувствую себя более продуктивно с написанием запросов, когда я могу просто написать SQL.
Хорошо, не всегда . Написание простых старых запросов CRUD (создание, чтение, обновление, удаление) является бессмысленной рутинной работой, эквивалентной очистке туалета.
Поэтому я написал небольшую библиотеку с именем structable, которая заботится о шаблонном CRUD для меня, но позволяет легко и просто создавать свои собственные запросы, когда пришло время выйти за рамки простых операций.
Примечание. Structable 3.0 является новым и нарушает обратную совместимость со Structable 1.0. Эта статья охватывает 3.0.
Структурная таблица
В основе Structable лежит простое средство отображения, которое читает аннотированную структуру и отображает ее в таблицу в реляционной базе данных. Я называю это «отображение структуры таблицы» (так называется StrucTable).
С помощью нескольких аннотаций я могу сделать простую карту:
type User struct {
Id int `stbl:"id,PRIMARY_KEY,SERIAL"`
Name string `stbl:"name"`
Email string `stbl:"email"`
NotMapped string // This will not be mapped to a DB record at all.
}
stblАннотацию говорит Structable о поле на структуры. Например, Nameстрока отображается в столбец с nameпомощью stblаннотации. Structable предполагает, что вы спроектировали свою базу данных таким образом, что вы сможете хранить строку в этом столбце.
IdПоле имеет некоторые дополнительные вещи в stblтеге:
Id int `stbl:"id,PRIMARY_KEY,SERIAL"`
PRIMARY_KEYКлючевое слово говорит structable , что поле Id является первичным ключом для таблицы. SERIAL(Или , AUTOINCREMENTесли вы предпочитаете) говорит Structable , что значение этого поля можно считать управлять базой данных.
Запись против рекордера
Структура выше является записью . Он содержит данные, но не знает, как что-то сделать с этими данными. Structable обеспечивает рекордер, чтобы что-то делать с данными.
В простейшей форме использование рекордера — это вопрос присоединения структуры записи к рекордеру :
db = //... we'll get to this in a minute.
dbFlavor = "postgres"
user := new(User)
userRecorder = structable.New(db, dbFlavor).Bind("users_table", user)
Рекордеру нужно несколько вещей. Во-первых, ему нужен дескриптор базы данных. Я вернусь к этому, но в двух словах, Structable использует мой любимый инструмент для работы с базами данных: белка .
Имея соединение с базой данных, мы можем связать () (прикрепить) конкретную таблицу и структуру к устройству записи. Теперь мы по существу сказали, что структура User хранится в users_tableи управляется нашей новой userRecorder.
Отсюда мы можем использовать рекордер для вставки, обновления, загрузки, тестирования или удаления записи:
user.Name = "Matt" userRecorder.Insert()
Теоретически это похоже на шаблон проектирования, известный как DAO. Но самое элегантное выражение Structable найдено с использованием шаблона Active Record.
Использование Structable для активных записей
Активная запись — это объект записи, который является собственным рекордером. Мы можем легко это сделать с помощью Structable, добавив в нашу Userструктуру всего два небольших дополнения :
type User struct {
structable.Recorder
builder squirrel.StatementBuilderType
Id int `stbl:"id,PRIMARY_KEY,SERIAL"`
Name string `stbl:"name"`
Email string `stbl:"email"`
}
// NewUser creates a new Structable wrapper for a user.
//
// Of particular importance, watch how we intialize the Recorder.
func NewUser(db squirrel.DBProxyBeginner, dbFlavor string) *User {
u := new(User)
u.Recorder = structable.New(db, dbFlavor).Bind("user_table", u)
return u
}
Структура Userтеперь имеет анонимный structable.Recorder. В Go это позволяет нам «наследовать» методы Recorder. В целях расширения я также всегда сохраняю a squirrel.StatementBuilderTypeв своих записях, чтобы впоследствии я мог добавить новые методы базы данных.
Теперь давайте посмотрим на NewUserметод. Вместо создания внешнего рекордера он инициализирует свой внутренний аноним Recorder. Теперь я могу использовать экземпляры моей Userструктуры следующим образом:
user := NewUser(db, dbFlavor) user.Name = "Matt" user.Insert() anotherUser := NewUser(db, dbFlavor) anotherUser.Id = 123 anotherUser.Load() // This loads because Id is a PRIMARY_KEY field.
Краткое слово о подключении к базе данных
Вы можете взглянуть на Squirrel, чтобы лучше понять, как управлять соединениями. Это интуитивно понятно и очень мощно. Но вот как я настроил это для примеров выше:
func main() {
// Boilerplate DB setup.
// First, we need to know the database driver.
driver := "postgres"
// Second, we need a database connection.
con, _ := sql.Open(driver, "dbname=structable_test sslmode=disable")
// Third, we wrap in a prepared statement cache for better performance.
cache := squirrel.NewStmtCacheProxy(con)
// Create an empty new user and give it some properties.
user := NewUser(cache, driver)
user.Name = "Matt"
user.Email = "matt@example.com"
}
По сути, я создаю новое соединение с базой данных Postgres (хотя оно отлично работает с MySQL и, возможно, с другими разновидностями БД). Затем я оборачиваю его в кэш операторов Squirrel, чтобы я мог прозрачно повторно использовать подготовленные операторы. Оттуда я отправляюсь на гонки.