前言
上一篇:go-zero&go web集成redis实战
从零开始基于go-zero搭建go web项目实战-04集成gorm实战
源码仓库地址 源码 https://gitee.com/li_zheng/treasure-box
golang gorm
官网地址:https://gorm.io/zh_CN/docs/index.html
GORM介绍
Gorm是Go语言目前比较热门的数据库ORM库,API简单明了,上手容易,使用简单,主要把struct类型和数据库表记录进行映射,操作数据库的时候不需要直接手写SQL,面向结构体,同时也支持手动编写sql进行调优。
特性
- 全功能 ORM
- 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
- Create,Save,Update,Delete,Find 中钩子方法
- 支持 Preload、Joins 的预加载
- 事务,嵌套事务,Save Point,Rollback To Saved Point
- Context、预编译模式、DryRun 模式
- 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
- SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
- 复合主键,索引,约束
- Auto Migration
- 自定义 Logger
- 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
- 每个特性都经过了测试的重重考验
- 开发者友好
GORM 官方支持的数据库类型有:MySQL, PostgreSQL, SQLite, SQL Server 和 TiDB
安装
这里使用MySQL数据库进行测试
安装依赖包
// 安装MySQL依赖
go get gorm.io/driver/mysql@v1.3.5
// 安装Gorm依赖
go get gorm.io/gorm@v1.25.4
简单入门
创建mysql数据库连接
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
//连接本地数据库bd01,用户名:root,密码:123456
dsn := "root:123456@tcp(127.0.0.1:3306)/db01?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
- mysql.Open(dsn) 根据提供的连接信息打一个mysql连接器
- gorm.Open() 在给定 dialector 的基础上开启一个 db session
- gorm.Config{} 可选配置
声明一个模型struct,对应数据的一张表,User继承了gorm.Model
,拥有gorm.Model的字段信息。
type User struct {
gorm.Model
Name string
Sex int8
}
// 指定表名 t_user
func (User) TableName() string {
return "t_user"
}
// gorm.Model 的定义,提供默认的字段和类型,GORM 约定使用 CreatedAt、UpdatedAt 追踪创建/更新时间。如果您定义了这种字段,GORM 在创建、更新时会自动填充 当前时间
type Model struct {
ID uintgorm:"primaryKey"
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAtgorm:"index"
}
操作数据库,这里给出几个例子,更多操作方式见官网
// 迁移 schema
db.AutoMigrate(&User{})
// Create
db.Create(&User{Name: "张三", Sex: 1})
// Read
var user User
db.First(&user, 1) // 根据整型主键查找
db.First(&user, "name = ?", "张三") // 查找 code 字段值为 D42 的记录
// Update
db.Model(&user).Update("Name", "李四")
// Update - 更新多个字段
db.Model(&user).Updates(User{Name: "王五", Sex: 2}) // 仅更新非零值字段
db.Model(&user).Updates(map[string]interface{}{"Name": '王五', "Sex": 2})
// Delete - 删除 product
db.Delete(&user, 1)
Web集成
新增配置struct
在项目原有基础上新增一个数据配置结构体,添加到全局配置 Config 中
type DbConf struct {
Host string
Port int
Username string
Password string
Db string
ParamStr string `json:",optional"`
MaxOpenConns int `json:",default=10"`
MaxIdleConns int `json:",default=5"`
ConnMaxIdleTime int `json:",default=60"`
ConnMaxLifetime int `json:",default=60"`
SlowThresholdMillisecond int64 `json:",default=1000"`
}
type Config struct {
rest.RestConf
Redis redis.RedisConf `json:",optional"`
Auth struct {
AccessSecret string
AccessExpire int64
}
Db DbConf `json:",optional"`
}
yaml中配置数据库
Db:
Host: localhost
Port: 3306
Username: root
# 这里密码如果是纯数字需要加引号
Password: "123456"
# 数据库名
Db: test01
#连接参数字符串拼接形式
ParamStr: charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai
#设置打开数据库连接的最大数量
MaxOpenConns: 10
#设置空闲连接池中连接的最大数量
MaxIdleConns: 5
#连接最大空闲时间 单位秒
ConnMaxIdleTime: 60
#设置连接可复用的最大时间 单位秒
ConnMaxLifetime: 60
项目启动加载配置
这里使用的是go-zero的conf.MustLoad(configFile, &c)
方法加载yaml配置,其他方式可自行根据情况进行配置读取,例如使用 gopkg.in/yaml.v2
进行读取。
conf.MustLoad(configFile, &c)
gopkg.in/yaml.v2 读取例子
func LoadAppConf(filePath string, out interface{}) error {
file, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
err = yaml.Unmarshal(file, out)
if err != nil {
return err
}
if conf.Logging.PrintConf {
fmt.Println("------------------------------------------", filePath, " Start------------------------------------------>")
fmt.Println(string(file))
fmt.Println()
}
return nil
}
初始化数据库连接
在项目上下文中初始化数据库连接信息,internal/svc/ctx.go 中的 initDb 方法会在服务启动时候调用,进行数据库的初始化,具体代码如下,调用细节可以参考:https://gitee.com/li_zheng/treasure-box 下对应的文件代码
func initDb() {
dbConf := sCtx.Config.Db
if len(dbConf.Host) == 0 {
return
}
logx.Infof("Initializing db ...")
//if len(dbConf.ParamStr) == 0 {
// dbConf.ParamStr = getParamStr(dbConf.ConnParams)
//}
dbUrl := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?%s",
dbConf.Username,
dbConf.Password,
dbConf.Host,
dbConf.Port,
dbConf.Db,
dbConf.ParamStr)
logx.Infof("DSN: %s", dbUrl)
dialector := mysql.New(mysql.Config{
DSN: dbUrl, // data source name
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
})
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.Config{
SlowThreshold: time.Duration(dbConf.SlowThresholdMillisecond) * time.Millisecond, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别
IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, // 禁用彩色打印
},
)
option := &gorm.Config{
//禁用默认全局事务
SkipDefaultTransaction: true,
//开启预编译sql
PrepareStmt: true,
Logger: newLogger,
}
db, err := gorm.Open(dialector, option)
if err != nil {
logx.Must(err)
}
sqlDb, err := db.DB()
if err != nil {
logx.Must(err)
}
sqlDb.SetMaxOpenConns(dbConf.MaxOpenConns)
sqlDb.SetMaxIdleConns(dbConf.MaxIdleConns)
sqlDb.SetConnMaxIdleTime(time.Second * time.Duration(dbConf.ConnMaxIdleTime))
sqlDb.SetConnMaxLifetime(time.Second * time.Duration(dbConf.ConnMaxLifetime))
sCtx.Db = db
logx.Infof("%+v", sqlDb.Stats())
logx.Infof("DB Initialized.")
}