database/sql package
Ихэнх бодит программ өгөгдлийг хадгалдаг — хэрэглэгчид, захиалгууд, нийтлэлүүд. Эдгээр өгөгдлийг хадгалахад database ашигладаг. Go-д database/sql гэсэн стандарт package байдаг бөгөөд энэ нь PostgreSQL, MySQL, SQLite зэрэг бүх SQL database-тэй нэгдсэн интерфэйсээр ажиллах боломжийг олгодог. Энэ хичээлд SQLite ашиглан суурь ойлголтуудыг судална.
database/sql-ийг эхлүүлэх
database/sql нь зөвхөн интерфэйс тодорхойлдог. Тодорхой database-тэй ажиллахын тулд driver суулгах хэрэгтэй. SQLite-д modernc.org/sqlite driver ашиглана:
go get modernc.org/sqlite
package main
import (
"database/sql"
"fmt"
"log"
_ "modernc.org/sqlite" // driver-ийг бүртгэх, гэхдээ шууд ашиглахгүй
)
func main() {
// Database нээх (файл байхгүй бол үүснэ)
db, err := sql.Open("sqlite", "myapp.db")
if err != nil {
log.Fatal("Database нээхэд алдаа:", err)
}
defer db.Close()
// Холболт амжилттай эсэхийг шалгах
if err := db.Ping(); err != nil {
log.Fatal("Database холбогдохгүй байна:", err)
}
fmt.Println("Database-тэй амжилттай холбогдлоо!")
}
_ ашиглан driver import хийх нь Go-д өргөн хэрэглэгддэг — package-ийн init() функцийг ажиллуулж driver-ийг бүртгэдэг.
Хүснэгт үүсгэх ба өгөгдөл оруулах
db.Exec() нь өгөгдөл буцаадаггүй SQL команд (CREATE, INSERT, UPDATE, DELETE) ажиллуулахад хэрэглэгддэг:
func хүснэгтҮүсгэх(db *sql.DB) error {
query := `
CREATE TABLE IF NOT EXISTS хэрэглэгчид (
id INTEGER PRIMARY KEY AUTOINCREMENT,
нэр TEXT NOT NULL,
и_мэйл TEXT UNIQUE NOT NULL,
xp INTEGER DEFAULT 0
)
`
_, err := db.Exec(query)
return err
}
func хэрэглэгчНэмэх(db *sql.DB, нэр, иМэйл string) (int64, error) {
// ? тэмдэг нь SQL injection-оос хамгаалдаг — заавал ашигла!
result, err := db.Exec(
"INSERT INTO хэрэглэгчид (нэр, и_мэйл) VALUES (?, ?)",
нэр, иМэйл,
)
if err != nil {
return 0, err
}
// Шинээр үүссэн мөрийн ID авах
id, err := result.LastInsertId()
return id, err
}
func main() {
db, _ := sql.Open("sqlite", "myapp.db")
defer db.Close()
хүснэгтҮүсгэх(db)
id, err := хэрэглэгчНэмэх(db, "Болд", "bold@example.mn")
if err != nil {
log.Println("Алдаа:", err)
return
}
fmt.Printf("Шинэ хэрэглэгч нэмэгдлээ, ID: %d\n", id)
}
Чухал: SQL query-д утгыг шууд string болгон бүү нэгтгэ — ? placeholder ашигла. Энэ нь SQL injection халдлагаас хамгаалдаг.
Өгөгдөл унших — Query ба Scan
Өгөгдөл уншихад db.Query() болон db.QueryRow() ашигладаг:
type Хэрэглэгч struct {
ID int
Нэр string
ИМэйл string
XP int
}
// Бүх хэрэглэгчдийг авах
func бүхХэрэглэгчид(db *sql.DB) ([]Хэрэглэгч, error) {
rows, err := db.Query("SELECT id, нэр, и_мэйл, xp FROM хэрэглэгчид ORDER BY xp DESC")
if err != nil {
return nil, err
}
defer rows.Close() // ← Заавал хаах!
var жагсаалт []Хэрэглэгч
for rows.Next() {
var х Хэрэглэгч
err := rows.Scan(&х.ID, &х.Нэр, &х.ИМэйл, &х.XP)
if err != nil {
return nil, err
}
жагсаалт = append(жагсаалт, х)
}
return жагсаалт, rows.Err()
}
// Нэг хэрэглэгч ID-аар авах
func хэрэглэгчАвах(db *sql.DB, id int) (Хэрэглэгч, error) {
var х Хэрэглэгч
err := db.QueryRow(
"SELECT id, нэр, и_мэйл, xp FROM хэрэглэгчид WHERE id = ?", id,
).Scan(&х.ID, &х.Нэр, &х.ИМэйл, &х.XP)
if err == sql.ErrNoRows {
return х, fmt.Errorf("ID %d-тэй хэрэглэгч олдсонгүй", id)
}
return х, err
}
rows.Scan() нь SQL-ийн баганын утгыг Go-ийн хувьсагч руу хуулдаг. Баганын дараалал болон Scan-ий аргументын дараалал таарах ёстой.
Өгөгдөл шинэчлэх ба устгах
// XP нэмэх
func xpНэмэх(db *sql.DB, id, нэмэх int) error {
result, err := db.Exec(
"UPDATE хэрэглэгчид SET xp = xp + ? WHERE id = ?",
нэмэх, id,
)
if err != nil {
return err
}
// Хэдэн мөр өөрчлөгдсөн шалгах
тоо, _ := result.RowsAffected()
if тоо == 0 {
return fmt.Errorf("ID %d-тэй хэрэглэгч олдсонгүй", id)
}
return nil
}
// Хэрэглэгч устгах
func хэрэглэгчУстгах(db *sql.DB, id int) error {
_, err := db.Exec("DELETE FROM хэрэглэгчид WHERE id = ?", id)
return err
}
Transaction — олон үйлдлийг хамтад нь
Хэрэв олон SQL команд бүгдээрээ амжилттай байх шаардлагатай бол transaction ашигладаг. Нэг нь амжилтгүй болбол бүгд буцаагдана:
func XPШилжүүлэх(db *sql.DB, эхID, зорьсонID, хэмжээ int) error {
// Transaction эхлүүлэх
tx, err := db.Begin()
if err != nil {
return err
}
// Ямар нэг алдаа гарвал бүгдийг буцаах
defer tx.Rollback()
// Эхний хэрэглэгчээс XP хасах
_, err = tx.Exec("UPDATE хэрэглэгчид SET xp = xp - ? WHERE id = ?", хэмжээ, эхID)
if err != nil {
return err // Rollback дуудагдана
}
// Хоёрдахь хэрэглэгчид XP нэмэх
_, err = tx.Exec("UPDATE хэрэглэгчид SET xp = xp + ? WHERE id = ?", хэмжээ, зорьсонID)
if err != nil {
return err // Rollback дуудагдана
}
// Бүх зүйл амжилттай → баталгаажуулах
return tx.Commit()
}
Transaction нь банкны шилжүүлэг, тасалбар худалдаа гэх мэт чухал үйлдлүүдэд зайлшгүй шаардлагатай.
Дараагийн хичээлд:
Database-тэй ажиллах кодоо хэрхэн найдвартай болгох вэ? Дараагийн хичээлд Go-ийн тест бичих үндсийг судална. testing package ашиглан функцуудаа автоматаар шалгах нь алдааг эрт илрүүлэх хамгийн үр дүнтэй арга юм.