Beego ORM

Beego ORMは「DjangoライクなAPIでGoアプリケーションの生産性を向上させるORM」として開発された、Go言語エコシステムで人気の高いWebフレームワークBeegoの中核コンポーネントです。DjangoやSQLAlchemyから着想を得た直感的なAPIデザインにより、Code Firstアプローチでデータベース操作を簡素化。MySQL、PostgreSQL、SQLite3をサポートし、型安全性を保ちながら複雑なクエリ操作やリレーションシップ管理を効率的に実現します。

ORMGoGolangデータベースBeegoSQLモデルフレームワーク

GitHub概要

beego/beego

beego is an open-source, high-performance web framework for the Go programming language.

スター32,157
ウォッチ1,168
フォーク5,630
作成日:2012年2月29日
言語:Go
ライセンス:Other

トピックス

beegogo

スター履歴

beego/beego Star History
データ取得日時: 2025/7/19 08:12

ライブラリ

Beego ORM

概要

Beego ORMは「DjangoライクなAPIでGoアプリケーションの生産性を向上させるORM」として開発された、Go言語エコシステムで人気の高いWebフレームワークBeegoの中核コンポーネントです。DjangoやSQLAlchemyから着想を得た直感的なAPIデザインにより、Code Firstアプローチでデータベース操作を簡素化。MySQL、PostgreSQL、SQLite3をサポートし、型安全性を保ちながら複雑なクエリ操作やリレーションシップ管理を効率的に実現します。

詳細

Beego ORM 2025年版はGo言語での企業レベルWebアプリケーション開発において、データベース層の標準ソリューションとして確立されています。構造体ベースのモデル定義、自動テーブル作成、クエリジェネレーション、トランザクション管理などの包括的な機能を提供。Goの型システムと密接に統合されているため、コンパイル時の型チェックによりランタイムエラーを最小化。リフレクションとコード生成を活用してGoの構造体を自動的にデータベーステーブルにマッピングし、保守性の高いデータアクセス層を構築できます。

主な特徴

  • Code Firstアプローチ: Go構造体の定義からデータベーステーブルを自動生成
  • 豊富なデータベースサポート: MySQL、PostgreSQL、SQLite3の幅広い対応
  • 型安全なクエリ: Goの型システムを活用した安全なデータベース操作
  • 自動Join機能: 関連テーブルの自動的な結合クエリ生成
  • Raw SQLサポート: 複雑なクエリに対応するプレーンSQL実行機能
  • トランザクション管理: 自動コミット・ロールバック機能付きトランザクション

メリット・デメリット

メリット

  • Goの標準的な型システムとの優れた統合による高い型安全性
  • DjangoライクなAPIによる学習コストの低減と開発効率の向上
  • 自動テーブル作成機能による迅速なプロトタイピングとスキーマ管理
  • 豊富なクエリビルダー機能による柔軟なデータベース操作
  • Beegoフレームワークとの完全統合による一貫した開発体験
  • リフレクションベースの自動マッピングによる保守性の向上

デメリット

  • 大規模データ処理時のリフレクションオーバーヘッドによるパフォーマンス制約
  • Goの慣習的なエラーハンドリングとは異なるORM固有のエラー処理
  • 複雑なSQLクエリの表現に限界がありRaw SQLが必要な場合がある
  • モデル構造の変更時にスキーマ移行の手動管理が必要
  • データベース固有の最適化機能へのアクセスが制限される
  • Beegoフレームワーク以外での使用時の統合コストが高い

参考ページ

書き方の例

基本セットアップ

package main

import (
    "github.com/beego/beego/v2/client/orm"
    _ "github.com/go-sql-driver/mysql"    // MySQL ドライバー
    _ "github.com/lib/pq"                 // PostgreSQL ドライバー
    _ "github.com/mattn/go-sqlite3"       // SQLite3 ドライバー
)

func init() {
    // データベース登録
    orm.RegisterDataBase("default", "mysql", 
        "user:password@tcp(localhost:3306)/database?charset=utf8mb4&parseTime=True&loc=Local", 30)
    
    // PostgreSQLの場合
    // orm.RegisterDataBase("default", "postgres", 
    //     "host=localhost port=5432 user=postgres password=password dbname=mydb sslmode=disable")
    
    // SQLite3の場合
    // orm.RegisterDataBase("default", "sqlite3", "path/to/database.db")
    
    // モデル登録
    orm.RegisterModel(new(User), new(Profile), new(Post))
    
    // テーブル自動作成(開発時のみ)
    orm.RunSyncdb("default", false, true)
}

