>백엔드 개발 >Golang >Gorm: 사용자 정의 데이터 유형 미리보기

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

DDD
DDD원래의
2024-09-13 20:15:36857검색

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

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

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

처리할 사용 사례

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

Gorm: Sneak Peek of Custom Data Types

각 컨테이너에 대해 하나씩 집중적으로 살펴보겠습니다.

Go 구조체?

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

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

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

main.go 파일?

여기서 DB 연결을 준비하고 객체를 관계로 마이그레이션하고(ORM은 O객체 RelationMapping을 나타냄) 삽입 및 가져오기를 수행합니다. 논리를 테스트하기 위한 기록입니다. 아래는 코드입니다:

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))
}

Now, let's see the relevant sections of this file. First, we define the seedDB function to add dummy data in the DB. The data lives in the data.sql file with the following content:

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}');

The main() function starts by setting up a DB connection. For this demo, we used PostgreSQL. Then, we ensure the officers table exists in the database and is up-to-date with the newest version of the models.Officer struct. Since this program is a sample, we did two additional things:

  • Removal of the table at the end of the main() function (when the program terminates, we would like to remove the table as well)
  • Seeding of some dummy data

Lastly, to ensure that everything works as expected, we do a couple of things:

  1. Fetching all the records in the DB
  2. Adding (and fetching back) a new officer

That's it for this file. Now, let's test our work ?.

The Truth Moment

Before running the code, please ensure that a PostgreSQL instance is running on your machine. With Docker ?, you can run this command:

docker run -d -p 54322:5432 -e POSTGRES_PASSWORD=postgres postgres

Now, we can safely run our application by issuing the command: go run . ?

The output is:

[
        {
                "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
        }
}

Voilà! Everything works as expected. We can re-run the code several times and always have the same output.

That's a Wrap

I hope you enjoyed this blog post regarding Gorm and the Custom Data Types. I always recommend you stick to the most straightforward approach. Opt for this only if you eventually need it. This approach adds flexibility in exchange for making the code more complex and less robust (a tiny change in the structs' definitions might lead to errors and extra work needed).

Keep this in mind. If you stick to conventions, you can be less verbose throughout your codebase.

That's a great quote to end this blog post.
If you realize that Custom Data Types are needed, this blog post should be a good starting point to present you with a working solution.

Please let me know your feelings and thoughts. Any feedback is always appreciated! If you're interested in a specific topic, reach out, and I'll shortlist it. Until next time, stay safe, and see you soon!

위 내용은 Gorm: 사용자 정의 데이터 유형 미리보기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.