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

作者:lomtom

个人网站:lomtom.top

个人公众号:博思奥园

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

Go系列:

  1. Go(一)基础入门
  2. Go(二)结构体
  3. Go(三)Go配置文件
  4. Go(四)Redis操作
  5. Go(五)Go不知道怎么用Gorm?

前言

所有的后端应用都离不开数据库的操作,在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使用有稍许疑惑的话,请移步官方文档:https://gorm.io/zh_CN/docs/index.html

安装

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

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

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

在使用时引入依赖即可

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

建立连接

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

最基本的连接

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

更多参数设置:https://github.com/go-sql-driver/mysql#parameters

设置连接池

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

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

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
}

全局连接

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

var db *gorm.DB

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

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

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配置文件

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

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

定义相应的结构体

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抽取出来

//获取链接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)
}

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

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的日志

//初始化数据库日志
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{})

conn, err1 := gorm.Open(mysql.Open(mySQLUri()), &gorm.Config{
	Logger: newLogger,
})

使用

GormCURD相对来说叶比较简单。

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

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. 获取第一个,默认查询第一个
// GetFirst SELECT * FROM users ORDER BY id LIMIT 1;
func GetFirst() (user *User) {
	db := config.GetDb()
	db.Model(&user).First(&user)
	return 
}
  1. 获取最后一个
// GetLast SELECT * FROM users ORDER BY id DESC LIMIT 1;
func GetLast() (user *User) {
	db := config.GetDb()
	db.Model(&user).Last(&user)
	return
}
  1. 通过主键获取
// 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. 通过主键批量查询
// 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. 获取部分参数,例如只获取名字和密码
// 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进行分页查询
// 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
// 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
}

更多请移步:https://gorm.io/zh_CN/docs/query.html

新增

  1. 创建单个(Create)
func Create(user *User)  {
	db := config.GetDb()
	db.Model(&user).Create(&user)
	return
}
  1. 保存单个(Save)
func Save(user *User)  {
	db := config.GetDb()
	db.Model(&user).Save(&user)
	return
}

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

  1. 创建多个
func CreateBatch(user []*User)  {
	db := config.GetDb()
	db.Model(&user).Create(&user)
	return
}

更多请移步:https://gorm.io/zh_CN/docs/create.html

修改

  1. 更新单个字段
// 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. 全量/多列更新(根据结构体)
// 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
}

更多请移步:https://gorm.io/zh_CN/docs/update.html

删除

  1. 简单删除(根据user里的id进行删除)
// 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进行删除
// 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将提交事务

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()

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
}
上一篇:golang gorm update or insert


下一篇:gorm中使用Updates更新非空的字段