"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > Gorm: 사용자 정의 데이터 유형 미리보기

Gorm: 사용자 정의 데이터 유형 미리보기

2024-11-08에 게시됨
검색:920

돌아온 것을 환영합니다, 여러분?! 오늘은 데이터베이스에서 데이터를 앞뒤로 이동할 때 직면할 수 있는 특정 사용 사례에 대해 논의합니다. 먼저 오늘의 과제에 대한 경계를 설정하겠습니다. 실제 사례에 충실하기 위해 미군의 개념을 몇 가지 빌려보겠습니다. 우리가 할 일은 경찰관의 경력 성적을 저장하고 읽을 수 있는 작은 소프트웨어를 작성하는 것입니다.

Gorm의 사용자 정의 데이터 유형

우리 소프트웨어는 육군 장교를 각각의 등급으로 처리해야 합니다. 처음에는 쉬워 보일 수 있으며 여기서는 사용자 정의 데이터 유형이 필요하지 않을 수도 있습니다. 하지만 이 기능을 보여주기 위해 기존과는 다른 방식으로 데이터를 표현해 보겠습니다. 덕분에 Go 구조체와 DB 관계 간의 사용자 지정 매핑을 정의하라는 요청을 받았습니다. 또한 데이터를 구문 분석하려면 특정 논리를 정의해야 합니다. 프로그램의 대상을 살펴봄으로써 이에 대해 확장해 보겠습니다. ?.

처리할 사용 사례

쉽게 설명하기 위해 그림을 사용하여 코드와 SQL 개체 간의 관계를 묘사해 보겠습니다.

Gorm: Sneak Peek of Custom Data Types

각 컨테이너를 하나씩 살펴보겠습니다.

Go 구조체?

여기서 두 개의 구조체를 정의했습니다. Grade 구조체는 군사 등급의 비완전한 목록을 보유하고 있습니다 ?️. 이 구조체는 데이터베이스의 테이블이 아닙니다. 반대로 Officer 구조체에는 ID, 이름 및 Grade 구조체에 대한 포인터가 포함되어 지금까지 장교가 달성한 등급을 나타냅니다.

DB에 장교를 쓸 때마다 grades_achieved 열에는 달성한 등급(Grade 구조체에서 true인 등급)으로 채워진 문자열 배열이 포함되어야 합니다.

DB 관계?

SQL 개체와 관련하여 Officer 테이블만 있습니다. id 및 name 열은 설명이 필요하지 않습니다. 그런 다음 문자열 모음에 장교의 등급을 보유하는 grades_achieved 열이 있습니다.

데이터베이스에서 장교를 디코딩할 때마다 grades_achieved 열을 구문 분석하고 Grade 구조체의 일치하는 "인스턴스"를 생성합니다.

동작이 표준 동작이 아니라는 것을 눈치챘을 것입니다. 원하는 방식으로 이를 이행하려면 몇 가지 준비를 해야 합니다.

여기서 모델의 레이아웃은 의도적으로 지나치게 복잡해졌습니다. 가능하다면 보다 간단한 해결책을 고수하십시오.

사용자 정의 데이터 유형

Gorm은 사용자 정의 데이터 유형을 제공합니다. 이는 검색을 정의하고 데이터베이스에/에서 저장하는 데 큰 유연성을 제공합니다. Scanner와 Valuer ?라는 두 가지 인터페이스를 구현해야 합니다. 전자는 DB에서 데이터를 가져올 때 적용할 사용자 지정 동작을 지정합니다. 후자는 데이터베이스에 값을 쓰는 방법을 나타냅니다. 둘 다 우리가 필요로 하는 비전통적인 매핑 논리를 달성하는 데 도움이 됩니다.

우리가 구현해야 하는 함수의 서명은 Scan(값 인터페이스{}) 오류와 Value()(driver.Value, 오류)입니다. 이제 코드를 살펴보겠습니다.

코드

이 예제의 코드는 domain/models.go와 main.go라는 두 파일에 있습니다. 모델(Go에서는 구조체로 번역됨)을 다루는 첫 번째 것부터 시작해 보겠습니다.