func main() {
    // ORM インスタンス取得
    o := orm.NewOrm()
    
    // 基本的な使用例
    user := &User{Name: "John Doe", Email: "[email protected]"}
    id, err := o.Insert(user)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("User created with ID: %d\n", id)
}

モデル定義と基本操作

package models

import (
    "time"
    "github.com/beego/beego/v2/client/orm"
)

// ユーザーモデル
type User struct {
    Id       int       `orm:"auto"`                           // 自動インクリメント主キー
    Name     string    `orm:"size(100)"`                     // 文字列長制限
    Email    string    `orm:"unique"`                        // ユニーク制約
    Age      int       `orm:"null"`                          // NULL許可
    IsActive bool      `orm:"default(true)"`                 // デフォルト値
    Created  time.Time `orm:"auto_now_add;type(datetime)"`   // 作成時自動設定
    Updated  time.Time `orm:"auto_now;type(datetime)"`       // 更新時自動設定
    
    // リレーションシップ
    Profile *Profile `orm:"rel(one)"`        // 1対1関係
    Posts   []*Post  `orm:"reverse(many)"`   // 1対多関係(逆参照)
}

// プロフィールモデル
type Profile struct {
    Id   int    `orm:"auto"`
    Bio  string `orm:"type(text);null"`
    User *User  `orm:"reverse(one)"`  // 逆参照1対1
}

// 投稿モデル
type Post struct {
    Id       int       `orm:"auto"`
    Title    string    `orm:"size(200)"`
    Content  string    `orm:"type(text)"`
    Published bool     `orm:"default(false)"`
    Created  time.Time `orm:"auto_now_add;type(datetime)"`
    User     *User     `orm:"rel(fk)"`  // 外部キー
}

// テーブル名カスタマイズ
func (u *User) TableName() string {
    return "users"
}

// CRUD操作の実装
func CreateUser(name, email string, age int) (*User, error) {
    o := orm.NewOrm()
    
    user := &User{
        Name:  name,
        Email: email,
        Age:   age,
    }
    
    // Insert操作
    id, err := o.Insert(user)
    if err != nil {
        return nil, err
    }
    
    user.Id = int(id)
    return user, nil
}

func GetUserByID(id int) (*User, error) {
    o := orm.NewOrm()
    
    user := &User{Id: id}
    err := o.Read(user)
    if err != nil {
        return nil, err
    }
    
    return user, nil
}

func GetUserByEmail(email string) (*User, error) {
    o := orm.NewOrm()
    
    user := &User{}
    err := o.QueryTable("user").Filter("email", email).One(user)
    if err != nil {
        return nil, err
    }
    
    return user, nil
}

func UpdateUser(user *User) error {
    o := orm.NewOrm()
    
    _, err := o.Update(user)
    return err
}

func DeleteUser(id int) error {
    o := orm.NewOrm()
    
    user := &User{Id: id}
    _, err := o.Delete(user)
    return err
}

// バリデーション(カスタム実装)
func (u *User) Valid() error {
    if len(u.Name) < 2 {
        return fmt.Errorf("name must be at least 2 characters")
    }
    
    if !strings.Contains(u.Email, "@") {
        return fmt.Errorf("invalid email format")
    }
    
    if u.Age < 0 || u.Age > 150 {
        return fmt.Errorf("age must be between 0 and 150")
    }
    
    return nil
}

高度なクエリ操作

package services

import (
    "fmt"
    "github.com/beego/beego/v2/client/orm"
)

// 複雑なクエリ操作
func GetUsersWithConditions(minAge, maxAge int, namePattern string) ([]*User, error) {
    o := orm.NewOrm()
    var users []*User
    
    qs := o.QueryTable("user")
    
    // 年齢範囲でフィルタ
    if minAge > 0 {
        qs = qs.Filter("age__gte", minAge)
    }
    if maxAge > 0 {
        qs = qs.Filter("age__lte", maxAge)
    }
    
    // 名前の部分一致
    if namePattern != "" {
        qs = qs.Filter("name__icontains", namePattern)
    }
    
    // アクティブユーザーのみ
    qs = qs.Filter("is_active", true)
    
    // ソート
    qs = qs.OrderBy("-created")
    
    // 結果取得
    _, err := qs.All(&users)
    if err != nil {
        return nil, err
    }
    
    return users, nil
}

