GoCasts آموزش Go به زبان ساده

بیش از ۱۰۰۰ شرکت‌کننده یادگیری Go و Backend رو از امروز شروع کن
ثبت‌نام دوره + تیم‌سازی

اتصال به PostgreSQL در گولنگ - آموزش دیتابیس در Go

در این مقاله، نحوه اتصال و کار با PostgreSQL در Go را یاد می‌گیریم. هم با database/sql استاندارد و هم با GORM ORM.

پیش‌نیازها

  • نصب Go
  • PostgreSQL نصب شده
  • آشنایی با SQL پایه

راه‌اندازی پروژه

mkdir go-postgres && cd go-postgres
go mod init github.com/username/go-postgres

# درایور PostgreSQL
go get github.com/lib/pq

# یا pgx (سریع‌تر و مدرن‌تر)
go get github.com/jackc/pgx/v5

روش ۱: database/sql استاندارد

اتصال به دیتابیس

package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/lib/pq"
)

const (
    host     = "localhost"
    port     = 5432
    user     = "postgres"
    password = "your-password"
    dbname   = "testdb"
)

func main() {
    // Connection string
    connStr := fmt.Sprintf(
        "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
        host, port, user, password, dbname,
    )

    // اتصال
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        log.Fatal("خطا در اتصال:", err)
    }
    defer db.Close()

    // تست اتصال
    if err := db.Ping(); err != nil {
        log.Fatal("خطا در ping:", err)
    }

    fmt.Println("اتصال موفق به PostgreSQL!")
}

ایجاد جدول

func createTable(db *sql.DB) error {
    query := `
    CREATE TABLE IF NOT EXISTS users (
        id SERIAL PRIMARY KEY,
        name VARCHAR(100) NOT NULL,
        email VARCHAR(100) UNIQUE NOT NULL,
        age INTEGER,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )`

    _, err := db.Exec(query)
    return err
}

Insert (افزودن)

func insertUser(db *sql.DB, name, email string, age int) (int, error) {
    query := `
    INSERT INTO users (name, email, age)
    VALUES ($1, $2, $3)
    RETURNING id`

    var id int
    err := db.QueryRow(query, name, email, age).Scan(&id)
    if err != nil {
        return 0, err
    }

    return id, nil
}

// استفاده
id, err := insertUser(db, "علی رضایی", "ali@example.com", 25)
if err != nil {
    log.Println("خطا در درج:", err)
}
fmt.Println("کاربر با ID", id, "ایجاد شد")

Select (خواندن)

type User struct {
    ID        int
    Name      string
    Email     string
    Age       int
    CreatedAt time.Time
}

// خواندن یک رکورد
func getUserByID(db *sql.DB, id int) (*User, error) {
    query := `SELECT id, name, email, age, created_at FROM users WHERE id = $1`

    user := &User{}
    err := db.QueryRow(query, id).Scan(
        &user.ID,
        &user.Name,
        &user.Email,
        &user.Age,
        &user.CreatedAt,
    )

    if err == sql.ErrNoRows {
        return nil, fmt.Errorf("کاربر یافت نشد")
    }
    if err != nil {
        return nil, err
    }

    return user, nil
}

// خواندن همه رکوردها
func getAllUsers(db *sql.DB) ([]User, error) {
    query := `SELECT id, name, email, age, created_at FROM users ORDER BY id`

    rows, err := db.Query(query)
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var users []User
    for rows.Next() {
        var u User
        if err := rows.Scan(&u.ID, &u.Name, &u.Email, &u.Age, &u.CreatedAt); err != nil {
            return nil, err
        }
        users = append(users, u)
    }

    return users, rows.Err()
}

Update (به‌روزرسانی)

func updateUser(db *sql.DB, id int, name string, age int) error {
    query := `UPDATE users SET name = $1, age = $2 WHERE id = $3`

    result, err := db.Exec(query, name, age, id)
    if err != nil {
        return err
    }

    rowsAffected, _ := result.RowsAffected()
    if rowsAffected == 0 {
        return fmt.Errorf("کاربر یافت نشد")
    }

    return nil
}

Delete (حذف)

func deleteUser(db *sql.DB, id int) error {
    query := `DELETE FROM users WHERE id = $1`

    result, err := db.Exec(query, id)
    if err != nil {
        return err
    }

    rowsAffected, _ := result.RowsAffected()
    if rowsAffected == 0 {
        return fmt.Errorf("کاربر یافت نشد")
    }

    return nil
}

Prepared Statements

برای کوئری‌های تکراری - امن‌تر و سریع‌تر:

func bulkInsert(db *sql.DB, users []User) error {
    stmt, err := db.Prepare(`
        INSERT INTO users (name, email, age)
        VALUES ($1, $2, $3)
    `)
    if err != nil {
        return err
    }
    defer stmt.Close()

    for _, user := range users {
        _, err := stmt.Exec(user.Name, user.Email, user.Age)
        if err != nil {
            return err
        }
    }

    return nil
}

