اتصال به PostgreSQL در گولنگ - آموزش دیتابیس در Go
2025/11/24در این مقاله، نحوه اتصال و کار با 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.modRepository 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
}نکات امنیتی
- همیشه از Prepared Statements استفاده کنید (جلوگیری از SQL Injection)
- رمز عبور را در کد ننویسید - از متغیر محیطی استفاده کنید
- از SSL برای اتصال استفاده کنید در production
- Connection pool را تنظیم کنید