// 集約クエリ
func GetUserStatistics() (map[string]interface{}, error) {
    o := orm.NewOrm()
    
    stats := make(map[string]interface{})
    
    // 総ユーザー数
    totalUsers, err := o.QueryTable("user").Count()
    if err != nil {
        return nil, err
    }
    stats["total_users"] = totalUsers
    
    // アクティブユーザー数
    activeUsers, err := o.QueryTable("user").Filter("is_active", true).Count()
    if err != nil {
        return nil, err
    }
    stats["active_users"] = activeUsers
    
    // 平均年齢(Raw SQLを使用)
    var avgAge float64
    err = o.Raw("SELECT AVG(age) FROM users WHERE age > 0").QueryRow(&avgAge)
    if err != nil {
        return nil, err
    }
    stats["average_age"] = avgAge
    
    return stats, nil
}

// リレーション込みの取得
func GetUserWithProfile(userID int) (*User, error) {
    o := orm.NewOrm()
    
    user := &User{Id: userID}
    err := o.Read(user)
    if err != nil {
        return nil, err
    }
    
    // プロフィール情報を読み込み
    _, err = o.LoadRelated(user, "Profile")
    if err != nil {
        return nil, err
    }
    
    return user, nil
}

func GetUsersWithPosts() ([]*User, error) {
    o := orm.NewOrm()
    var users []*User
    
    // ユーザーと投稿を一緒に取得
    _, err := o.QueryTable("user").RelatedSel().All(&users)
    if err != nil {
        return nil, err
    }
    
    // 各ユーザーの投稿を読み込み
    for _, user := range users {
        _, err = o.LoadRelated(user, "Posts")
        if err != nil {
            return nil, err
        }
    }
    
    return users, nil
}

// 条件付きアップデート
func UpdateUsersStatus(condition map[string]interface{}, status bool) (int64, error) {
    o := orm.NewOrm()
    
    qs := o.QueryTable("user")
    
    // 動的条件適用
    for key, value := range condition {
        qs = qs.Filter(key, value)
    }
    
    // 一括更新
    return qs.Update(orm.Params{
        "is_active": status,
        "updated":   time.Now(),
    })
}

// ページネーション
func GetUsersPaginated(page, pageSize int) ([]*User, int64, error) {
    o := orm.NewOrm()
    var users []*User
    
    qs := o.QueryTable("user").Filter("is_active", true).OrderBy("-created")
    
    // 総件数取得
    total, err := qs.Count()
    if err != nil {
        return nil, 0, err
    }
    
    // ページネーション適用
    offset := (page - 1) * pageSize
    _, err = qs.Limit(pageSize, offset).All(&users)
    if err != nil {
        return nil, 0, err
    }
    
    return users, total, nil
}

リレーション操作

// 1対1関係の操作
func CreateUserWithProfile(name, email, bio string) (*User, error) {
    o := orm.NewOrm()
    
    // トランザクション開始
    err := o.Begin()
    if err != nil {
        return nil, err
    }
    
    // ユーザー作成
    user := &User{
        Name:  name,
        Email: email,
    }
    
    id, err := o.Insert(user)
    if err != nil {
        o.Rollback()
        return nil, err
    }
    user.Id = int(id)
    
    // プロフィール作成
    profile := &Profile{
        Bio:  bio,
        User: user,
    }
    
    _, err = o.Insert(profile)
    if err != nil {
        o.Rollback()
        return nil, err
    }
    
    // コミット
    err = o.Commit()
    if err != nil {
        return nil, err
    }
    
    user.Profile = profile
    return user, nil
}

// 1対多関係の操作
func CreatePostForUser(userID int, title, content string) (*Post, error) {
    o := orm.NewOrm()
    
    // ユーザー存在確認
    user := &User{Id: userID}
    err := o.Read(user)
    if err != nil {
        return nil, fmt.Errorf("user not found: %v", err)
    }
    
    // 投稿作成
    post := &Post{
        Title:   title,
        Content: content,
        User:    user,
    }
    
    id, err := o.Insert(post)
    if err != nil {
        return nil, err
    }
    post.Id = int(id)
    
    return post, nil
}

