從零封裝Gin框架實(shí)現(xiàn)數(shù)據(jù)庫(kù)初始化GORM
前言
許多框架都會(huì)引入 ORM 模型來(lái)表示模型類(lèi)和數(shù)據(jù)庫(kù)表的映射關(guān)系,這一篇將使用 gorm[1] 作為 ORM 庫(kù),它遵循了 ActiveRecord(模型與數(shù)據(jù)庫(kù)表一一對(duì)應(yīng)) 模式,并且提供了強(qiáng)大的功能,例如模型關(guān)聯(lián)、關(guān)聯(lián)預(yù)加載、數(shù)據(jù)庫(kù)遷移等,更多內(nèi)容查看官方文檔[2]
安裝
go get -u gorm.io/gorm # GORM 官方支持 sqlite、mysql、postgres、sqlserver go get -u gorm.io/driver/mysql
定義配置項(xiàng)
新建 config/database.go
文件,自定義配置項(xiàng)
package config type Database struct { Driver string `mapstructure:"driver" json:"driver" yaml:"driver"` Host string `mapstructure:"host" json:"host" yaml:"host"` Port int `mapstructure:"port" json:"port" yaml:"port"` Database string `mapstructure:"database" json:"database" yaml:"database"` UserName string `mapstructure:"username" json:"username" yaml:"username"` Password string `mapstructure:"password" json:"password" yaml:"password"` Charset string `mapstructure:"charset" json:"charset" yaml:"charset"` MaxIdleConns int `mapstructure:"max_idle_conns" json:"max_idle_conns" yaml:"max_idle_conns"` MaxOpenConns int `mapstructure:"max_open_conns" json:"max_open_conns" yaml:"max_open_conns"` LogMode string `mapstructure:"log_mode" json:"log_mode" yaml:"log_mode"` EnableFileLogWriter bool `mapstructure:"enable_file_log_writer" json:"enable_file_log_writer" yaml:"enable_file_log_writer"` LogFilename string `mapstructure:"log_filename" json:"log_filename" yaml:"log_filename"` }
config/config.go
添加 Database
成員屬性
package config type Configuration struct { App App `mapstructure:"app" json:"app" yaml:"app"` Log Log `mapstructure:"log" json:"log" yaml:"log"` Database Database `mapstructure:"database" json:"database" yaml:"database"` }
config.yaml
增加對(duì)應(yīng)配置項(xiàng)
database: driver: mysql # 數(shù)據(jù)庫(kù)驅(qū)動(dòng) host: 127.0.0.1 # 域名 port: 3306 # 端口號(hào) database: go-test # 數(shù)據(jù)庫(kù)名稱(chēng) username: root # 用戶(hù)名 password: root # 密碼 charset: utf8mb4 # 編碼格式 max_idle_conns: 10 # 空閑連接池中連接的最大數(shù)量 max_open_conns: 100 # 打開(kāi)數(shù)據(jù)庫(kù)連接的最大數(shù)量 log_mode: info # 日志級(jí)別 enable_file_log_writer: true # 是否啟用日志文件 log_filename: sql.log # 日志文件名稱(chēng)
自定義 Logger(使用文件記錄日志)
gorm
有一個(gè)默認(rèn)的 logger[3] ,由于日志內(nèi)容是輸出到控制臺(tái)的,我們需要自定義一個(gè)寫(xiě)入器,將默認(rèn)logger.Writer
接口的實(shí)現(xiàn)切換為自定義的寫(xiě)入器,上一篇引入了 lumberjack
,將繼續(xù)使用它。
新建 bootstrap/db.go
文件,編寫(xiě) getGormLogWriter
函數(shù):
package bootstrap import ( "gopkg.in/natefinch/lumberjack.v2" "gorm.io/gorm/logger" "io" "jassue-gin/global" "log" "os" ) // 自定義 gorm Writer func getGormLogWriter() logger.Writer { var writer io.Writer // 是否啟用日志文件 if global.App.Config.Database.EnableFileLogWriter { // 自定義 Writer writer = &lumberjack.Logger{ Filename: global.App.Config.Log.RootDir + "/" + global.App.Config.Database.LogFilename, MaxSize: global.App.Config.Log.MaxSize, MaxBackups: global.App.Config.Log.MaxBackups, MaxAge: global.App.Config.Log.MaxAge, Compress: global.App.Config.Log.Compress, } } else { // 默認(rèn) Writer writer = os.Stdout } return log.New(writer, "\r\n", log.LstdFlags) }
接下來(lái),編寫(xiě) getGormLogger
函數(shù), 切換默認(rèn) Logger
使用的 Writer
func getGormLogger() logger.Interface { var logMode logger.LogLevel switch global.App.Config.Database.LogMode { case "silent": logMode = logger.Silent case "error": logMode = logger.Error case "warn": logMode = logger.Warn case "info": logMode = logger.Info default: logMode = logger.Info } return logger.New(getGormLogWriter(), logger.Config{ SlowThreshold: 200 * time.Millisecond, // 慢 SQL 閾值 LogLevel: logMode, // 日志級(jí)別 IgnoreRecordNotFoundError: false, // 忽略ErrRecordNotFound(記錄未找到)錯(cuò)誤 Colorful: !global.App.Config.Database.EnableFileLogWriter, // 禁用彩色打印 }) }
至此,自定義 Logger 就已經(jīng)實(shí)現(xiàn)了,這里只簡(jiǎn)單替換了 logger.Writer
的實(shí)現(xiàn),大家可以根據(jù)各自的需求做其它定制化配置
初始化數(shù)據(jù)庫(kù)
在 bootstrap/db.go
文件中,編寫(xiě) InitializeDB
初始化數(shù)據(jù)庫(kù)函數(shù),以便于在 main.go
中調(diào)用
package bootstrap import ( "gopkg.in/natefinch/lumberjack.v2" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "io" "jassue-gin/global" "log" "os" "strconv" "time" ) func InitializeDB() *gorm.DB { // 根據(jù)驅(qū)動(dòng)配置進(jìn)行初始化 switch global.App.Config.Database.Driver { case "mysql": return initMySqlGorm() default: return initMySqlGorm() } } // 初始化 mysql gorm.DB func initMySqlGorm() *gorm.DB { dbConfig := global.App.Config.Database if dbConfig.Database == "" { return nil } dsn := dbConfig.UserName + ":" + dbConfig.Password + "@tcp(" + dbConfig.Host + ":" + strconv.Itoa(dbConfig.Port) + ")/" + dbConfig.Database + "?charset=" + dbConfig.Charset +"&parseTime=True&loc=Local" mysqlConfig := mysql.Config{ DSN: dsn, // DSN data source name DefaultStringSize: 191, // string 類(lèi)型字段的默認(rèn)長(zhǎng)度 DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的數(shù)據(jù)庫(kù)不支持 DontSupportRenameIndex: true, // 重命名索引時(shí)采用刪除并新建的方式,MySQL 5.7 之前的數(shù)據(jù)庫(kù)和 MariaDB 不支持重命名索引 DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的數(shù)據(jù)庫(kù)和 MariaDB 不支持重命名列 SkipInitializeWithVersion: false, // 根據(jù)版本自動(dòng)配置 } if db, err := gorm.Open(mysql.New(mysqlConfig), &gorm.Config{ DisableForeignKeyConstraintWhenMigrating: true, // 禁用自動(dòng)創(chuàng)建外鍵約束 Logger: getGormLogger(), // 使用自定義 Logger }); err != nil { global.App.Log.Error("mysql connect failed, err:", zap.Any("err", err)) return nil } else { sqlDB, _ := db.DB() sqlDB.SetMaxIdleConns(dbConfig.MaxIdleConns) sqlDB.SetMaxOpenConns(dbConfig.MaxOpenConns) return db } } func getGormLogger() logger.Interface { var logMode logger.LogLevel switch global.App.Config.Database.LogMode { case "silent": logMode = logger.Silent case "error": logMode = logger.Error case "warn": logMode = logger.Warn case "info": logMode = logger.Info default: logMode = logger.Info } return logger.New(getGormLogWriter(), logger.Config{ SlowThreshold: 200 * time.Millisecond, // 慢 SQL 閾值 LogLevel: logMode, // 日志級(jí)別 IgnoreRecordNotFoundError: false, // 忽略ErrRecordNotFound(記錄未找到)錯(cuò)誤 Colorful: !global.App.Config.Database.EnableFileLogWriter, // 禁用彩色打印 }) } // 自定義 gorm Writer func getGormLogWriter() logger.Writer { var writer io.Writer // 是否啟用日志文件 if global.App.Config.Database.EnableFileLogWriter { // 自定義 Writer writer = &lumberjack.Logger{ Filename: global.App.Config.Log.RootDir + "/" + global.App.Config.Database.LogFilename, MaxSize: global.App.Config.Log.MaxSize, MaxBackups: global.App.Config.Log.MaxBackups, MaxAge: global.App.Config.Log.MaxAge, Compress: global.App.Config.Log.Compress, } } else { // 默認(rèn) Writer writer = os.Stdout } return log.New(writer, "\r\n", log.LstdFlags) }
編寫(xiě)模型文件進(jìn)行數(shù)據(jù)庫(kù)遷移
新建 app/models/common.go
文件,定義公用模型字段
package models import ( "gorm.io/gorm" "time" ) // 自增ID主鍵 type ID struct { ID uint `json:"id" gorm:"primaryKey"` } // 創(chuàng)建、更新時(shí)間 type Timestamps struct { CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // 軟刪除 type SoftDeletes struct { DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` }
新建 app/models/user.go
文件,定義 User
模型
package models type User struct { ID Name string `json:"name" gorm:"not null;comment:用戶(hù)名稱(chēng)"` Mobile string `json:"mobile" gorm:"not null;index;comment:用戶(hù)手機(jī)號(hào)"` Password string `json:"password" gorm:"not null;default:'';comment:用戶(hù)密碼"` Timestamps SoftDeletes }
在 bootstrap/db.go
文件中,編寫(xiě)數(shù)據(jù)庫(kù)表初始化代碼
func initMySqlGorm() *gorm.DB { // ... if db, err := gorm.Open(mysql.New(mysqlConfig), &gorm.Config{ DisableForeignKeyConstraintWhenMigrating: true, // 禁用自動(dòng)創(chuàng)建外鍵約束 Logger: getGormLogger(), // 使用自定義 Logger }); err != nil { return nil } else { sqlDB, _ := db.DB() sqlDB.SetMaxIdleConns(dbConfig.MaxIdleConns) sqlDB.SetMaxOpenConns(dbConfig.MaxOpenConns) initMySqlTables(db) return db } } // 數(shù)據(jù)庫(kù)表初始化 func initMySqlTables(db *gorm.DB) { err := db.AutoMigrate( models.User{}, ) if err != nil { global.App.Log.Error("migrate table failed", zap.Any("err", err)) os.Exit(0) } }
定義全局變量 DB
在 global/app.go
中,編寫(xiě):
package global import ( "github.com/spf13/viper" "go.uber.org/zap" "gorm.io/gorm" "jassue-gin/config" ) type Application struct { ConfigViper *viper.Viper Config config.Configuration Log *zap.Logger DB *gorm.DB } var App = new(Application)
測(cè)試
在 main.go
中調(diào)用數(shù)據(jù)庫(kù)初始化函數(shù)
package main import ( "github.com/gin-gonic/gin" "jassue-gin/bootstrap" "jassue-gin/global" "net/http" ) func main() { // 初始化配置 bootstrap.InitializeConfig() // 初始化日志 global.App.Log = bootstrap.InitializeLog() global.App.Log.Info("log init success!") // 初始化數(shù)據(jù)庫(kù) global.App.DB = bootstrap.InitializeDB() // 程序關(guān)閉前,釋放數(shù)據(jù)庫(kù)連接 defer func() { if global.App.DB != nil { db, _ := global.App.DB.DB() db.Close() } }() r := gin.Default() // 測(cè)試路由 r.GET("/ping", func(c *gin.Context) { c.String(http.StatusOK, "test gorm") }) // 啟動(dòng)服務(wù)器 r.Run(":" + global.App.Config.App.Port) }
啟動(dòng) main.go
,由于我還沒(méi)有創(chuàng)建 go-test
數(shù)據(jù)庫(kù),并且調(diào)整了 logger.Writer
為 lumberjack
,所以會(huì)生成 storage/logs/sql.log
文件,文件內(nèi)容如下:
2021/10/13 19:17:47 /Users/sujunjie/go/src/jassue-gin/bootstrap/db.go:44
[error] failed to initialize database, got error Error 1049: Unknown database 'go-test'
創(chuàng)建 go-test
數(shù)據(jù)庫(kù),重新啟動(dòng) main.go
,users
表創(chuàng)建成功。
以上就是從零封裝Gin框架實(shí)現(xiàn)數(shù)據(jù)庫(kù)初始化GORM的詳細(xì)內(nèi)容,更多關(guān)于Gin GORM數(shù)據(jù)庫(kù)初始化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言中g(shù)o?mod?vendor使用方法
go mod vendor的功能是將新增的依賴(lài)包自動(dòng)寫(xiě)入當(dāng)前項(xiàng)目的 vendor目錄,下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言中g(shù)o?mod?vendor使用的相關(guān)資料,需要的朋友可以參考下2022-10-10詳解Golang如何實(shí)現(xiàn)支持隨機(jī)刪除元素的堆
堆是一種非常常用的數(shù)據(jù)結(jié)構(gòu),它能夠支持在O(1)的時(shí)間復(fù)雜度獲取到最大值(或最小值)。本文主要介紹了如何實(shí)現(xiàn)支持O(log(n))隨機(jī)刪除元素的堆,需要的可以參考一下2022-09-09Go語(yǔ)言中處理JSON數(shù)據(jù)的編碼和解碼的方法
在Go語(yǔ)言中,處理JSON數(shù)據(jù)的編碼和解碼主要依賴(lài)于標(biāo)準(zhǔn)庫(kù)中的encoding/json包,這個(gè)包提供了兩個(gè)核心的函數(shù):Marshal和Unmarshal,本文給大家介紹了Go語(yǔ)言中處理JSON數(shù)據(jù)的編碼和解碼的方法,需要的朋友可以參考下2024-04-04golang編程入門(mén)之http請(qǐng)求天氣實(shí)例
這篇文章主要介紹了golang編程入門(mén)之http請(qǐng)求天氣實(shí)例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08解析Go語(yǔ)言編程中的struct結(jié)構(gòu)
這篇文章主要介紹了Go語(yǔ)言編程中的struct結(jié)構(gòu),是Go語(yǔ)言入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10Go?1.21新內(nèi)置函數(shù)min、max和clear的用法詳解
Go?1.21?版本已經(jīng)正式發(fā)布,它帶來(lái)了許多新特性和改進(jìn),其中引入了的三個(gè)新內(nèi)置函數(shù):max、min?和?clear,接下來(lái)我們就來(lái)看看這些函數(shù)的用途和特點(diǎn)吧2023-08-08