С возвращением, ребята ?! Сегодня мы обсудим конкретный вариант использования, с которым мы можем столкнуться при перемещении данных туда и обратно из/в базу данных. Во-первых, позвольте мне установить границы сегодняшней задачи. Чтобы придерживаться примера из реальной жизни, давайте позаимствуем некоторые концепции из армии США. Наша сделка состоит в том, чтобы написать небольшую программу, которая будет сохранять и читать оценки офицеров, которых они достигли за свою карьеру.
Наше программное обеспечение должно обрабатывать армейских офицеров соответствующих им званий. На первый взгляд это может показаться простым, и нам, вероятно, не нужен здесь какой-либо пользовательский тип данных. Однако, чтобы продемонстрировать эту возможность, давайте воспользуемся нетрадиционным способом представления данных. Благодаря этому нас просят определить собственное сопоставление между структурами Go и отношениями БД. Кроме того, мы должны определить конкретную логику для анализа данных. Давайте подробнее рассмотрим цели программы?.
Чтобы упростить задачу, давайте воспользуемся рисунком, чтобы изобразить связи между кодом и объектами SQL:
Давайте рассмотрим каждый контейнер по отдельности.
Здесь мы определили две структуры. Структура Grade содержит неисчерпывающий список военных званий ?️. Эта структура не будет таблицей в базе данных. И наоборот, структура офицера содержит идентификатор, имя и указатель на структуру Grade, указывающий, какие оценки были достигнуты офицером на данный момент.
Всякий раз, когда мы записываем офицера в БД, столбец Grades_achieved должен содержать массив строк, заполненных достигнутыми оценками (теми, которые имеют значение true в структуре Grade).
Что касается объектов SQL, у нас есть только таблица офицеров. Столбцы id и name говорят сами за себя. Затем у нас есть столбец Grades_achieved, в котором хранятся оценки офицера в виде набора строк.
Всякий раз, когда мы декодируем офицера из базы данных, мы анализируем столбец Grades_achieved и создаем соответствующий «экземпляр» структуры Grade.
Возможно, вы заметили, что поведение не является стандартным. Мы должны принять некоторые меры, чтобы выполнить его желаемым образом.
Здесь расположение моделей намеренно усложнено. Пожалуйста, по возможности придерживайтесь более простых решений.
Горм предоставляет нам пользовательские типы данных. Они дают нам большую гибкость в определении извлечения и сохранения в/из базы данных. Мы должны реализовать два интерфейса: Scanner и Valuer?. Первый определяет пользовательское поведение, которое будет применяться при извлечении данных из БД. Последний указывает, как записывать значения в базу данных. Оба помогают нам достичь необходимой нам нетрадиционной логики отображения.
Сигнатуры функций, которые мы должны реализовать, — это Scan(value Interface{}) error и Value() (driver.Value, error). Теперь давайте посмотрим на код.
Код этого примера находится в двух файлах: домен/models.go и main.go. Начнем с первого, касающегося моделей (в Go они переводятся как структуры).
Во-первых, позвольте мне представить код этого файла:
package models import ( "database/sql/driver" "slices" "strings" ) type Grade struct { Lieutenant bool Captain bool Colonel bool General bool } type Officer struct { ID uint64 `gorm:"primaryKey"` Name string GradesAchieved *Grade `gorm:"type:varchar[]"` } func (g *Grade) Scan(value interface{}) error { // we should have utilized the "comma, ok" idiom valueRaw := value.(string) valueRaw = strings.Replace(strings.Replace(valueRaw, "{", "", -1), "}", "", -1) grades := strings.Split(valueRaw, ",") if slices.Contains(grades, "lieutenant") { g.Lieutenant = true } if slices.Contains(grades, "captain") { g.Captain = true } if slices.Contains(grades, "colonel") { g.Colonel = true } if slices.Contains(grades, "general") { g.General = true } return nil } func (g Grade) Value() (driver.Value, error) { grades := make([]string, 0, 4) if g.Lieutenant { grades = append(grades, "lieutenant") } if g.Captain { grades = append(grades, "captain") } if g.Colonel { grades = append(grades, "colonel") } if g.General { grades = append(grades, "general") } return grades, nil }
Теперь давайте выделим важные части ?:
Благодаря этим двум методам мы можем контролировать отправку и получение типа Grade во время взаимодействия с БД. Теперь давайте посмотрим на файл main.go.
Здесь мы подготавливаем соединение с БД, переносим объекты в отношения (ORM означает Object Relation Mapping), а также вставляем и извлекаем записи для проверки логики. Ниже приведен код:
package main import ( "encoding/json" "fmt" "os" "gormcustomdatatype/models" "gorm.io/driver/postgres" "gorm.io/gorm" ) func seedDB(db *gorm.DB, file string) error { data, err := os.ReadFile(file) if err != nil { return err } if err := db.Exec(string(data)).Error; err != nil { return err } return nil } // docker run -d -p 54322:5432 -e POSTGRES_PASSWORD=postgres postgres func main() { dsn := "host=localhost port=54322 user=postgres password=postgres dbname=postgres sslmode=disable" db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { fmt.Fprintf(os.Stderr, "could not connect to DB: %v", err) return } db.AutoMigrate(&models.Officer{}) defer func() { db.Migrator().DropTable(&models.Officer{}) }() if err := seedDB(db, "data.sql"); err != nil { fmt.Fprintf(os.Stderr, "failed to seed DB: %v", err) return } // print all the officers var officers []models.Officer if err := db.Find(&officers).Error; err != nil { fmt.Fprintf(os.Stderr, "could not get the officers from the DB: %v", err) return } data, _ := json.MarshalIndent(officers, "", "\t") fmt.Fprintln(os.Stdout, string(data)) // add a new officer db.Create(&models.Officer{ Name: "Monkey D. Garp", GradesAchieved: &models.Grade{ Lieutenant: true, Captain: true, Colonel: true, General: true, }, }) var garpTheHero models.Officer if err := db.First(&garpTheHero, 4).Error; err != nil { fmt.Fprintf(os.Stderr, "failed to get officer from the DB: %v", err) return } data, _ = json.MarshalIndent(&garpTheHero, "", "\t") fmt.Fprintln(os.Stdout, string(data)) }
Теперь давайте посмотрим соответствующие разделы этого файла. Сначала мы определяем функциюseedDB для добавления фиктивных данных в БД. Данные хранятся в файле data.sql со следующим содержимым:
INSERT INTO public.officers (id, "name", grades_achieved) VALUES(nextval('officers_id_seq'::regclass), 'john doe', '{captain,lieutenant}'), (nextval('officers_id_seq'::regclass), 'gerard butler', '{general}'), (nextval('officers_id_seq'::regclass), 'chuck norris', '{lieutenant,captain,colonel}');
Функция main() начинается с установки соединения с БД. Для этой демонстрации мы использовали PostgreSQL. Затем мы проверяем, что таблица офицеров существует в базе данных и соответствует последней версии структуры models.Officer. Поскольку эта программа является образцом, мы сделали еще две вещи:
Наконец, чтобы убедиться, что все работает как положено, мы делаем пару вещей:
Вот и все для этого файла. Теперь давайте проверим нашу работу?.
Перед запуском кода убедитесь, что на вашем компьютере запущен экземпляр PostgreSQL. С помощью Docker? вы можете запустить эту команду:
docker run -d -p 54322:5432 -e POSTGRES_PASSWORD=postgres postgres
Теперь мы можем безопасно запустить наше приложение, введя команду: go run . ?
Вывод:
[ { "ID": 1, "Name": "john doe", "GradesAchieved": { "Lieutenant": true, "Captain": true, "Colonel": false, "General": false } }, { "ID": 2, "Name": "gerard butler", "GradesAchieved": { "Lieutenant": false, "Captain": false, "Colonel": false, "General": true } }, { "ID": 3, "Name": "chuck norris", "GradesAchieved": { "Lieutenant": true, "Captain": true, "Colonel": true, "General": false } } ] { "ID": 4, "Name": "Monkey D. Garp", "GradesAchieved": { "Lieutenant": true, "Captain": true, "Colonel": true, "General": true } }
Вуаля! Все работает так, как ожидалось. Мы можем перезапустить код несколько раз и всегда получать один и тот же результат.
Надеюсь, вам понравилась эта запись в блоге, посвященная Gorm и Пользовательским типам данных. Я всегда рекомендую вам придерживаться самого простого подхода. Выбирайте этот вариант только в том случае, если он вам в конечном итоге понадобится. Этот подход добавляет гибкости в обмен на то, что код становится более сложным и менее надежным (небольшое изменение в определениях структур может привести к ошибкам и необходимости дополнительной работы).
Имейте это в виду. Если вы придерживаетесь соглашений, вы можете быть менее многословным в своей кодовой базе.
Это отличная цитата для завершения этого поста в блоге.
Если вы понимаете, что необходимы пользовательские типы данных, эта запись в блоге должна стать хорошей отправной точкой, чтобы представить вам работающее решение.
Пожалуйста, дайте мне знать о своих чувствах и мыслях. Любая обратная связь всегда приветствуется! Если вас интересует конкретная тема, свяжитесь с нами, и я внесу ее в шорт-лист. До новых встреч, берегите себя и до скорой встречи!
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3