【Golang 1.18】ORM?ORZ?傻傻分不清楚?
應該有學了三週了吧?今天主要是來介紹ORM - Object Relational Mapping這個東西,簡單來說它就是一個幫助你把Go語法 => SQL語法的工具,因為一般有學過SQL語法的人並不是很多,加上基本上需要高效能的CRUD的機會就更少了,在語法上大大的降低了前端工程師對於SQL的語法熟悉度,而且轉換資料庫很容易,有助於快速開發。
作業環境
項目 | 版本 |
---|---|
CPU | Apple M1 |
macOS | Big Sur 12.3 arm64 |
Golang | 1.18 arm64 |
Visual Studio Code | 1.66 arm64 |
Postman | 9.0.9 arm64 |
DB Browser for SQLite | 3.12.1 x86_64 |
SQLite | 3.31.0 |
初始化設定
安裝GORM + Sqlite Driver
go mod init william // 產生go.mod
go get -u gorm.io/gorm // gorm本體
go get -u gorm.io/driver/sqlite // gorm的sqlite-driver
package main
import (
"log"
"william/utility"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
const sqliteDatabasePath = "./material/sqliteDB.sqlite3" // 把資料庫存放在material資料夾下
type DatabaseType int8
const (
Sqlite3 DatabaseType = 0 // 支援Sqlite3
MySql DatabaseType = 1 // 支援MySql
)
func init() {
log.SetFlags(log.Lshortfile | log.LstdFlags)
}
func main() {
database, error := Sqlite3.createDatabase(sqliteDatabasePath)
utility.Println(error)
utility.Println(database)
}
// 建立資料庫
func (dbType DatabaseType) createDatabase(path string) (*gorm.DB, error) {
var database *gorm.DB
var error error
switch dbType {
case Sqlite3:
database, error = gorm.Open(sqlite.Open(path), &gorm.Config{})
case MySql:
break
}
return database, error
}
package main
// 資料表的長相
type Product struct {
gorm.Model
Code string `json:"code" gorm:"index:idx_name,unique"`
Price uint `json:"price"`
}
func main() {
error := initSetting()
utility.Println(error)
}
// 初始設定
func initSetting() error {
database, error := Sqlite3.createDatabase(sqliteDatabasePath)
if error != nil { return error }
error = createTables(database)
if error != nil { return error }
return nil
}
// 建立表格
func createTables(database *gorm.DB) error {
return database.AutoMigrate(&Product{})
}
CRUD
安裝gin frameork
go get -u github.com/gin-gonic/gin // gin => 打API用
新增 - insert單一商品
- 首先先來新增單一商品
- http://localhost:8080/product + {“code”:“iPhone”,“price”:20000}
// 初始設定
func initSetting() error {
database, error := Sqlite3.createDatabase(sqliteDatabasePath)
if error != nil { return error }
error = createTables(database)
if error != nil { return error }
initRouter(database)
return nil
}
// 初始化Router相關設定
func initRouter(database *gorm.DB) {
router := gin.Default()
router.MaxMultipartMemory = 8 << 20
registerWebAPI(router, database)
router.Run(":8080")
}
// 註冊API
func registerWebAPI(router *gin.Engine, database *gorm.DB) {
insertProduct(router, database)
}
// MARK: - WebAPI
// <POST>新增單一商品 => http://localhost:8080/product + {"code":"iPhone","price":20000}
func insertProduct(router *gin.Engine, database *gorm.DB) {
var emptyProduct Product
router.POST("/product", func(context *gin.Context) {
dictionary := utility.RequestBodyToMap(context)
result, error := emptyProduct._Insert(database, dictionary)
utility.ContextJSON(context, http.StatusOK, result, error)
})
}
// MARK: - [Product小工具](https://gorm.io/zh_CN/docs/query.html)
// 新增單一商品 => ["code":"iPhone","price":20000]
func (_product *Product) _Insert(database *gorm.DB, dictionary map[string]interface{}) (map[string]interface{}, error) {
code := dictionary["code"].(string)
price := uint(dictionary["price"].(float64))
isSuccess := false
error := database.Create(&Product{Code: code, Price: price}).Error
if error == nil {
isSuccess = true
}
result := map[string]interface{}{"isSuccess": isSuccess}
return result, error
}
搜尋 - select單一商品
- 首先先來搜尋單一商品
- http://localhost:8080/product/1
- 要注意的是因為gorm.Model的屬性都是大寫,如果要輸出成小寫,記得去改寫SourceCode
// <GET>搜尋單一商品 => http://localhost:8080/product/87
func selectProduct(router *gin.Engine, database *gorm.DB) {
var emptyProduct Product
router.GET("/product/:id", func(context *gin.Context) {
id, error := utility.StringToInt(context.Param("id"))
result := emptyProduct._Select(database, uint(id))
if result.ID == 0 {
utility.ContextJSON(context, http.StatusOK, nil, error)
return
}
utility.ContextJSON(context, http.StatusOK, result, error)
})
}
// 搜尋單一商品 => id = 87
func (_product *Product) _Select(database *gorm.DB, id uint) Product {
var product Product
database.Take(&product, "id=?", id)
return product
}
刪除單一商品
- 就痛快的把它刪掉吧…
// <DELETE>刪除單一商品 => http://localhost:8080/product/87
func deleteProduct(router *gin.Engine, database *gorm.DB) {
var emptyProduct Product
router.DELETE("/product/:id", func(context *gin.Context) {
id, error := utility.StringToInt(context.Param("id"))
isSuccess := emptyProduct._Delete(database, uint(id))
result := map[string]interface{}{"isSuccess": isSuccess}
utility.ContextJSON(context, http.StatusOK, result, error)
})
}
// 刪除單一商品 => http://localhost:8080/product/87
func (_product *Product) _Delete(database *gorm.DB, id uint) bool {
product := _product._Select(database, id)
database.Delete(&product, id)
return product.ID != 0
}
更新
- 更新單一商品部分內容…
- http://localhost:8080/product/87 + { “code”: “iPhone 21”, “price”: 99999 }
// [<PATCH>更新單一商品部分內容](https://ihower.tw/blog/archives/6483) => http://localhost:8080/product/87
func partialUpdateProduct(router *gin.Engine, database *gorm.DB) {
var emptyProduct Product
router.PATCH("/product/:id", func(context *gin.Context) {
id, error := utility.StringToInt(context.Param("id"))
dictionary := utility.RequestBodyToMap(context)
result := emptyProduct._PartialUpdate(database, uint(id), dictionary)
utility.ContextJSON(context, http.StatusOK, result, error)
})
}
// 更新單一商品部分內容 => http://localhost:8080/product/87 + { "code": "iPhone 21", "price": 99999 }
func (_product *Product) _PartialUpdate(database *gorm.DB, id uint, dictionary map[string]interface{}) map[string]interface{} {
isSuccess := false
product := _product._Select(database, id)
error := database.Model(&product).Updates(dictionary).Error
if error == nil {
isSuccess = true
}
result := map[string]interface{}{}
result["isSuccess"] = isSuccess
return result
}
搜尋部分商品
- 搜尋部分商品,也就是有含過濾條件的
- 名字有含『iPhone』,價格 >= 35000
- http://localhost:8080/product?code=iPhone&price=35000
// [<GET>搜尋部分商品](https://stackoverflow.com/questions/51534285/how-to-access-gorm-model-id/71865857) => http://localhost:8080/product?price=1487&code=iPhone
func selectAllProduct(router *gin.Engine, database *gorm.DB) {
var emptyProduct Product
router.GET("/product", func(context *gin.Context) {
code := context.Query("code")
price := context.Query("price")
dictionary := map[string]interface{}{
"code": code,
"price": price,
}
utility.Println(dictionary)
result := emptyProduct._SelectAll(database, dictionary)
utility.ContextJSON(context, http.StatusOK, result, nil)
})
}
// 搜尋部分商品 (過濾條件) => http://localhost:8080/product?code=iPhone&price=35000
func (_product *Product) _SelectAll(database *gorm.DB, dictionary map[string]interface{}) []Product {
var products []Product
var _database = database
code := dictionary["code"].(string)
price, _ := utility.StringToInt(dictionary["price"].(string))
if len(code) != 0 {
_database = _database.Where("code LIKE ?", "%"+code+"%")
}
if price > 0 {
_database = _database.Where("price >= ?", price)
}
_database.Find(&products)
return products
}
同場加映
使用Gmail發信
- 傳說中的求職三寶 - 簡訊 / 推播 / 垃圾信,利用Gmail來寄寄信,也是必備的能力之一…
- 不過這裡的密碼不是mail的密碼,而去要去google去設定開啟安全性 -> 應用程式密碼 -> 郵件的密碼…
- http://localhost:8080/mail + {“to”:“linuxice0609@gmail.com”,“title”:“垃垃信測試”,“message”:“2022/4/14 Golang smtp test!!!!”}
type gmailInformation struct {
fromMail string
toMail string
password string
host string
port uint
title string
message string
}
// [<POST>寄垃圾信](https://gist.github.com/jpillora/cb46d183eca0710d909a) => http://localhost:8080/mail + {"to":"linuxice0609@gmail.com","title":"垃垃信測試","message":"2022/4/14 Golang smtp test!!!!"}
func mailServer(router *gin.Engine, database *gorm.DB) {
router.POST("/mail", func(context *gin.Context) {
dictionary := utility.RequestBodyToMap(context)
info := gmailInformation{
fromMail: "linuxice0609@gmail.com",
password: "<password>",
host: "smtp.gmail.com",
port: 587,
toMail: dictionary["to"].(string),
title: dictionary["title"].(string),
message: dictionary["message"].(string),
}
result, error := gmailServer(info)
utility.ContextJSON(context, http.StatusOK, result, error)
})
}
// [使用Gmail寄信](https://support.google.com/mail/?p=InvalidSecondFactor)
func gmailServer(info gmailInformation) (map[string]interface{}, error) {
var isSuccess bool = false
var error error = nil
smtpServer := fmt.Sprintf("%s:%d", info.host, info.port)
message := fmt.Sprintf("From: %s\nTo: %s\nSubject: %s\n\n %s", info.fromMail, info.toMail, info.title, info.message)
authentication := smtp.PlainAuth("", info.fromMail, info.password, info.host)
error = smtp.SendMail(smtpServer, authentication, info.fromMail, []string{info.toMail}, []byte(message))
if error == nil {
isSuccess = true
}
result := map[string]interface{}{"isSuccess": isSuccess}
return result, error
}
Crontab
go get -u github.com/robfig/cron/v3
// [定時排程任務](https://www.readfog.com/a/1637371620314157056)
func cronSchedule() {
schedule := cron.New(cron.WithSeconds())
index := 1
schedule.AddFunc("* * * * * *", func() {
utility.Println("每一秒執行一次", index)
index++
})
schedule.Start()
// time.Sleep(time.Minute * 1)
select {}
}
切換Database
- 這裡就把連Sqlite -> MySQL,先建立一個叫GoGo的資料庫,圖就不附上了…
go get -u gorm.io/driver/mysql
// 初始設定
func initSetting() error {
// database, error := Sqlite3.createDatabase(sqliteDatabasePath)
var info MySQLInformation = MySQLInformation{
username: "root",
password: "12345678",
host: "localhost",
port: 3306,
database: "GoGo",
}
database, error := MySql.createDatabase(mysqlDatabaseSource(info))
if error != nil {
return error
}
error = createTables(database)
if error != nil {
return error
}
initRouter(database)
return nil
}
// 建立資料庫
func (dbType DatabaseType) createDatabase(path string) (*gorm.DB, error) {
var database *gorm.DB
var error error
switch dbType {
case Sqlite3:
database, error = gorm.Open(sqlite.Open(path), &gorm.Config{})
case MySql:
databaseConnect, err := sql.Open("mysql", path)
if err != nil {
error = err
break
}
database, error = gorm.Open(mysql.New(mysql.Config{Conn: databaseConnect}), &gorm.Config{})
}
return database, error
}