Go(五)Go不知道怎么用Gorm?

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

作者:lomtom

个人网站:lomtom.top

个人公众号:博思奥园

你的支持就是我最大的动力。

前言

所有的后端应用都离不开数据库的操作,在Go中也有一些好用的数据库操作组件,例如Gorm就是一个很不错的选择。

这里是Gorm自己例举的优点:

  • 全功能 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这么多特性,但是也不阻碍GormGo中一个非常优秀的ORM框架。

本文也不探究Gorm和其他框架的优劣比较,而是从使用者出发,一起来探讨Gorm在实际开发中的使用。

当然Gorm本身的官方文档已经非常详细了,如果对本文中的部分Gorm使用有稍许疑惑的话,请移步官方文档:gorm.io/zh_CN/docs/…

安装

在控制台执行go get命令进行安装依赖,驱动根据自己的实际使用进行安装,这里以MySQL为例。

Gorm 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server

1
2
go复制代码go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

在使用时引入依赖即可

1
2
3
4
go复制代码import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)

建立连接

使用Gorm建立数据库的连接其实很简单,但是要做到好用,那就需要花点心思,在这里,将带领大家怎么从最简单的连接到好用的连接设置。

最基本的连接

1
2
3
4
5
6
7
8
9
go复制代码func GetDb() *gorm.DB {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err!= nil {
return nil
}
return db
}

注意:

  1. 想要正确的处理 time.Time ,您需要带上 parseTime 参数,
  2. 使用charset指定编码,要支持完整的 UTF-8 编码,您需要将 charset=utf8 更改为 charset=utf8mb4

更多参数设置:github.com/go-sql-driv…

设置连接池

Gorm同样支持连接池,Gorm使用 database/sql 维护连接池

分别使用SetMaxIdleConnsSetMaxOpenConnsSetConnMaxLifetime来设置最大空闲连接数、最大连接数和设置连接空闲超时参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
go复制代码func GetDb() *gorm.DB {
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{

})
if err != nil {
return nil
}
sqlDB, err := db.DB()
if err != nil {
log.Printf("database setup error %v", err)
}
sqlDB.SetMaxIdleConns(10) //最大空闲连接数
sqlDB.SetMaxOpenConns(100) //最大连接数
sqlDB.SetConnMaxLifetime(time.Hour) //设置连接空闲超时
return db
}

全局连接

为了方便使用,我们可以在一开始就使用一个全局变量来保存数据库的连接,在使用时直接调用即可,而不需要再次进行数据库的初始化。

1
2
3
4
5
6
go复制代码var db *gorm.DB

// GetDb 获取连接
func GetDb() *gorm.DB {
return db
}

将之前的函数改为给db进行初始化并赋值,在使用的时候直接调用GetDb函数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
go复制代码func DbInit(){
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
tempDb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{

})
if err != nil {
return nil
}
sqlDB, err := tempDb.DB()
if err != nil {
log.Printf("database setup error %v", err)
}
sqlDB.SetMaxIdleConns(10) //最大空闲连接数
sqlDB.SetMaxOpenConns(100) //最大连接数
sqlDB.SetConnMaxLifetime(time.Hour) //设置连接空闲超时
db = tempDb
}

利用配置文件

到这里,你其实发现已经能够很好的使用Gorm去建立数据库连接了,但是有没有什么办法像Spring Boot一样从配置文件中获取连接参数呢,恰好第三章中讲到了怎么使用读取配置文件的方法,那何不利用起来呢?

戳 -> Go(三)Go配置文件

在配置文件中定义数据库连接参数

1
2
3
4
5
6
7
8
9
10
bash复制代码database:
type: mysql
host: localhost
port: 3306
username: root
password: 123456
dbname: test
max_idle_conn: 10
max_open_conn: 30
conn_max_lifetime: 300

定义相应的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bash复制代码var Database *database

type conf struct {
DB database `yaml:"database"`
}

type database struct {
Type string `yaml:"type"`
Host string `yaml:"host"`
Port string `yaml:"port"`
UserName string `yaml:"username"`
Password string `yaml:"password"`
DbName string `yaml:"dbname"`
MaxIdleConn int `yaml:"max_idle_conn"`
MaxOpenConn int `yaml:"max_open_conn"`
ConnMaxLifetime int `yaml:"conn_max_lifetime"`
}

具体怎么绑定参数,请戳 -> Go(三)Go配置文件

为了更直观的感受,将URI抽取出来

