從零封裝Gin框架實(shí)現(xiàn)數(shù)據(jù)庫初始化GORM
前言
許多框架都會(huì)引入 ORM 模型來表示模型類和數(shù)據(jù)庫表的映射關(guān)系,這一篇將使用 gorm[1] 作為 ORM 庫,它遵循了 ActiveRecord(模型與數(shù)據(jù)庫表一一對應(yīng)) 模式,并且提供了強(qiáng)大的功能,例如模型關(guān)聯(lián)、關(guān)聯(lián)預(yù)加載、數(shù)據(jù)庫遷移等,更多內(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 增加對應(yīng)配置項(xiàng)
database: driver: mysql # 數(shù)據(jù)庫驅(qū)動(dòng) host: 127.0.0.1 # 域名 port: 3306 # 端口號(hào) database: go-test # 數(shù)據(jù)庫名稱 username: root # 用戶名 password: root # 密碼 charset: utf8mb4 # 編碼格式 max_idle_conns: 10 # 空閑連接池中連接的最大數(shù)量 max_open_conns: 100 # 打開數(shù)據(jù)庫連接的最大數(shù)量 log_mode: info # 日志級(jí)別 enable_file_log_writer: true # 是否啟用日志文件 log_filename: sql.log # 日志文件名稱
自定義 Logger(使用文件記錄日志)
gorm 有一個(gè)默認(rèn)的 logger[3] ,由于日志內(nèi)容是輸出到控制臺(tái)的,我們需要自定義一個(gè)寫入器,將默認(rèn)logger.Writer 接口的實(shí)現(xiàn)切換為自定義的寫入器,上一篇引入了 lumberjack ,將繼續(xù)使用它。
新建 bootstrap/db.go 文件,編寫 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)
}接下來,編寫 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)了,這里只簡單替換了 logger.Writer 的實(shí)現(xiàn),大家可以根據(jù)各自的需求做其它定制化配置
初始化數(shù)據(jù)庫
在 bootstrap/db.go 文件中,編寫 InitializeDB 初始化數(shù)據(jù)庫函數(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 類型字段的默認(rèn)長度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的數(shù)據(jù)庫不支持
DontSupportRenameIndex: true, // 重命名索引時(shí)采用刪除并新建的方式,MySQL 5.7 之前的數(shù)據(jù)庫和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的數(shù)據(jù)庫和 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)
}編寫模型文件進(jìn)行數(shù)據(jù)庫遷移
新建 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:用戶名稱"`
Mobile string `json:"mobile" gorm:"not null;index;comment:用戶手機(jī)號(hào)"`
Password string `json:"password" gorm:"not null;default:'';comment:用戶密碼"`
Timestamps
SoftDeletes
}在 bootstrap/db.go 文件中,編寫數(shù)據(jù)庫表初始化代碼
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ù)庫表初始化
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 中,編寫:
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)測試
在 main.go 中調(diào)用數(shù)據(jù)庫初始化函數(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ù)庫
global.App.DB = bootstrap.InitializeDB()
// 程序關(guān)閉前,釋放數(shù)據(jù)庫連接
defer func() {
if global.App.DB != nil {
db, _ := global.App.DB.DB()
db.Close()
}
}()
r := gin.Default()
// 測試路由
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 ,由于我還沒有創(chuàng)建 go-test 數(shù)據(jù)庫,并且調(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ù)庫,重新啟動(dòng) main.go ,users 表創(chuàng)建成功。
以上就是從零封裝Gin框架實(shí)現(xiàn)數(shù)據(jù)庫初始化GORM的詳細(xì)內(nèi)容,更多關(guān)于Gin GORM數(shù)據(jù)庫初始化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go Slice進(jìn)行參數(shù)傳遞如何實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了Go Slice進(jìn)行參數(shù)傳遞如何實(shí)現(xiàn)的過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Go語言標(biāo)準(zhǔn)輸入輸出庫的基本使用教程
輸入輸出在任何一門語言中都必須提供的一個(gè)功能,下面這篇文章主要給大家介紹了關(guān)于Go語言標(biāo)準(zhǔn)輸入輸出庫的基本使用,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02

