Skip to main content

ใช้ Go ต่อ SQL Database

มาลองต่อผ่าน go กัน

Ref: https://pkg.go.dev/database/sql

ลง package

  • package ที่เราจะใช้คือ database/sql ซึ่งเป็น standard library อยู่แล้ว
  • แต่ต้องลง driver เพื่อเป็นตัวแทนในการพูดคุยกับ PostgreSQL เพิ่ม
go get github.com/lib/pq
  • ทำการต่อไปยัง PostgreSQL + handle error การ connect
package main

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

_ "github.com/lib/pq"
)

const (
host = "localhost" // or the Docker service name if running in another container
port = 5432 // default PostgreSQL port
user = "myuser" // as defined in docker-compose.yml
password = "mypassword" // as defined in docker-compose.yml
dbname = "mydatabase" // as defined in docker-compose.yml
)

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

// Open a connection
db, err := sql.Open("postgres", psqlInfo)
if err != nil {
log.Fatal(err)
}
defer db.Close()

// Check the connection
err = db.Ping()
if err != nil {
log.Fatal(err)
}

fmt.Println("Successfully connected!")
}

CRUD ผ่าน Go

ทำพื้นฐาน function CRUD (ย้าย query มาทำผ่าน Go)

สิ่งที่ปกติเราต้องทำคือ

  1. สร้าง function สำหรับคุยกับ SQL ในแต่ละเคสโดยส่งผ่านตัวแปร sql.DB เป็นตัวแทนในการพูดคุย
  2. (optional) ประกาศ​ Struct ที่เป็นตัวแทนในการรับข้อมูล

ตัวอย่าง

  1. CRUD Function

Create

func createProduct(db *sql.DB, name string, price int, category string, quantity int) (int, error) {
var id int
err := db.QueryRow(`INSERT INTO products(name, price, category, quantity) VALUES($1, $2, $3, $4) RETURNING id;`, name, price, category, quantity).Scan(&id)
if err != nil {
return 0, err
}
return id, nil
}

Read

type Product struct {
ID int
Name string
Price int
Category string
Quantity int
}

func getProduct(db *sql.DB, id int) (Product, error) {
var p Product
row := db.QueryRow(`SELECT id, name, price, category, quantity FROM products WHERE id = $1;`, id)
err := row.Scan(&p.ID, &p.Name, &p.Price, &p.Category, &p.Quantity)
if err != nil {
return Product{}, err
}
return p, nil
}

Update

func updateProduct(db *sql.DB, id int, name string, price int, category string, quantity int) error {
_, err := db.Exec(`UPDATE products SET name = $1, price = $2, category = $3, quantity = $4 WHERE id = $5;`, name, price, category, quantity, id)
return err
}

Delete

func deleteProduct(db *sql.DB, id int) error {
_, err := db.Exec(`DELETE FROM products WHERE id = $1;`, id)
return err
}

Note

  • Exec = เป็น method ที่ใช้สำหรับการ execute SQL โดยไม่มีการ return rows กลับคืนมา (เช่น INSERT, UPDATE, DELETE)
  • QueryRow = เป็น method ที่ใช้สำหรับ query SQL เพื่อดึงข้อมูลกลับมา (เป็นข้อมูลตัวเดียว) ปกติจะใช้กับตระกูลของ SELECT

Read - หลาย Item

ต้องใช้คำสั่ง Query ในการดึงข้อมูล

  • Query คือคำสั่งที่ใช้สำหรับการดึงข้อมูลแบบหลาย rows (ในกรณีที่เรา expected ว่า Query นั้นได้ข้อมูลมามากกว่า 1 rows) ซึ่งปกติ จะใช้กับตระกูลของ SELECT
type Product struct {
ID int
Name string
Price int
Category string
Quantity int
}

func getProducts(db *sql.DB) ([]Product, error) {
rows, err := db.Query("SELECT id, name, price, category, quantity FROM products")
if err != nil {
return nil, err
}
defer rows.Close()

var products []Product
for rows.Next() {
var p Product
err := rows.Scan(&p.ID, &p.Name, &p.Price, &p.Category, &p.Quantity)
if err != nil {
return nil, err
}
products = append(products, p)
}

// Check for errors from iterating over rows
if err = rows.Err(); err != nil {
return nil, err
}

return products, nil
}

