데이터베이스 행을 구조체로 변환하는 방법


80

구조체가 있다고 가정 해 보겠습니다.

type User struct {
    Name  string
    Id    int
    Score int
}

그리고 동일한 스키마를 가진 데이터베이스 테이블. 데이터베이스 행을 구조체로 구문 분석하는 가장 쉬운 방법은 무엇입니까? 아래에 답변을 추가했지만 그것이 가장 좋은 것인지 잘 모르겠습니다.

답변:


99

Go 패키지 테스트는 종종 작업 방법에 대한 단서를 제공합니다. 예를 들어 database/sql/sql_test.go,

func TestQuery(t *testing.T) {
    /* . . . */
    rows, err := db.Query("SELECT|people|age,name|")
    if err != nil {
            t.Fatalf("Query: %v", err)
    }
    type row struct {
            age  int
            name string
    }
    got := []row{}
    for rows.Next() {
            var r row
            err = rows.Scan(&r.age, &r.name)
            if err != nil {
                    t.Fatalf("Scan: %v", err)
            }
            got = append(got, r)
    }
    /* . . . */
}

func TestQueryRow(t *testing.T) {
    /* . . . */
    var name string
    var age int
    var birthday time.Time
    err := db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&age)
    /* . . . */
}

귀하의 질문에 대해 행을 구조로 쿼리하면 다음과 같이 번역됩니다.

var row struct {
    age  int
    name string
}
err = db.QueryRow("SELECT|people|age,name|age=?", 3).Scan(&row.age, &row.name)

귀하의 솔루션과 비슷해 보이지만 솔루션을 찾는 방법을 보여주는 것이 중요합니다.


78
쉬운 방법은 구조체 필드에 수동으로 바인드 컬럼에 있다면 나는 하드 방법 궁금 해요
안토니 사냥

10
불행하게도,이 특히 큰 구조체의 경우에 매우 편리하지 않습니다 - 구조체의 특성에 수동으로 바인딩을 사용하여 ... 완전한 실패입니다 jmoiron / SQLX 또는 다른 라이브러리를 더 효율적으로 ...
shadyyx

나는 행에서 아무것도 다시 얻지 못한다. 모든 변수가 비어 있도록 설정됩니다.
filthy_wizard

14
여기서 파이프 구문은 도대체 무엇입니까? "선택 | 사람"? 나는 godocs에서 그것에 대한 어떤 참조도 보지 못했습니다.
Brian

9
Brian과 같은 WTF 순간을 보냈습니다. 순전히 sql / database ( golang.org/src/database/sql/fakedb_test.go ) 테스트를 위해 만든 가짜 드라이버로 밝혀졌습니다 . 새로운 코드에 사용할 수 있기를 정말 바랬습니다!
Trey Stout

58

github.com/jmoiron/sqlx를 권장 합니다. .

README에서 :

sqlx는 이동 표준 database/sql라이브러리 에서 확장 세트를 제공하는 라이브러리입니다. 의 SQLX 버전 sql.DB, sql.TX, sql.Stmt , 등. 모두 기본 인터페이스를 그대로 유지하므로 해당 인터페이스가 표준 인터페이스의 상위 집합이됩니다. 따라서 데이터베이스 / SQL을 사용하여 기존 코드베이스를 sqlx와 통합하는 것이 비교적 어렵지 않습니다.

주요 추가 개념은 다음과 같습니다.

  • 행을 구조체 (내장 구조체 지원 포함), 맵 및 슬라이스로 마샬링
  • 준비된 명령문을 포함한 명명 된 매개 변수 지원
  • GetSelect구조체 / 슬라이스 쿼리에서 빠르게 이동합니다

README에는 행을 구조체로 스캔하는 방법을 보여주는 코드 스 니펫도 포함되어 있습니다.

type Place struct {
    Country       string
    City          sql.NullString
    TelephoneCode int `db:"telcode"`
}
// Loop through rows using only one struct
place := Place{}
rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
    err := rows.StructScan(&place)
    if err != nil {
        log.Fatalln(err)
    } 
    fmt.Printf("%#v\n", place)
}

