Bun: SQL 우선 Golang ORM

31696 단어 databasegosql
Bun은 PostgreSQL, MySQL/MariaDB, MSSQL 및 SQLite용SQL-first Golang ORM입니다.

SQL-first는 다음 Bun 쿼리와 같이 Go에서 SQL 쿼리를 작성할 수 있음을 의미합니다.

var num int
err := db.NewSelect().
    TableExpr("generate_series(1, 3)").
    Where("generate_series = ?", 3).
    Limit(10).
    Scan(ctx, &num)


다음 SQL을 생성합니다.

SELECT *
FROM generate_series(1, 3)
WHERE generate_series = 123
LIMIT 10


SQL은 여전히 ​​존재하지만 Bun은 ? placeholders 덕분에 SQL 주입으로부터 보호하면서 긴 쿼리를 생성하는 데 도움이 됩니다.

Where("id = ?", 123)     // WHERE id = 123
Where("id >= ?", 123)    // WHERE id >= 123
Where("id = ?", "hello") // WHERE id = 'hello'

Where("id IN (?)", bun.In([]int{1, 2, 3})) // WHERE id IN (1, 2, 3)

Where("? = ?", bun.Ident("column"), "value") // WHERE "column" = 'value'


Bun을 사용하면 다음 Bun 쿼리와 같이 정말 복잡한 쿼리를 작성할 수 있습니다.

regionalSales := db.NewSelect().
    ColumnExpr("region").
    ColumnExpr("SUM(amount) AS total_sales").
    TableExpr("orders").
    GroupExpr("region")

topRegions := db.NewSelect().
    ColumnExpr("region").
    TableExpr("regional_sales").
    Where("total_sales > (SELECT SUM(total_sales) / 10 FROM regional_sales)")

var []items map[string]interface{}

err := db.NewSelect().
    With("regional_sales", regionalSales).
    With("top_regions", topRegions).
    ColumnExpr("region").
    ColumnExpr("product").
    ColumnExpr("SUM(quantity) AS product_units").
    ColumnExpr("SUM(amount) AS product_sales").
    TableExpr("orders").
    Where("region IN (SELECT region FROM top_regions)").
    GroupExpr("region").
    GroupExpr("product").
    Scan(ctx, &items)


다음 SQL을 생성합니다.

WITH regional_sales AS (
    SELECT region, SUM(amount) AS total_sales
    FROM orders
    GROUP BY region
), top_regions AS (
    SELECT region
    FROM regional_sales
    WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
)
SELECT region,
       product,
       SUM(quantity) AS product_units,
       SUM(amount) AS product_sales
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product


구조체와 테이블



Bun에서는 다음 코드와 같이 구조체 기반models을 사용하여 Go 구조체를 데이터베이스 테이블에 매핑할 수 있습니다.

type Model struct {
    ID        int64 `bun:",pk,autoincrement"`
    Name      string `bun:",notnull"`
    CreatedAt time.Time `bun:",nullzero,default:now()"`
}

err := db.ResetModel(ctx, &Model{})


다음 테이블을 생성합니다.

CREATE TABLE "models" (
  "id" BIGSERIAL NOT NULL,
  "name" VARCHAR NOT NULL,
  "created_at" TIMESTAMPTZ DEFAULT now(),
  PRIMARY KEY ("id"),
)


그런 다음 Go 구조체를 사용하여 행을 선택/삽입/업데이트/삭제할 수 있습니다.

model := new(Model)
err := db.NewSelect().Model().Where("id = ?", 123).Scan(ctx)

model.ID = 0
res, err := db.NewInsert().Model(model).Exec(ctx)

res, err := db.NewUpdate().
    Model(model).
    Set("name = ?", "updated name").
    WherePK().
    Exec(ctx)

res, err := db.NewDelete().Model(model).WherePK().Exec(ctx)


자세한 내용은 Bun documentation을 참조하십시오.

골랑 ORM



그렇다면 Golang ORM 부분은 어떻습니까? Bun을 사용하면 Go 구조체를 사용하여 공통 테이블 관계를 정의할 수 있습니다. 예를 들어 다음은 AuthorBook에 속하는 관계를 정의하는 방법입니다.