เพิ่มเติม

  • defer คือ คำสั่งที่จะทำงานก่อนคำสั่งสุดท้ายใน program (หรือ function นั้นๆ) โดยปกติมันจะใช้สำหรับคำสั่ง cleanup เพื่อปิด process ให้ครบก่อนที่จะหยุดทำงาน

ตัวอย่าง

func main() {
fmt.Println(test())
fmt.Println("Successfully connected!")
}

func test() string {
defer fmt.Println("Before!")
fmt.Println("After!")
return "test"
}

Result

After!
Before!
test
Successfully connected!

เพิ่มผ่าน JOIN

  • ใช้ Query เหมือนเดิมแค่เพิ่ม Join เข้าไป
type ProductWithSupplier struct {
ProductID int
ProductName string
Price int
SupplierName string
SupplierLocation string
}

func getProductsAndSuppliers(db *sql.DB) ([]ProductWithSupplier, error) {
// SQL JOIN query
query := `
SELECT
p.id AS product_id,
p.name AS product_name,
p.price,
s.name AS supplier_name,
s.location AS supplier_location
FROM
products p
INNER JOIN suppliers s
ON p.supplier_id = s.id;`

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

var products []ProductWithSupplier
for rows.Next() {
var p ProductWithSupplier
err := rows.Scan(&p.ProductID, &p.ProductName, &p.Price, &p.SupplierName, &p.SupplierLocation)
if err != nil {
return nil, err
}
products = append(products, p)
}

if err = rows.Err(); err != nil {
return nil, err
}

return products, nil
}

Transaction

Ref: https://go.dev/doc/database/execute-transactions

  • ประกาศ​ struct รับ 2 ตัวเข้ามา
type Product struct {
// define your product fields
Name string
Price int
Category string
Quantity int
SupplierID int
}

type Supplier struct {
// define your supplier fields
Name string
Location string
}
  • เพิ่มคำสั่งใช้ Transaction
func addProductAndSupplier(db *sql.DB, product Product, supplier Supplier) error {
// Start a transaction
tx, err := db.Begin()
if err != nil {
return err
}

// Rollback the transaction in case of a panic
defer tx.Rollback()

// Insert into the supplier table
supplierResult, err := tx.Exec("INSERT INTO suppliers (name, location) VALUES ($1, $2) RETURNING id", supplier.Name, supplier.Location)
if err != nil {
return err
}

// Get the last inserted ID for the supplier
supplierID, err := supplierResult.LastInsertId()
if err != nil {
return err
}

// Insert into the product table
_, err = tx.Exec("INSERT INTO products (name, price, category, quantity, supplier_id) VALUES ($1, $2, $3, $4, $5)", product.Name, product.Price, product.Category, product.Quantity, supplierID)
if err != nil {
return err
}

// Commit the transaction
return tx.Commit()
}
  • ลองเรียกใช้จาก func main()
func main() {
product := Product{
Name: "Example Product",
Price: 100,
Category: "Electronics",
Quantity: 50,
}

supplier := Supplier{
Name: "Example Supplier",
Location: "Example Location",
}

err = addProductAndSupplier(db, product, supplier)
if err != nil {
log.Fatalf("Error adding product and supplier: %v", err)
} else {
fmt.Println("Product and supplier added successfully")
}
}

Note

  • ไอเดียของ Transaction ใน go คือ เปิดผ่านคำสั่ง db.Begin()
  • เมื่อ run ทุกอย่างเรียบร้อยให้ใช้คำสั่ง tx.Commit()
  • และถ้ามีอะไรเกิดขึ้นไม่ถูกต้อง ใช้คำสั่ง tx.Rollback()
  • โดยปกติการ handle Rollback จะใช้ defer tx.Rollback() เพื่อเป็นการ run เสมอตอนจบ function (และคำสั่งจะไม่มีผลอะไรหาก run tx.Commit() ไปแล้วเรียบร้อย)