Chuyển tới nội dung chính

📦 Kho Lưu Trữ - Database

Trong nhà hàng, chúng ta cần một kho lưu trữ để quản lý thông tin về món ăn, đơn hàng, khách hàng. Trong Go, database là nơi lưu trữ dữ liệu lâu dài của ứng dụng.

🎯 Kho Lưu Trữ Là Gì?

Database giống như kho lưu trữ của nhà hàng - nơi ghi chép tất cả thông tin quan trọng:

// Kết nối đến kho lưu trữ (database)
import (
"database/sql"
_ "github.com/lib/pq" // Driver PostgreSQL
)

func main() {
// Mở cửa kho
db, err := sql.Open("postgres", "user=admin dbname=restaurant")
if err != nil {
panic(err)
}
defer db.Close() // Đóng cửa kho khi xong việc
}

Giải thích theo nhà hàng:

  • sql.Open() = Mở cửa kho lưu trữ
  • defer db.Close() = Nhớ đóng cửa kho khi tan ca
  • Driver = Chìa khóa mở cửa kho (mỗi loại kho cần chìa khóa riêng)

📝 Ngôn Ngữ Quản Lý Kho - SQL

SQL là ngôn ngữ quản lý kho - cách chúng ta giao tiếp với kho lưu trữ:

// Tạo sổ ghi chép món ăn
func createMenuTable(db *sql.DB) error {
query := `
CREATE TABLE IF NOT EXISTS menu (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2),
category VARCHAR(50)
)`

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

Các lệnh quản lý kho cơ bản:

  • CREATE TABLE = Tạo sổ ghi chép mới
  • INSERT = Thêm món vào kho
  • SELECT = Xem thông tin món
  • UPDATE = Cập nhật thông tin món
  • DELETE = Xóa món khỏi kho

➕ Thêm Món Vào Kho - INSERT

Thêm món ăn mới vào kho lưu trữ:

type MenuItem struct {
ID int
Name string
Price float64
Category string
}

// Thêm món mới vào menu
func addMenuItem(db *sql.DB, item MenuItem) error {
query := `
INSERT INTO menu (name, price, category)
VALUES ($1, $2, $3)`

_, err := db.Exec(query, item.Name, item.Price, item.Category)
if err != nil {
return fmt.Errorf("không thể thêm món: %v", err)
}

fmt.Printf("✅ Đã thêm món %s vào kho\n", item.Name)
return nil
}

// Sử dụng
func main() {
db, _ := sql.Open("postgres", "...")
defer db.Close()

pho := MenuItem{
Name: "Phở Bò",
Price: 50000,
Category: "Món chính",
}

addMenuItem(db, pho) // Thêm phở vào kho
}

Giải thích:

  • INSERT INTO = Thêm món vào sổ ghi chép
  • $1, $2, $3 = Vị trí điền thông tin (tránh lỗi bảo mật)
  • db.Exec() = Thực hiện lệnh thêm món

🔍 Xem Thông Tin Món - SELECT

Tìm kiếm và xem thông tin món ăn trong kho:

// Xem tất cả món ăn
func getAllMenuItems(db *sql.DB) ([]MenuItem, error) {
query := "SELECT id, name, price, category FROM menu"

rows, err := db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close() // Đóng sổ khi đọc xong

var items []MenuItem

for rows.Next() { // Đọc từng dòng trong sổ
var item MenuItem
err := rows.Scan(&item.ID, &item.Name, &item.Price, &item.Category)
if err != nil {
return nil, err
}
items = append(items, item)
}

return items, nil
}

// Tìm món theo tên
func getMenuItemByName(db *sql.DB, name string) (*MenuItem, error) {
query := "SELECT id, name, price, category FROM menu WHERE name = $1"

var item MenuItem
err := db.QueryRow(query, name).Scan(
&item.ID, &item.Name, &item.Price, &item.Category,
)

if err == sql.ErrNoRows {
return nil, fmt.Errorf("không tìm thấy món %s", name)
}

return &item, err
}

Giải thích:

  • db.Query() = Mở sổ và đọc nhiều dòng
  • db.QueryRow() = Đọc một dòng cụ thể
  • rows.Next() = Lật sang trang tiếp theo
  • rows.Scan() = Ghi thông tin vào biến

✏️ Cập Nhật Thông Tin - UPDATE

Sửa đổi thông tin món ăn đã có:

// Cập nhật giá món ăn
func updatePrice(db *sql.DB, name string, newPrice float64) error {
query := "UPDATE menu SET price = $1 WHERE name = $2"

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

rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("không tìm thấy món %s để cập nhật", name)
}

fmt.Printf("✅ Đã cập nhật giá món %s thành %.0f đồng\n", name, newPrice)
return nil
}

// Sử dụng
updatePrice(db, "Phở Bò", 55000) // Tăng giá phở lên 55k

Lưu ý:

  • result.RowsAffected() = Kiểm tra có món nào được cập nhật không
  • Luôn kiểm tra kết quả để đảm bảo cập nhật thành công

🗑️ Xóa Món Khỏi Kho - DELETE