Transactions

func transferMoney(db *sql.DB, fromID, toID int, amount float64) error {
    // شروع transaction
    tx, err := db.Begin()
    if err != nil {
        return err
    }

    // در صورت خطا، rollback
    defer func() {
        if err != nil {
            tx.Rollback()
        }
    }()

    // کسر از حساب مبدا
    _, err = tx.Exec(
        `UPDATE accounts SET balance = balance - $1 WHERE id = $2`,
        amount, fromID,
    )
    if err != nil {
        return err
    }

    // اضافه به حساب مقصد
    _, err = tx.Exec(
        `UPDATE accounts SET balance = balance + $1 WHERE id = $2`,
        amount, toID,
    )
    if err != nil {
        return err
    }

    // تأیید transaction
    return tx.Commit()
}

روش ۲: با GORM ORM

go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres

تعریف Model

package main

import (
    "time"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

type User struct {
    ID        uint           `gorm:"primaryKey"`
    Name      string         `gorm:"size:100;not null"`
    Email     string         `gorm:"size:100;uniqueIndex;not null"`
    Age       int
    Active    bool           `gorm:"default:true"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"` // Soft delete
}

type Post struct {
    ID        uint `gorm:"primaryKey"`
    Title     string
    Content   string
    UserID    uint
    User      User `gorm:"foreignKey:UserID"` // رابطه
    CreatedAt time.Time
}

اتصال با GORM

func main() {
    dsn := "host=localhost user=postgres password=secret dbname=testdb port=5432 sslmode=disable"

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("خطا در اتصال:", err)
    }

    // Migration خودکار
    db.AutoMigrate(&User{}, &Post{})

    fmt.Println("اتصال موفق با GORM!")
}

CRUD با GORM

// Create
user := User{Name: "علی", Email: "ali@example.com", Age: 25}
result := db.Create(&user)
fmt.Println("ID:", user.ID, "Rows:", result.RowsAffected)

// Read - یک رکورد
var user User
db.First(&user, 1)                    // با ID
db.First(&user, "email = ?", "ali@example.com")  // با شرط

// Read - همه
var users []User
db.Find(&users)
db.Where("age > ?", 20).Find(&users)

// Update
db.Model(&user).Update("age", 26)
db.Model(&user).Updates(User{Name: "علی رضایی", Age: 26})
db.Model(&user).Updates(map[string]interface{}{"name": "علی", "age": 27})

// Delete
db.Delete(&user, 1)  // Soft delete اگر DeletedAt دارید
db.Unscoped().Delete(&user, 1)  // حذف واقعی

کوئری‌های پیشرفته GORM

// Select خاص
var users []User
db.Select("name", "email").Find(&users)

// Order و Limit
db.Order("created_at desc").Limit(10).Find(&users)

// Joins
type Result struct {
    UserName  string
    PostTitle string
}
var results []Result
db.Table("users").
    Select("users.name as user_name, posts.title as post_title").
    Joins("left join posts on posts.user_id = users.id").
    Scan(&results)

// Preload (روابط)
var usersWithPosts []User
db.Preload("Posts").Find(&usersWithPosts)

// Transaction
db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&user1).Error; err != nil {
        return err
    }
    if err := tx.Create(&user2).Error; err != nil {
        return err
    }
    return nil
})

Connection Pool

func setupDB() *sql.DB {
    db, _ := sql.Open("postgres", connStr)

    // تنظیمات pool
    db.SetMaxOpenConns(25)                  // حداکثر اتصال باز
    db.SetMaxIdleConns(5)                   // حداکثر اتصال بیکار
    db.SetConnMaxLifetime(5 * time.Minute)  // عمر هر اتصال

    return db
}

ساختار پروژه پیشنهادی

project/
├── main.go
├── config/
│   └── database.go
├── models/
│   └── user.go
├── repository/
│   └── user_repository.go
├── handlers/
│   └── user_handler.go
└── go.mod

Repository Pattern

repository/user_repository.go:

package repository

type UserRepository interface {
    Create(user *User) error
    GetByID(id int) (*User, error)
    GetAll() ([]User, error)
    Update(user *User) error
    Delete(id int) error
}

type userRepository struct {
    db *sql.DB
}

func NewUserRepository(db *sql.DB) UserRepository {
    return &userRepository{db: db}
}

func (r *userRepository) Create(user *User) error {
    // implementation
}

نکات امنیتی

  1. همیشه از Prepared Statements استفاده کنید (جلوگیری از SQL Injection)
  2. رمز عبور را در کد ننویسید - از متغیر محیطی استفاده کنید
  3. از SSL برای اتصال استفاده کنید در production
  4. Connection pool را تنظیم کنید

قدم‌های بعدی

منابع

بیش از ۱۰۰۰ شرکت‌کننده یادگیری Go و Backend رو از امروز شروع کن
ثبت‌نام دوره + تیم‌سازی