iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0

昨天成功連線資料庫之後,有透過 DB Browser for SQLite 看到 tasks.db 檔案裡面的內容,也有透過程式新增了一筆測試資料存進資料庫裡面~ 😆

所以今天,我們要把之前「 新增、讀取、更新、刪除 」的功能與資料庫做連接,讓我們做這些動作的時候,與資料庫的內容同步!

如果忘記 GORM 怎麼與資料庫連接的話,可以參考 Day 17 的介紹! 🙌
還記得之前我們有在 taskRepo.go(repository)檔案裡面放了一個這個暫存資料嗎?
因為當時還沒有資料庫,所以就先建立一個 slice 取代:

// 暫存資料
var tasks = []model.Task{
	{Item: "鐵人賽文章", Status: false},
}

而現在要把所有 CRUD 的功能都跟資料庫做連接的話,就會更改到以下檔案:

  1. 變更 taskRepo.go 的資料來源、微調 task.Service.goapiHandler.go
  2. main.go

為了避免在多個檔案都重複寫一樣的資料庫連線程式,所以就再把「 資料庫 」獨立出來。建立一個檔案 database/db.go 專門處理資料庫的事情!
db.go 內容:

package database
import (
	"log"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

var DB *gorm.DB   // 建立

func InitDB() {
	var err error
	
	// 確認 DB 是否已初始化
	if DB != nil { 
		return
	}

	// 連線 SQLite DB
	DB, err = gorm.Open(sqlite.Open("tasks.db?_journal_mode=WAL"), &gorm.Config{})
	if err != nil {
		log.Fatal("failed to connect database: ", err)
	}
}

📌 為什麼要把原本的 sqlite.Open(“tasks.db”) 改成 sqlite.Open("tasks.db?_journal_mode=WAL") 呢?
是這樣的~ 因為我在啟動 server 之後,有收到這個錯誤訊息:

database is locked

這是因為資料庫目前被其他連線鎖住,所以無法寫入。在 SQLite 中,同一個時間只能有一個寫入,雖然可以同時多個讀取,但是當要寫入時,就會鎖住整個 DB。
→ 鎖住的原因就是:Go server 在運作,但也同時用 DB Browser 打開了 tasks.db

那解決方式就是:

  1. 把 DB Browser 關掉。(但這對開發上太不友善了 🫠,畢竟還是希望可以馬上看 DB)
  2. 調整 GORM 的設定,讓它可以同時讀取和寫入。

所以就更改原本的設定,加上 journal_mode=WAL 改善鎖定問題:

db, err := gorm.Open(sqlite.Open("tasks.db?_journal_mode=WAL"), &gorm.Config{})

→ WAL (Write-Ahead Logging) 允許同時讀取和寫入,提高並發能力。


簡化主程式內容

把原本建立在 main 檔案的 DB 連線程式都搬到 database/db.go 檔案裡之後,我們就可以直接 import database,把原本很長的程式調整成兩行即可!
main.go 內容:

package main
import (
	"app/apiHandler"
	"app/middleware"
	"app/database"                         // 引入資料庫
	"app/model"
	"github.com/gin-gonic/gin"
)

func main() {
	database.InitDB()                       // 初始資料庫
	database.DB.AutoMigrate(&model.Task{})  // 自動建表

	r := gin.Default()
	r.Use(middleware.ErrorHandler())
	r.GET("/tasks", apiHandler.GetTasks)
	r.POST("/tasks", apiHandler.AddTask)
	r.PATCH("/tasks/:id", apiHandler.UpdateTask)
	r.DELETE("/tasks/:id", apiHandler.DeleteTask)

	r.Run(":8080")
}

更改資料來源為資料庫

最後是更改 repository 的檔案!還記得之前我們在 repository 有建立一個暫時儲存 task 的的 slice 嗎?

// 暫存資料
var tasks = []model.Task{
	{Item: "鐵人賽文章", Status: false},
}

這是還沒有連接資料庫時的替代方案,所有的「 新增、讀取、更新、刪除 」動作,都是針對這裡面的資料~
但現在我們有資料庫了!所以這部分就不需要囉!取而代之的是改用 GORM 的方式(詳細用法可以參考 Day 17)來操作。
taskRepo.go 內容:

package repository
import (
	"app/model"
	"app/database"
)

func GetAllTasks() ([]model.Task){
	var tasks []model.Task
	database.DB.Find(&tasks)
	return  tasks
}

func AddTask(new *model.Task) (model.Task, error){
	result := database.DB.Create(&new)
	return *new, result.Error
}

func UpdateTask(id int, input model.UpdateTask)(model.Task, bool){
	var task model.Task
	err := database.DB.First(&task, id).Error;  // 找出資料庫中 id 相符的 task
	if err != nil{
		return model.Task{}, false
	}
	
	if input.Item != nil{
		task.Item = *input.Item
	}
	if input.Status != nil{
		task.Status = *input.Status
	}
	database.DB.Save(&task)  //寫入資料庫
	return task, true
}

func DeleteTask(id int) bool{
	var task model.Task
	err := database.DB.First(&task, id).Error; 
	if err != nil{
		return false
	}

	result := database.DB.Delete(&task).Error   //刪除該筆資料
	if result != nil{
		return false
	}
	return true
}

完成 CRUD 的資料庫操作之後,要記得測試看看過程有沒有問題~如果有的話再細微調整與 repository 連動的 service 和 apiHandler 檔案。
以上就是今天分享了!


上一篇
Day19 - Go 與資料庫:用 GORM 操作 SQLite
下一篇
Day21 - 資料庫:為什麼需要 Migration?
系列文
Go,一起成為全端吧!—— 給前端工程師的 Golang 後端學習筆記24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言