1
2
3
4
5
6
7
8
9
go复制代码//获取链接URI
func mySQLUri() string {
return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true",
Database.UserName,
Database.Password,
Database.Host,
Database.Port,
Database.DbName)
}

那么最终呈现的就是这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
go复制代码var db *gorm.DB

// GetDb 获取连接
func GetDb() *gorm.DB {
return db
}

// DbInit 数据库连接池初始化
func DbInit() {
fmt.Println(mySQLUri())
conn, err1 := gorm.Open(mysql.Open(mySQLUri()), &gorm.Config{})
if err1 != nil {
log.Printf("connect get failed.")
return
}
sqlDB, err := conn.DB()
if err != nil {
log.Printf("database setup error %v", err)
}
sqlDB.SetMaxIdleConns(Database.MaxIdleConn) //最大空闲连接数
sqlDB.SetMaxOpenConns(Database.MaxOpenConn) //最大连接数
sqlDB.SetConnMaxLifetime(time.Duration(Database.ConnMaxLifetime) * time.Second) //设置连接空闲超时
db = conn
}

如果想要在项目启动时自动初始化,将DbInit方法名改为init即可,否则,需要在main方法中自行调用执行初始化。

为了更好的开发,我们可以自定义Gorm的日志

1
2
3
4
5
6
7
8
9
10
go复制代码//初始化数据库日志
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Info, // Log level
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
Colorful: true, // Disable color
},
)

将其作为参数放置在Gorm参数上gorm.Open(mysql.Open(mySQLUri()), &gorm.Config{})

1
2
3
go复制代码conn, err1 := gorm.Open(mysql.Open(mySQLUri()), &gorm.Config{
Logger: newLogger,
})

使用

GormCURD相对来说叶比较简单。

定义一个结构体User,除开记录的字段,有编号、姓名、密码三个字段

1
2
3
4
5
6
7
8
9
10
11
12
13
go复制代码type User struct {
Id int64 `gorm:"primaryKey;column:id;"`
Username string `gorm:"column:user_name;type:varchar(255);default:(-)" `
Password string `gorm:"column:password;type:varchar(255);default:(-)"`
Deleted gorm.DeletedAt `gorm:"column:deleted;type:timestamp;default:(-)"`
CreateTime time.Time `gorm:"column:create_time;type:timestamp;default:(-)"`
UpdateTime time.Time `gorm:"column:update_time;type:timestamp;default:(-)"`
}

// TableName 自定义表名
func (*User) TableName() string {
return "users"
}

说明:

  1. 使用primaryKey指定主键
  2. 使用column:id指定在数据库中的列名
  3. 使用gorm.DeletedAt标明该字段为删除标志,如果使用了gorm.DeletedAt,数据库列类型必须为时间格式。
  4. 使用type:varchar(255)标明字段类型
  5. 使用default:(-)设置默认值,-表示为无默认值。
  6. 使用User.TableName表名数据库名,当使用Model绑定结构体时,Gorm会默认调用该方法,除此之外,还可以使用db.Table("user")显式的标明表名。

查询

  1. 获取第一个,默认查询第一个
1
2
3
4
5
6
go复制代码// GetFirst SELECT * FROM users ORDER BY id LIMIT 1;
func GetFirst() (user *User) {
db := config.GetDb()
db.Model(&user).First(&user)
return
}
  1. 获取最后一个
1
2
3
4
5
6
go复制代码// GetLast SELECT * FROM users ORDER BY id DESC LIMIT 1;
func GetLast() (user *User) {
db := config.GetDb()
db.Model(&user).Last(&user)
return
}
  1. 通过主键获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码// GetById SELECT * FROM users WHERE id = 1;
func GetById(id int64) (user *User) {
db := config.GetDb()
db.Model(&user).Find(&user,id)
return
}

等同于

func GetById(id int64) (user *User) {
db := config.GetDb()
db.Model(&user).Where("id = ?",id).Find(&user)
return
}
  1. 通过主键批量查询
1
2
3
4
5
6
7
8
9
10
11
12
go复制代码// GetByIds SELECT * FROM users WHERE id IN (1,2,3);
func GetByIds(ids []int64) (user []*User) {
db := config.GetDb()
db.Model(&user).Find(&user,ids)
return
}
等同于
func GetByIds(s []int64) (user []*User) {
db := config.GetDb()
db.Model(&user).Where("id in ?",ids).Find(&user)
return
}
  1. 获取部分参数,例如只获取名字和密码