domain/models.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
}

이제 관련 부분을 강조해 보겠습니다.:

  1. 등급 구조체에는 소프트웨어에서 예측한 등급만 나열됩니다.
  2. Officer 구조체는 엔터티의 특성을 정의합니다. 이 엔터티는 DB의 관계입니다. 우리는 두 가지 Gorm 표기법을 적용했습니다.
    1. ID 필드의 gorm:"primaryKey"를 관계의 기본 키로 정의합니다
    2. gorm:"type:varchar[]"는 GradesAchieved 필드를 DB의 varchar 배열로 매핑합니다. 그렇지 않으면 별도의 DB 테이블 또는 임원 테이블의 추가 열로 변환됩니다.
  3. Grade 구조체는 스캔 기능을 구현합니다. 여기서는 원시 값을 가져와 조정하고 g 변수에 일부 필드를 설정한 다음
  4. 를 반환합니다.
  5. Grade 구조체는 Value 함수를 값 수신자 유형으로 구현합니다(이번에는 수신자를 변경할 필요가 없으며 * 참조를 사용하지 않습니다). 임원 테이블의 grades_achieved 열에 쓸 값을 반환합니다

이 두 가지 방법 덕분에 DB 상호 작용 중에 Grade 유형을 보내고 검색하는 방법을 제어할 수 있습니다. 이제 main.go 파일을 살펴보겠습니다.

main.go 파일?

여기서 DB 연결을 준비하고 객체를 관계로 마이그레이션하고(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))
}

이제 이 파일의 관련 섹션을 살펴보겠습니다. 먼저 DB에 더미 데이터를 추가하기 위해 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() 함수는 DB 연결을 설정하는 것으로 시작됩니다. 이 데모에서는 PostgreSQL을 사용했습니다. 그런 다음 Officer 테이블이 데이터베이스에 존재하고 models.Officer 구조체의 최신 버전으로 최신 상태인지 확인합니다. 이 프로그램은 샘플이므로 두 가지 추가 작업을 수행했습니다.

  • main() 함수 끝에서 테이블 제거(프로그램이 종료되면 테이블도 제거하고 싶습니다)
  • 일부 더미 데이터 시드

마지막으로 모든 것이 예상대로 작동하는지 확인하기 위해 다음과 같은 몇 가지 작업을 수행합니다.

  1. DB의 모든 레코드를 가져오는 중
  2. 새 임원 추가(및 다시 가져오기)

이 파일이 전부입니다. 이제 작업을 테스트해 볼까요?.

진실의 순간

코드를 실행하기 전에 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사용자 정의 데이터 유형에 관한 이 블로그 게시물이 도움이 되셨기를 바랍니다. 나는 항상 가장 간단한 접근 방식을 고수할 것을 권장합니다. 결국 필요할 경우에만 이것을 선택하십시오. 이 접근 방식은 코드를 더 복잡하고 덜 견고하게 만드는 대신 유연성을 추가합니다(구조체 정의의 작은 변경으로 인해 오류가 발생하고 추가 작업이 필요할 수 있음).

이 점을 명심하세요. 규칙을 준수하면 코드베이스 전체에서 덜 장황해질 수 있습니다.

이 블로그 게시물을 마무리하는 훌륭한 인용문입니다.
사용자 정의 데이터 유형이 필요하다는 것을 깨닫는다면 이 블로그 게시물이 작동하는 솔루션을 제시하는 좋은 출발점이 될 것입니다.

당신의 감정과 생각을 들려주세요. 어떤 피드백이라도 언제나 감사드립니다! 특정 주제에 관심이 있는 경우 연락해 주시면 최종 후보로 선정해 드리겠습니다. 다음 시간까지 건강 조심하시고 곧 만나요!

릴리스 선언문 이 글은 https://dev.to/ossan/gorm-sneak-peek-of-custom-data-types-97n?1에서 복제되었습니다.1 침해 내용이 있는 경우, [email protected]으로 연락하여 삭제하시기 바랍니다.
최신 튜토리얼 더>

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3