Gorm 操作 MySQL
Posted 寻寻觅觅的Gopher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gorm 操作 MySQL相关的知识,希望对你有一定的参考价值。
“强烈推荐阅读官方文档https://gorm.io/zh_CN/docs/
”
安装
go get -u gorm.io/gorm
连接mysql
创建 model.go
文件,代码如下:
package model
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"time"
)
var db *gorm.DB
const dsn = "root:123456@tcp(127.0.0.1:3306)/db_gorm_example?charset=utf8mb4&parseTime=True&loc=Local"
func Setup() {
var err error
db, err = gorm.Open(
mysql.New(mysql.Config{
DSN: dsn, // 连接地址
DefaultStringSize: 191, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}),
&gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 日志输出级别
NamingStrategy: schema.NamingStrategy{
TablePrefix: "sys_", // 表名前缀,`User` 的表名应该是 `sys_users`
SingularTable: true, // 使用单数表名,启用该选项,此时,`User` 的表名应该是 `sys_user`
},
DisableForeignKeyConstraintWhenMigrating: false, //在 AutoMigrate 或 CreateTable 时,GORM 会自动创建外键约束
})
if err != nil {
panic(err)
}
setConnectionPool()
}
// 设置连接池
func setConnectionPool() {
if db != nil {
sqlDB, err := db.DB()
if err != nil {
panic(err)
}
sqlDB.SetMaxIdleConns(10) // 空闲连接池中连接的最大数量
sqlDB.SetMaxOpenConns(10) // 打开数据库连接的最大数量
sqlDB.SetConnMaxLifetime(60 * time.Minute) // 连接可复用的最大时间
err = sqlDB.Ping()
if err != nil {
panic(err)
}
}
}
sqlDB
对象是包含多个 idle(空闲)
和 open(打开)
数据库连接的连接池。idle
通过 SetMaxIdleConns
设置空闲连接池中连接的最大数量;open
通过 SetMaxOpenConns
设置打开数据库连接的最大数量。当使用连接来执行数据库任务时,该连接会被标记为 open
。任务完成后,连接将变为 idle
。此外,还可以通过 SetConnMaxLifetime
设置连接池中连接可复用的最大时间,到期后会断开连接,下次使用需要重新连接。
为什么要使用连接池?
“连接复用,提升性能:数据库连接本质就是一个 socket 连接。频繁建立、断开连接会降低系统的性能。所以可以把数据库连接缓存起来复用,在规定可复用时间内(ConnMaxLifetime)可以直接重用这些连接。这就是连接池。
减少等待时间:在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接(不可以超过MaxOpenConns )并将其添加到池中。减少了用户必须等待建立与数据库的连接的时间。
”
定义模型
首先定义一个班级模型,class.go
:
package model
import "gorm.io/gorm"
// Class 班级
type Class struct {
gorm.Model
// 班级名称,类型为varchar(20),不能为空,唯一约束
ClassName string `json:"class_name" gorm:"size:20;not null;unique;comment:班级名称"`
}
// 插入一行记录
func (c *Class) Create() error {
return db.Create(c).Error
}
学生模型,student.go
:
package model
import "gorm.io/gorm"
// Student 学生
type Student struct {
gorm.Model
// 学生姓名,类型为varchar(20),不能为空
Name string `json:"name" gorm:"type:varchar(20);not null;comment:学生姓名"`
ClassID uint `json:"-" gorm:"not null;comment:学生所属班级ID"`
Class Class `json:"class"`
}
// 插入一行记录
func (s *Student) Create() error {
return db.Create(s).Error
}
// 批量插入
func BatchCreateStudent(students []Student) error {
return db.Create(&students).Error
}
// 查询所有学生信息
func QueryAllStudentInfo() ([]Student, error) {
var students []Student
err := db.Model(&Student{}).
Preload("Class"). // 预加载班级信息
Find(&students).Error
if err != nil {
return nil, err
}
return students, nil
}
// 查询指定学生信息
func QueryStudentInfoByID(studentID uint) (*Student, error) {
var student Student
err := db.Model(&Student{}).Where("id = ?", studentID).
Preload("Class"). // 预加载班级信息
Take(&student).Error
if err != nil {
return nil, err
}
return &student, nil
}
// 软删除
func DelStudent(studentID uint) error {
return db.Delete(&Student{}).Where("id = ?", studentID).Error
}
// 永久删除
func UnscopedDelStudent(studentID uint) error {
return db.Unscoped().Delete(&Student{}).Where("id = ?", studentID).Error
}
班级和学生是一对多的关系,如果想要 gorm
自动为我们创建外键约束,可以使用 AutoMigrate
自动迁移功能。在model.go
中补充:
func Setup() {
...
autoMigrate(&Class{}, &Student{})
}
// 自动迁移
func autoMigrate(tables ...interface{}) {
// 创建表时添加后缀
db.Set("gorm:table_options",
"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4").
AutoMigrate(tables...)
// AutoMigrate 会创建表、缺失的外键、约束、列和索引。
// 如果大小、精度、是否为空可以更改,则 AutoMigrate 会改变列的类型。
}
运行测试
最后在 main.go
中测试上面的代码:
package main
import (
"gorm-example/model"
)
func main() {
// 初始化连接
model.Setup()
// 创建一行学生记录(gorm会自动帮我们创建对应的班级记录)
student := model.Student{
Name: "小米",
Class: model.Class{
ClassName: "一年级",
},
}
student.Create()
select {}
}
输出日志:查看数据库:可以看到 gorm 在为我们创建表的时候加入了 id
、created_at
、updated_at
、deleted_at
四个字段,这其实是在定义模型的时候,我们在结构体中嵌入的 gorm.Model
起的作用,可以看看 gorm.Model
结构体的源码:也正是有了 DeletedAt
的存在,我们的数据库表才可以实现软删除。
测试使用 gorm 来查询数据,更改我们的 main.go
:
package main
import (
"fmt"
"gorm-example/model"
)
func main() {
// 初始化连接
model.Setup()
// 查询所有学生数据
students, err := model.QueryAllStudentInfo()
if err != nil {
panic(err)
}
for i, student := range students {
fmt.Printf(
"---------第%d条记录---------\n"+
"学生ID:%d\n"+
"学生姓名:%s\n"+
"学生班级:%s\n"+
"---------------------------\n",
i+1,
student.ID,
student.Name,
student.Class.ClassName)
}
select {}
}
输出结果:
Gorm中使用事务
默认 gorm 是会在事务里执行写入操作的(创建、更新、删除)。当然,如果不需要,我们也可以禁用它,官方说是会获得大约 30%+ 性能提升。禁用默认事务只要在 model.go
中的 gorm.Config
加入SkipDefaultTransaction: true
即可。
func Setup() {
var err error
db, err = gorm.Open(
mysql.New(mysql.Config{
DSN: dsn, // 连接地址
DefaultStringSize: 191, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}),
&gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 日志输出级别
NamingStrategy: schema.NamingStrategy{
TablePrefix: "sys_", // 表名前缀,`User` 的表名应该是 `sys_users`
SingularTable: true, // 使用单数表名,启用该选项,此时,`User` 的表名应该是 `sys_user`
},
DisableForeignKeyConstraintWhenMigrating: false, //在 AutoMigrate 或 CreateTable 时,GORM 会自动创建外键约束
SkipDefaultTransaction: true, //禁用默认事务
})
if err != nil {
panic(err)
}
setConnectionPool()
autoMigrate(&Class{}, &Student{})
}
来一个需要使用事务的需求吧,定义一个模型 account.go
:
package model
import (
"errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// Account 账户
type Account struct {
gorm.Model
Amount uint `json:"amount" gorm:"not null;default:0;comment:金额"`
}
// 转账属于一系列操作,只能全部成功,必须在事务中执行
// 这里只做示例,忽略余额不够的情况
func Transfer(remitter, payee uint, money int) error {
// 使用事务
return db.Transaction(func(tx *gorm.DB) error {
// 加排他锁
var accounts []Account
err := tx.
Clauses(clause.Locking{Strength: "UPDATE"}).
Model(&Account{}).
Where("id in (?)", []uint{remitter, payee}).
Find(&accounts).Error
if err != nil {
return err
}
if len(accounts) != 2 {
return errors.New("账号异常")
}
remitterOK := false
payeeOK := false
for _, account := range accounts {
switch account.ID {
case remitter:
// 汇款人
err = tx.Model(&Account{}).
Where("id = ?", remitter).
Update("amount", gorm.Expr("amount - ?", money)).Error
if err != nil {
return err
}
remitterOK = true
case payee:
// 收款人
err = tx.Model(&Account{}).
Where("id = ?", payee).
Update("amount", gorm.Expr("amount + ?", money)).Error
if err != nil {
return err
}
payeeOK = true
}
}
if remitterOK && payeeOK {
// 当返回 nil 的时候才会提交事务
return nil
} else {
return errors.New("转账失败")
}
})
}
同样,让自动迁移为我们自动建表,在 model.go
的 Setup()
函数中的 autoMigrate
补充 &Account{}
:
autoMigrate(&Class{}, &Student{}, &Account{})
手动插入一些数据如下:在 main.go
中进行转账操作,更改如下:
package main
import (
"gorm-example/model"
)
func main() {
// 初始化连接
model.Setup()
// (1)转账给(2) 100元
err := model.Transfer(1, 2, 100)
if err != nil {
panic(err)
}
select {}
}
查看变更:
最后
关于 gorm 操作 MySQL 的代码可以从
https://github.com/togettoyou/go-one-server
或阅读原文获取。
To Be Continued
以上是关于Gorm 操作 MySQL的主要内容,如果未能解决你的问题,请参考以下文章