Xóa món ăn khỏi menu:

// Xóa món ăn
func deleteMenuItem(db *sql.DB, name string) error {
query := "DELETE FROM menu WHERE name = $1"

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

rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("không tìm thấy món %s để xóa", name)
}

fmt.Printf("🗑️ Đã xóa món %s khỏi menu\n", name)
return nil
}

Cảnh báo: Xóa dữ liệu là hành động không thể hoàn tác!

🔄 Giao Dịch Nhập Kho - Transactions

Transaction giống như phiếu giao dịch nhập kho - đảm bảo tất cả thao tác đều thành công hoặc đều bị hủy:

// Xử lý đơn hàng (phải thành công toàn bộ)
func processOrder(db *sql.DB, customerID int, items []MenuItem) error {
// Bắt đầu giao dịch
tx, err := db.Begin()
if err != nil {
return err
}

// Nếu có lỗi, hủy toàn bộ giao dịch
defer func() {
if err != nil {
tx.Rollback()
fmt.Println("❌ Giao dịch thất bại, hoàn tác tất cả")
}
}()

// Bước 1: Tạo đơn hàng
var orderID int
err = tx.QueryRow(
"INSERT INTO orders (customer_id, total) VALUES ($1, $2) RETURNING id",
customerID, calculateTotal(items),
).Scan(&orderID)
if err != nil {
return err
}

// Bước 2: Thêm từng món vào đơn
for _, item := range items {
_, err = tx.Exec(
"INSERT INTO order_items (order_id, item_id, quantity) VALUES ($1, $2, $3)",
orderID, item.ID, 1,
)
if err != nil {
return err
}
}

// Bước 3: Xác nhận giao dịch
err = tx.Commit()
if err != nil {
return err
}

fmt.Printf("✅ Đơn hàng #%d hoàn tất\n", orderID)
return nil
}

func calculateTotal(items []MenuItem) float64 {
total := 0.0
for _, item := range items {
total += item.Price
}
return total
}

Giải thích giao dịch:

  • tx.Begin() = Bắt đầu phiếu giao dịch
  • tx.Commit() = Xác nhận và hoàn tất giao dịch
  • tx.Rollback() = Hủy bỏ toàn bộ giao dịch nếu có lỗi
  • Đảm bảo: Tất cả thao tác thành công hoặc không có gì thay đổi

🤖 Người Quản Lý Kho Tự Động - ORM

ORM (Object-Relational Mapping) như người quản lý kho tự động - giúp làm việc với database dễ dàng hơn:

// Sử dụng GORM - ORM phổ biến cho Go
import (
"gorm.io/gorm"
"gorm.io/driver/postgres"
)

// Model tự động ánh xạ với bảng
type MenuItem struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100"`
Price float64
Category string `gorm:"size:50"`
}

func main() {
// Kết nối database
dsn := "host=localhost user=admin dbname=restaurant"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}

// Tự động tạo bảng
db.AutoMigrate(&MenuItem{})

// CRUD đơn giản hơn nhiều!

// Thêm món
pho := MenuItem{Name: "Phở Bò", Price: 50000, Category: "Món chính"}
db.Create(&pho)

// Tìm món
var item MenuItem
db.First(&item, "name = ?", "Phở Bò")

// Cập nhật
db.Model(&item).Update("price", 55000)

// Xóa
db.Delete(&item)

// Tìm tất cả món
var items []MenuItem
db.Where("category = ?", "Món chính").Find(&items)

fmt.Printf("Tìm thấy %d món chính\n", len(items))
}

Ưu điểm ORM:

  • Code ngắn gọn, dễ đọc hơn SQL thuần
  • Tự động tạo bảng từ struct
  • An toàn hơn (tránh SQL injection)
  • Dễ chuyển đổi giữa các loại database

Nhược điểm:

  • Hiệu suất có thể kém hơn SQL thuần
  • Khó tối ưu với truy vấn phức tạp

📚 Tổng Kết

Khái niệmVí dụ Nhà HàngCode Go
DatabaseKho lưu trữsql.Open()
TableSổ ghi chépCREATE TABLE
INSERTThêm món vào khodb.Exec("INSERT...")
SELECTXem thông tin móndb.Query("SELECT...")
UPDATECập nhật móndb.Exec("UPDATE...")
DELETEXóa móndb.Exec("DELETE...")
TransactionGiao dịch nhập khotx.Begin(), tx.Commit()
ORMQuản lý tự độngGORM, Ent, ...

Các package phổ biến:

  • database/sql - Thư viện chuẩn
  • GORM - ORM phổ biến nhất
  • sqlx - Mở rộng database/sql
  • Ent - ORM hiện đại từ Facebook

Lời khuyên:

  • Luôn đóng connection: defer db.Close()
  • Dùng prepared statements để tránh SQL injection
  • Sử dụng transaction cho thao tác nhiều bước
  • Cân nhắc ORM cho dự án lớn, SQL thuần cho tối ưu

Tiếp theo: 🚀 Triển Khai - Deployment - Mang nhà hàng code ra thế giới thực!