📦 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ớiINSERT= Thêm món vào khoSELECT= Xem thông tin mónUPDATE= Cập nhật thông tin mónDELETE= 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òngdb.QueryRow()= Đọc một dòng cụ thểrows.Next()= Lật sang trang tiếp theorows.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ịchtx.Commit()= Xác nhận và hoàn tất giao dịchtx.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ệm | Ví dụ Nhà Hàng | Code Go |
|---|---|---|
| Database | Kho lưu trữ | sql.Open() |
| Table | Sổ ghi chép | CREATE TABLE |
| INSERT | Thêm món vào kho | db.Exec("INSERT...") |
| SELECT | Xem thông tin món | db.Query("SELECT...") |
| UPDATE | Cập nhật món | db.Exec("UPDATE...") |
| DELETE | Xóa món | db.Exec("DELETE...") |
| Transaction | Giao dịch nhập kho | tx.Begin(), tx.Commit() |
| ORM | Quản lý tự động | GORM, Ent, ... |
Các package phổ biến:
database/sql- Thư viện chuẩnGORM- ORM phổ biến nhấtsqlx- Mở rộng database/sqlEnt- 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!