// Join操作
func GetPostsWithUserInfo() ([]map[string]interface{}, error) {
    o := orm.NewOrm()
    var maps []orm.Params
    
    // Join クエリ
    _, err := o.Raw(`
        SELECT p.id, p.title, p.content, p.published, p.created,
               u.name as user_name, u.email as user_email
        FROM posts p
        INNER JOIN users u ON p.user_id = u.id
        WHERE p.published = ?
        ORDER BY p.created DESC
    `, true).Values(&maps)
    
    if err != nil {
        return nil, err
    }
    
    // map[string]interface{} に変換
    results := make([]map[string]interface{}, len(maps))
    for i, m := range maps {
        results[i] = make(map[string]interface{})
        for k, v := range m {
            results[i][k] = v
        }
    }
    
    return results, nil
}

実用例

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    
    "github.com/beego/beego/v2/client/orm"
    "github.com/beego/beego/v2/server/web"
)

// コントローラー実装
type UserController struct {
    web.Controller
}

// GET /users - ユーザー一覧取得
func (c *UserController) GetAll() {
    page, _ := strconv.Atoi(c.GetString("page", "1"))
    pageSize, _ := strconv.Atoi(c.GetString("page_size", "10"))
    
    users, total, err := GetUsersPaginated(page, pageSize)
    if err != nil {
        c.Data["json"] = map[string]interface{}{
            "error": err.Error(),
        }
        c.Ctx.ResponseWriter.WriteHeader(http.StatusInternalServerError)
        c.ServeJSON()
        return
    }
    
    c.Data["json"] = map[string]interface{}{
        "users": users,
        "total": total,
        "page":  page,
        "page_size": pageSize,
    }
    c.ServeJSON()
}

// GET /users/:id - ユーザー詳細取得
func (c *UserController) Get() {
    idStr := c.Ctx.Input.Param(":id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        c.Data["json"] = map[string]interface{}{
            "error": "Invalid user ID",
        }
        c.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
        c.ServeJSON()
        return
    }
    
    user, err := GetUserWithProfile(id)
    if err != nil {
        c.Data["json"] = map[string]interface{}{
            "error": "User not found",
        }
        c.Ctx.ResponseWriter.WriteHeader(http.StatusNotFound)
        c.ServeJSON()
        return
    }
    
    c.Data["json"] = user
    c.ServeJSON()
}

// POST /users - ユーザー作成
func (c *UserController) Post() {
    var userRequest struct {
        Name  string `json:"name"`
        Email string `json:"email"`
        Age   int    `json:"age"`
        Bio   string `json:"bio"`
    }
    
    err := json.Unmarshal(c.Ctx.Input.RequestBody, &userRequest)
    if err != nil {
        c.Data["json"] = map[string]interface{}{
            "error": "Invalid JSON format",
        }
        c.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
        c.ServeJSON()
        return
    }
    
    // バリデーション
    if userRequest.Name == "" || userRequest.Email == "" {
        c.Data["json"] = map[string]interface{}{
            "error": "Name and email are required",
        }
        c.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
        c.ServeJSON()
        return
    }
    
    // ユーザー作成
    user, err := CreateUserWithProfile(userRequest.Name, userRequest.Email, userRequest.Bio)
    if err != nil {
        c.Data["json"] = map[string]interface{}{
            "error": err.Error(),
        }
        c.Ctx.ResponseWriter.WriteHeader(http.StatusInternalServerError)
        c.ServeJSON()
        return
    }
    
    c.Data["json"] = user
    c.Ctx.ResponseWriter.WriteHeader(http.StatusCreated)
    c.ServeJSON()
}

// データベース初期化
func InitDatabase() {
    // 開発環境でのテストデータ作成
    o := orm.NewOrm()
    
    // サンプルユーザー作成
    users := []*User{
        {Name: "Alice Johnson", Email: "[email protected]", Age: 28},
        {Name: "Bob Smith", Email: "[email protected]", Age: 35},
        {Name: "Carol Brown", Email: "[email protected]", Age: 22},
    }
    
    for _, user := range users {
        _, err := o.Insert(user)
        if err != nil {
            log.Printf("Failed to create user %s: %v", user.Name, err)
        } else {
            log.Printf("Created user: %s", user.Name)
        }
    }
}

// メイン関数
func main() {
    // ルーティング設定
    web.Router("/users", &UserController{}, "get:GetAll;post:Post")
    web.Router("/users/:id", &UserController{}, "get:Get")
    
    // データベース初期化
    InitDatabase()
    
    log.Println("Server starting on :8080")
    web.Run(":8080")
}