type Book struct {
    ID       int64
    AuthorID int64
    Author   Author `bun:"rel:belongs-to,join:author_id=id"`
}

type Author struct {
    ID int64
}


그런 다음 Relation 메서드를 사용하여 테이블을 조인합니다.

err := db.NewSelect().
    Model(book).
    Relation("Author").
    Where("id = ?", 123).
    Scan(ctx)



SELECT
  "book"."id", "book"."title", "book"."text",
  "author"."id" AS "author__id", "author"."name" AS "author__name"
FROM "books"
LEFT JOIN "users" AS "author" ON "author"."id" = "book"."author_id"
WHERE id = 1


자세한 내용은 ORM: Table relationships을 참조하십시오.

데이터베이스에 연결



Bun은 데이터베이스/sql 위에서 작동하며 PostgreSQL, MySQL/MariaDB, MSSQL 및 SQLite를 지원합니다.

PostgreSQL 데이터베이스에 연결하려면:

import (
    "github.com/uptrace/bun"
    "github.com/uptrace/bun/dialect/pgdialect"
    "github.com/uptrace/bun/driver/pgdriver"
)

dsn := "postgres://postgres:@localhost:5432/test?sslmode=disable"
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))

db := bun.NewDB(sqldb, pgdialect.New())


MySQL 데이터베이스에 연결하려면:

import (
    "github.com/uptrace/bun"
    "github.com/uptrace/bun/dialect/mysqldialect"
    _ "github.com/go-sql-driver/mysql"
)

sqldb, err := sql.Open("mysql", "root:pass@/test")
if err != nil {
    panic(err)
}

db := bun.NewDB(sqldb, mysqldialect.New())


실행된 모든 쿼리를 기록하려면 다음을 설치할 수 있습니다bundebug plugin.

import "github.com/uptrace/bun/extra/bundebug"

db.AddQueryHook(bundebug.NewQueryHook(
    bundebug.WithVerbose(true), // log everything
))


쿼리 실행



model 이 있으면 쿼리 실행을 시작할 수 있습니다.

// Select a user by a primary key.
user := new(User)
err := db.NewSelect().Model(user).Where("id = ?", 1).Scan(ctx)

// Select first 10 users.
var users []User
err := db.NewSelect().Model(&users).OrderExpr("id ASC").Limit(10).Scan(ctx)


scanning 쿼리 결과와 관련하여 Bun은 매우 유연하며 구조체로 스캔할 수 있습니다.

user := new(User)
err := db.NewSelect().Model(user).Limit(1).Scan(ctx)


스칼라:

var id int64
var name string
err := db.NewSelect().Model((*User)(nil)).Column("id", "name").Limit(1).Scan(ctx, &id, &name)

map[string]interface{}로 :

var m map[string]interface{}
err := db.NewSelect().Model((*User)(nil)).Limit(1).Scan(ctx, &m)


그리고 위 유형의 조각으로:

var users []User
err := db.NewSelect().Model(&users).Limit(1).Scan(ctx)

var ids []int64
var names []string
err := db.NewSelect().Model((*User)(nil)).Column("id", "name").Limit(1).Scan(ctx, &ids, &names)

var ms []map[string]interface{}
err := db.NewSelect().Model((*User)(nil)).Scan(ctx, &ms)


삽입/업데이트/삭제 쿼리의 결과를 반환하고 검색할 수도 있습니다.

var ids []int64
res, err := db.NewDelete().Model((*User)(nil)).Returning("id").Exec(ctx, &ids)


무엇 향후 계획?



시작하려면 documentation을 참조하고 examples을 실행하십시오.

Bun은 OpenTelemetrydistributed tracing 을 활성화하는 metrics 계측을 포함하여 많은 플러그인과 함께 제공됩니다.

추적을 사용하면 OpenTelemetry와 함께 작동하는 monitor performance 중 하나를 사용할 수 있습니다open source tracing tools. 많은DataDog competitors도 OpenTelemetry를 지원합니다.

게다가 export metrics to Prometheus Grafana or a popular alternative 을 사용하여 시각화할 수 있습니다.

좋은 웹페이지 즐겨찾기