iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0

What is ORM

ORM全名為Object-Relational Mapping 物件關係對應,也就是透過物件導向的方式將關係資料庫的資料映射至物件的技術。

那ORM有三大核心準則分別是

  • 簡單性
  • 傳達性
  • 精確性

ORM的使用也帶來了以下優點:

  • 隱藏了數據訪問的細節,封閉通用數據庫的交互訪問,並將其封裝成物件,使我們使用時完全不用寫SQL Command。
  • ORM使我們構造固化數據結構變得簡單易行。
  • ORM可以防止SQL-Injection
  • ORM使面對對象不需要編碼,能夠像使用物件一樣簡單的操作資料庫。
  • ORM可以自動對實體對象與資料庫中表進行字段與屬性的映射,不需要單獨的數據訪問層就可以數據進行增刪改查,並提高開發效率。
  • 方便data migration,當資料庫發生改變時,不需要對模組進行修改,只需要修改映射關係即可。

同時ORM也隱含著以下缺點:

  • 自動化進行映射與關聯管理,必然的會犧牲效能。然而現今大部分的ORM框架都已經用各式各樣方法儘量地減少效能的犧牲了。
  • 操作ORM會隱藏了數據層面的業務邏輯,導致使用者對資料庫設計方面掌握度較低。
  • 更為複雜的SQL Command,ORM並無法執行,到頭來還是需要寫Pure SQL。

GORM

GORM是專門為Gin這個Framework所設計的ORM Library,主要用於操作資料庫。這邊我們會基於過往的MVC架構將其依不同功能分成dao、model以及service三層。

Connect with Database

這邊我們專門放連結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
}
  1. 首先我們創造一個全域變數叫做SqlSession,有點致敬db session的概念。
  2. 之後再寫個function用來連接database

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)
	}
}
  1. 首先,我們透過godotenv得到環境變數DB_CONFIG
  2. 之後則透過呼叫Initialize()來連接上資料庫
  3. 最後則進行migration,將User這張table給create出來

model

這邊則負責堆放需要在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如程式碼所述,這邊就不在多做贅述。

service

這裡則是關於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中的field
  • SelectOneUsers()為利用id query User table的orm function
  • RegisterOneUser()為透過account, password以及email去創建一筆新資料在User table的orm function
  • CheckOneUser()則為檢查該account是否已經存在於User table的orm function

Controller

最後則是這次新增的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而已,故不在贅述!

Summary

這章節我們從分割程式碼結構、如何使用使用orm並串接上API做了一次範例給大家看,這次的code也會放在以下連結,有興趣者可以參考!

https://github.com/Neskem/Ironman-2021/tree/Day-18


上一篇
Day17 Preparation of Gin and Env
下一篇
Day19 Gin with Swagger
系列文
fmt.Println("從零開始的Golang生活")30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言