1
2
3
4
5
6
go复制代码// GetSomeParam SELECT username,password FROM users WHERE id = 1;
func GetSomeParam(id int64) (user *User) {
db := config.GetDb()
db.Model(&user).Select("username", "password").Find(&user,id)
return
}
  1. 分页查询,可以使用Limit & Offset进行分页查询
1
2
3
4
5
6
go复制代码// GetPage SELECT * FROM users OFFSET 5 LIMIT 10;
func GetPage(limit int,offset int) (user []*User) {
db := config.GetDb()
db.Model(&user).Limit(limit).Offset(offset).Find(&user)
return
}
  1. order
1
2
3
4
5
6
7
8
9
10
11
12
go复制代码// GetByOrder SELECT * FROM users ORDER BY id desc, username;
func GetByOrder() (user []*User) {
db := config.GetDb()
db.Model(&user).Order("id desc,username").Find(&user)
return
}
等同于
func GetByOrder() (user []*User) {
db := config.GetDb()
db.Model(&user).Order("id desc").Order("username").Find(&user)
return
}

更多请移步:gorm.io/zh_CN/docs/…

新增

  1. 创建单个(Create)
1
2
3
4
5
go复制代码func Create(user *User)  {
db := config.GetDb()
db.Model(&user).Create(&user)
return
}
  1. 保存单个(Save)
1
2
3
4
5
go复制代码func Save(user *User)  {
db := config.GetDb()
db.Model(&user).Save(&user)
return
}

CreateSave的区别:Save需要插入的数据存在则不进行插入,Create无论什么情况都执行插入

  1. 创建多个
1
2
3
4
5
go复制代码func CreateBatch(user []*User)  {
db := config.GetDb()
db.Model(&user).Create(&user)
return
}

更多请移步:gorm.io/zh_CN/docs/…

修改

  1. 更新单个字段
1
2
3
4
5
6
go复制代码// UpdateUsername UPDATE users SET username = "lomtom" where id = 1
func UpdateUsername(id int64,username string) {
db := config.GetDb()
db.Model(&User{}).Where("id = ?",id).Update("username",username)
return
}
  1. 全量/多列更新(根据结构体)
1
2
3
4
5
6
go复制代码// UpdateByUser UPDATE `user` SET `id`=14,`user_name`='lomtom',`password`='123456',`create_time`='2021-09-26 14:22:21.271',`update_time`='2021-09-26 14:22:21.271' WHERE id = 14 AND `user`.`deleted` IS NULL
func UpdateByUser(user *User) {
db := config.GetDb()
db.Model(&User{}).Where("id = ?",user.Id).Updates(&user)
return
}

更多请移步:gorm.io/zh_CN/docs/…

删除

  1. 简单删除(根据user里的id进行删除)
1
2
3
4
5
6
7
go复制代码// DeleteByUser DELETE from users where id = 28;
// DeleteByUser UPDATE `user` SET `deleted`='2021-09-26 14:25:33.368' WHERE `user`.`id` = 28 AND `user`.`deleted` IS NULL
func DeleteByUser(user *User) {
db := config.GetDb()
db.Model(&User{}).Delete(&user)
return
}

说明: 结构体未加gorm.DeletedAt标记的字段,直接删除,加了将更新deleted字段,即实现软删除

  1. 根据id进行删除
1
2
3
4
5
6
go复制代码// DeleteById UPDATE `user` SET `deleted`='2021-09-26 14:29:55.15' WHERE `user`.`id` = 28 AND `user`.`deleted` IS NULL
func DeleteById(id int64) {
db := config.GetDb()
db.Model(&User{}).Delete(&User{},id)
return
}

事务

同样,Gorm也有丰富的事务支持。

匿名事务

可使用db.Transaction匿名方法来表明多个操作在一个事务里面,返回err将回滚,返回nil将提交事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
go复制代码func Transaction() error {
db := config.GetDb()
err := db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
if err := tx.Create(&User{Username: "lomtom"}).Error; err != nil {
// 返回任何错误都会回滚事务
return err
}
if err := tx.Delete(&User{}, 28).Error; err != nil {
return err
}
// 返回 nil 提交事务
return nil
})
if err != nil {
return err
}
return nil
}

手动事务

db.Begin()表明一个事务的开始,出现错误使用tx.Rollback(),事务提交使用tx.Commit()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
go复制代码func Transaction1() error {
db := config.GetDb()
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
if err := tx.Create(&User{Username: "lomtom"}).Error; err != nil {
// 回滚事务
tx.Rollback()
return err
}
if err := tx.Delete(&User{}, 28).Error; err != nil {
tx.Rollback()
return err
}
// 提交事务
return tx.Commit().Error
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%