각 열을 구조체의 필드에 수동으로 매핑 할 필요가 없었습니다. sqlx에는 struct 필드에 대한 데이터베이스 열에 대한 몇 가지 기본 매핑이 있으며 태그를 사용하여 데이터베이스 열을 지정할 수 있습니다 ( 위 TelephoneCodePlacestruct 필드 참고 ). 이에 대한 자세한 내용 은 설명서를 참조하십시오 .


37

여기에 한 가지 방법이 Scan있습니다. 함수 에서 모든 구조체 값을 수동으로 할당하기 만하면 됩니다.

func getUser(name string) (*User, error) {
    var u User
    // this calls sql.Open, etc.
    db := getConnection()
    // note the below syntax only works for postgres
    err := db.QueryRow("SELECT * FROM users WHERE name = $1", name).Scan(&u.Id, &u.Name, &u.Score)
    if err != nil {
        return &User{}, err
    } else {
        return &u, nil
    }
}

1
@eslammostafa 어떤 시점에서이 코드가 NULL 값에 문제가있을 수 있습니까?
deFreitas

예를 들어 @deFreitas 점수는 데이터베이스에서 오는 null 값을 의미합니다.
she7ata

@eslammostafa 나는이 정말 고통, 이해
deFreitas

그 동안 @deFreitas는 sql.NullString 및 sql.NullInt64 ..etc를 사용하여 null 값을 처리하지만 약간의 추가 작업입니다.
she7ata

@eslammostafa sql.NullString구조를 사용 하고 변환 하면 문제가 JSON발생합니다. 생성 된 것은 친숙하지 않습니다. a VO또는 이와 비슷한 것을 사용해야 합니다
deFreitas

7
rows, err := connection.Query("SELECT `id`, `username`, `email` FROM `users`")

if err != nil {
    panic(err.Error())
}

for rows.Next() {
    var user User

    if err := rows.Scan(&user.Id, &user.Username, &user.Email); err != nil {
        log.Println(err.Error())
    }

    users = append(users, user)
}

전체 예


1

이를위한 패키지가 있습니다 : sqlstruct

안타깝게도 지난번에 내장 된 구조체를 지원하지 않는지 확인했습니다 (자신을 구현하는 것은 사소한 일입니다. 몇 시간 만에 프로토 타입을 만들었습니다).

방금 sqlstruct에 대한 변경 사항을 커밋했습니다.


-1

사용 : go-models-mysql sqlbuilder

val, err = m.ScanRowType(row, (*UserTb)(nil))

또는 전체 코드

import (
    "database/sql"
    "fmt"

    lib "github.com/eehsiao/go-models-lib"
    mysql "github.com/eehsiao/go-models-mysql"
)

// MyUserDao : extend from mysql.Dao
type MyUserDao struct {
    *mysql.Dao
}

// UserTb : sql table struct that to store into mysql
type UserTb struct {
    Name       sql.NullString `TbField:"Name"`
    Id         int            `TbField:"Id"`
    Score      int            `TbField:"Score"`
}

// GetFirstUser : this is a data logical function, you can write more logical in there
// sample data logical function to get the first user
func (m *MyUserDao) GetFirstUser() (user *User, err error) {

    m.Select("Name", "Id", "Score").From("user").Limit(1)
    fmt.Println("GetFirstUser", m.BuildSelectSQL().BuildedSQL())
    var (
        val interface{}
        row *sql.Row
    )

    if row, err = m.GetRow(); err == nil {
        if val, err = m.ScanRowType(row, (*UserTb)(nil)); err == nil {
            u, _ := val.(*UserTb)

            user = &User{
                Name:       lib.Iif(u.Name.Valid, u.Nae.String, "").(string),
                Id:         u.Id,
                Score:      u.Score,
            }
        }
    }
    row, val = nil, nil

    return
}

-1

이를위한 라이브러리가 있습니다 : scany .

다음과 같이 사용할 수 있습니다.

type User struct {
    Name  string
    Id    int
    Score int
}

// db is your *sql.DB instance
// ctx is your current context.Context instance

// Use sqlscan.Select to query multiple records.
var users []*User
sqlscan.Select(ctx, db, &users, `SELECT name, id, score FROM users`)

// Use sqlscan.Get to query exactly one record.
var user User
sqlscan.Get(ctx, db, &user, `SELECT name, id, score FROM users WHERE id=123`)

잘 문서화되어 있고 작업하기 쉽습니다.

면책 조항 : 나는이 도서관의 저자입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.