ORM
全名為Object-Relational Mapping 物件關係對應,也就是透過物件導向的方式將關係資料庫的資料映射至物件的技術。
那ORM有三大核心準則分別是
ORM的使用也帶來了以下優點:
同時ORM也隱含著以下缺點:
GORM是專門為Gin這個Framework所設計的ORM Library,主要用於操作資料庫。這邊我們會基於過往的MVC架構將其依不同功能分成dao、model以及service三層。
這邊我們專門放連結database所用到的function與變數等。
dao/postgresql.go
package dao
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var (
SqlSession *gorm.DB
)
func Initialize(dbConfig string) (*gorm.DB, error) {
var err error
SqlSession, err = gorm.Open(postgres.Open(dbConfig), &gorm.Config{})
return SqlSession, err
}
main.go
package main
import (
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"ironman-2021/app/config"
"ironman-2021/app/dao"
"ironman-2021/app/model"
"os"
)
func main() {
envErr := godotenv.Load()
if envErr != nil {
panic(envErr)
}
port := os.Getenv("PORT")
dbConfig := os.Getenv("DB_CONFIG")
db, ormErr := dao.Initialize(dbConfig)
if ormErr != nil {
panic(ormErr)
}
migrateErr := db.AutoMigrate(&model.User{})
if migrateErr != nil {
return
}
server := gin.Default()
server.GET("/hc", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "health check",
})
})
config.RouteUsers(server)
err := server.Run(":" + port)
if err != nil {
panic(err)
}
}
godotenv
得到環境變數DB_CONFIG
Initialize()
來連接上資料庫這邊則負責堆放需要在database所創建的table資料
model/user.go
package model
import (
"gorm.io/gorm"
"time"
)
type User struct {
gorm.Model
ID int64 `gorm:"primary_key;auto_increment" json:"id"`
Account string `gorm:"size:100;not null;unique" json:"account"`
Password string `gorm:"size:100;not null;" json:"password"`
Email string `gorm:"size:100;not null;unique" json:"email"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}
我們create了一個名為User的gorm.Model
,然後上面的primary key、field如程式碼所述,這邊就不在多做贅述。
這裡則是關於orm相關的變數以及function,這邊也是主要對資料庫進行資料CRUD的地方。
service/user.go
package service
import (
"fmt"
"ironman-2021/app/dao"
"ironman-2021/app/model"
)
var UserFields = []string{"id", "account", "email"}
func SelectOneUsers(id int64) (*model.User, error) {
userOne:=&model.User{}
err := dao.SqlSession.Select(UserFields).Where("id=?", id).First(&userOne).Error
if err != nil {
return nil, err
} else {
return userOne, nil
}
}
func RegisterOneUser(account string, password string, email string) error {
if !CheckOneUser(account) {
return fmt.Errorf("User exists.")
}
user := model.User{
Account: account,
Password: password,
Email: email,
}
insertErr := dao.SqlSession.Model(&model.User{}).Create(&user).Error
return insertErr
}
func CheckOneUser(account string) bool {
result := false
var user model.User
dbResult := dao.SqlSession.Where("account = ?", account).Find(&user)
if dbResult.Error != nil {
fmt.Printf("Get User Info Failed:%v\n", dbResult.Error)
} else {
result = true
}
return result
}
UserFields
為我們SelectOneUsers()
所要調用的User table中的fieldSelectOneUsers()
為利用id query User table的orm functionRegisterOneUser()
為透過account, password以及email去創建一筆新資料在User table的orm functionCheckOneUser()
則為檢查該account是否已經存在於User table的orm function最後則是這次新增的controller directory,這邊主要是用來撰寫API邏輯的地方,舉例來說一次的GET DATA API,我們會將其切割為routing, business logic, orm query三大部分,controller這層就是用來實現business logic的地方
controller/user.go
package controller
import (
"github.com/gin-gonic/gin"
"ironman-2021/app/service"
"net/http"
"strconv"
)
type UsersController struct {}
func NewUsersController() UsersController {
return UsersController{}
}
func QueryUsersController() UsersController {
return UsersController{}
}
type Register struct {
Account string `json:"account" binding:"required" example:"account"`
Password string `json:"password" binding:"required" example:"password"`
Email string `json:"email" binding:"required" example:"test123@gmail.com"`
}
func (u UsersController) CreateUser (c *gin.Context){
var form Register
bindErr := c.BindJSON(&form)
if bindErr == nil {
err := service.RegisterOneUser(form.Account, form.Password, form.Email)
if err == nil {
c.JSON(http.StatusOK, gin.H{
"status": 1,
"msg": "success Register",
"data": nil,
})
} else {
c.JSON(http.StatusInternalServerError, gin.H{
"status": -1,
"msg": "Register Failed" + err.Error(),
"data": nil,
})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{
"status": -1,
"msg": "Failed to parse register data" + bindErr.Error(),
"data": nil,
})
}
}
func (u UsersController) GetUser (c *gin.Context){
id := c.Params.ByName("id")
userId, err := strconv.ParseInt(id, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": -1,
"msg": "Failed to parse params" + err.Error(),
"data": nil,
})
}
userOne, err := service.SelectOneUsers(userId)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"status": -1,
"msg": "User not found" + err.Error(),
"data": nil,
})
} else {
c.JSON(http.StatusOK, gin.H{
"status": 0,
"msg": "Successfully get user data",
"user": &userOne,
})
}
}
由於這次範例並沒寫什麼商業邏輯,就只做簡單的call service function進行data的create與query而已,故不在贅述!
這章節我們從分割程式碼結構、如何使用使用orm並串接上API做了一次範例給大家看,這次的code也會放在以下連結,有興趣者可以參考!
https://github.com/Neskem/Ironman-2021/tree/Day-18