首页 > 其他分享 >一篇玩转Gorm框架的CRUD

一篇玩转Gorm框架的CRUD

时间:2023-06-24 19:34:12浏览次数:46  
标签:CRUD 玩转 fmt db gorm result Println Gorm User

简介

关于ORM(Object-Relational Mapping)

ORM其实指的是将关系型数据库中的数据和面向对象程序中对象模型进行映射的技术; ORM可以用来自动化处理SQL语句的生成和执行,程序员可以更专注于业务逻辑的实现而不是数据的细节。

为什么需要ORM?(参考gpt,结合自身使用过程)

  • 提高开发效率(自动化生成SQL,减少手动编写SQL时间)
  • 只需要定义好模型,可以自动处理不同数据库之间的差异;(如果传统编写,换数据库相当于需要吧原来的逻辑重写一遍)
  • 易于维护,方便拓展(传统编写SQL如果多起来,排查问题和重构时很痛苦);
  • 安全,参数化查询避免SQL注入

关于Gorm

Gorm是一个全功能的ORM框架,主要针对Go语言而开发,支持处理主流的关系型数据库(MySQL、PostgreSQL、SQL Server) 以及一些NoSQL数据库。由于这边是直接使用的是Gorm 2,关于Gorm的重要特性可以以gorm官网为准,可以直接参考:

https://gorm.io/zh_CN/docs/index.html#%E5%AE%89%E8%A3%85

安装和设置

快速安装Gorm2,Mysql驱动

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

初始化Mysql连接(参考官网)

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

func main() {
  // 参考 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{})
}

备注:

  • 定义DSN(Data Source Name)连接数据库字符串dsn;
  • 传入MySQL驱动,初始化gorm数据库连接;

这是我自己定义初始化连接,读取对应配置文件

type Config struct {
	MysqlConfig DBConfig `json:"mysql"`
}

type DBConfig struct {
	Username string `json:"username"`
	Password string `json:"password"`
	Host     string `json:"host"`
	Port     int    `json:"port"`
	DBName   string `json:"prefix"`
}

func initDB() (db *gorm.DB, err error) {
	confPath := "conf.json"
	if _, err = os.Stat(confPath); err != nil {
		return
	}

	var config Config
	if err = configor.Load(&config, confPath); err != nil {
		return
	}

	// 新建Database Gorm连接
	mysqlConfig := &config.MysqlConfig
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4",
		mysqlConfig.Username, mysqlConfig.Password, mysqlConfig.Host,
		mysqlConfig.Port, mysqlConfig.DBName)
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})

	return
}

func main() {

	db, err := initDB()

	if err != nil {
		log.Fatalf(err.Error())
		return
	}
	
	db = db.Debug()
	execSQL(db)
}

备注:

  • 这边我在项目中定义了关于数据库连接的结构体,通过读取预设好的config.json文件,加载好预设的配置信息;
  • 另外这边的db = db.Debug(),可以开启db的DEBUG模式,能够打印出执行的SQL语句以及对应的参数,以方便调试;

config.json参考

{
  "mysql": {
    "username": "root",
    "password": "root",
    "host": "127.0.0.1",
    "prefix": "GormDemo",
    "port": 3306
  }
}

定义模型

由于Gorm模型的结构体名的蛇形复数作为表名,字段名的蛇形作为列名,如果Mysql表的设计遵循了GORM约定,则可少写很多代码, 但是实际情况,往往不是这样:

type User struct {
	ID string `gorm:"column:id" faker:"-"`
	Email string `gorm:"column:email" faker:"email"`
	Password string `gorm:"column:password" faker:"password"`
	PhoneNumber string `gorm:"column:phone_number" faker:"phone_number"`
	UserName string `gorm:"column:username" faker:"username"`
	FirstName string `gorm:"first_name" faker:"first_name"`
	LastName string `gorm:"last_name" faker:"last_name"`
	Century string `gorm:"century" faker:"century"`
	Date string `gorm:"date" faker:"date"`
}

func (u User) TableName() string {
	return "user"
}

备注:

  • 这是我定义的结构体User,通过实现Tabler接口来更改默认表名,TableName()方法会将Music表名重写为user(如果不修改则会默认为users);
  • 修改对应字段对应数据的列名,修改标签column;
  • 模型中的faker的标签可以忽略,这边主要是生成假数据才会使用到的;

CRUD

创建

单挑记录插入

// insertOneRow 单挑记录插入
func insertOneRow(db *gorm.DB){
	var tmpUser *User

	// 生成随机数据
	err := faker.FakeData(&tmpUser)
	if err != nil {
		log.Fatalf(err.Error())
	}

	result := db.Create(tmpUser)
	if result.Error != nil {
		log.Fatalf(result.Error.Error())
	}

	fmt.Println("result RowsAffected: ", result.RowsAffected)
	fmt.Printf("%+v\n", tmpUser)

}

备注:

  • 由于想生成写测试数据,所以使用了github.com/go-faker/faker生成假数据,有兴趣可以了解,像前面的结构体定义Tag之后就能生成对应的假数据;
  • 基本创建单条记录没啥好说的,只要定义模型的结构体,补充数据,然后插入即可;

批量记录插入

// insertRows 批量插入
func insertRows(db *gorm.DB) {
	var users []*User
	for i := 0; i < 10; i++ {
		tmpUser := User{}
		err := faker.FakeData(&tmpUser)
		if err != nil {
			log.Fatal(err.Error())
		}
		users = append(users, &tmpUser)
	}

	result := db.Create(users)

	if result.Error != nil {
		log.Fatalf(result.Error.Error())
	}

	fmt.Println("RowsAffected: ", result.RowsAffected)

	for _, m := range users {
		fmt.Printf("%+v\n", m)
	}
}

定义钩子函数

func (u *User) BeforeCreate(tx *gorm.DB) (err error){
	u.ID = uuid.New()
	return nil
}

备注:

  • 在插入记录之前,生成uuid(具体根据实际业务场景补充处理的业务逻辑,包括检验等)
  • 官方还提供提供很多额外的钩子函数(BeforeSave,AfterSave等)具体参考官方提供的文档

查询

简单查询

func printRecord(u *User, result *gorm.DB){
	fmt.Printf("%v\n", u)
	fmt.Println(result.Error, result.RowsAffected)
}

func printRecords(u []*User, result *gorm.DB){

	for _, u := range u {
		fmt.Println(u)
	}
	fmt.Println(result.Error, result.RowsAffected)

}

func simpleQueryRow(db *gorm.DB){

	// 查询第一条记录(主键升序)
	var firstUser *User
	result := db.First(&firstUser)
	printRecord(firstUser, result)

	// 仅当有一个ID主键时,可直接定义User时把ID初始化
	firstIDUser2 := &User{ID: "e8efff22-a497-4a88-be1e-5123eb23ff75"}
	result = db.First(&firstIDUser2)
	printRecord(firstIDUser2, result)

	// 查询表中第一条记录(没有指定排序字段)
	var firstUser2 *User
	result = db.Take(&firstUser2)
	printRecord(firstUser2, result)

	// 查询表中最后一条记录(主键排序)
	var lastUser *User
	result = db.Last(&lastUser)
	printRecord(lastUser, result)

	// 查询当前所有记录
	var users []*User
	result = db.Find(&users)
	printRecords(users, result)

}

备注:

  • 这几个都是按照官方给出的示例,整理了一下日常比较常用到的查询操作

条件查询

func condQueryRow(db *gorm.DB){

	// 查询当前username为condQueryRow的第一条记录(Struct方式)
	var tmpUser1 *User
	result := db.Where(&User{UserName: "qNptxqb"}).First(&tmpUser1)
	printRecord(tmpUser1, result)

	// 查询当前username为condQueryRow的第一条记录(Map方式)
	var tmpUser2 *User
	result = db.Where(map[string]interface{}{"username": "qNptxqb"}).First(&tmpUser2)
	printRecord(tmpUser2, result)

	// 指定Century查询字段查询记录
	var tmpUser3 []User
	result = db.Where(&User{Century: "VII", UserName: "jaQlaFs"}, "Century").Find(&tmpUser3)
	printRecords(tmpUser3, result)

	// String 条件,直接写表达式
	var tmpUser4 *User
	result = db.Where("username = ?", "qNptxqb").First(&tmpUser4)
	printRecord(tmpUser4, result)

	var users []User
	result = db.Where("date > ?", "2010-10-1").Find(&users)
	printRecords(users, result)

	// Order排序(默认升序)
	var users2 []User
	result = db.Where("date > ?", "2010-10-1").Order("date").Find(&users2)
	printRecords(users2, result)

	// 查询特定的字段,不返回所有字段
	var tmpUser5 *User
	result = db.Select("username", "date").Where("username = ?", "qNptxqb").First(&tmpUser5)
	printRecord(tmpUser5, result)
}

备注:

  • Gorm提供了很多条件查询的场景,目前对我而言基本的查询业务逻辑都能支持。具体可能出现一些复杂的sql,后面会介绍怎么直接使用sql查询;

高级查询

type APIUser struct {
	ID string `gorm:"primaryKey,column:id"`
	UserName string `gorm:"column:username"`
	FirstName string `gorm:"first_name"`
	LastName string `gorm:"last_name"`
}

func advancedQueryRow(db *gorm.DB){

	// 智能选择字段,如果经常只需要查询某些字段,可以重新定义小结构体
	var apiUser []APIUser
	result := db.Model(&User{}).Find(&apiUser)
	for _, user := range apiUser{
		fmt.Println(user)
	}
	fmt.Println(result.Error, result.RowsAffected)

	// 扫描结果绑定值map[string]interface{} 或者 []map[string]interface{}
	var users []map[string]interface{}
	result = db.Model(&User{}).Find(&users)
	for _, user := range users{
		fmt.Println(user)
	}
	fmt.Println(result.Error, result.RowsAffected)

	// Pluck查询单个列,并将结果扫描到切片
	var emails []string
	result = db.Model(&User{}).Pluck("email",&emails)
	fmt.Println(emails)
	fmt.Println(result.Error, result.RowsAffected)

	// Count查询
	var count int64
	result = db.Model(&User{}).Where("date > ?", "2012-10-22").Count(&count)
	fmt.Println(count)
	fmt.Println(result.Error, result.RowsAffected)
}

备注:

  • 定义小结构体可以实现在调用API时自动选择特定字段
  • Pluck适合查询单个列,如果需要查询多个常用字段可以通过Select和Scan

更新

常用更新操作

func updateRow(db *gorm.DB){
	// Save会保存所有字段,即使字段是零值,如果保存的值没有主键,就会创建,否则则是更新指定记录
	result := db.Save(&User{ID: "e8efff22-a497-4a88-be1e-5123eb23ff75", UserName: "zhangsan", Date: "2023-12-12"})
	fmt.Println(result.Error, result.RowsAffected)

	// 更新单个列
	result = db.Model(&User{}).Where("username = ?", "jaQlaFs").Update("first_name", "zhangsan")
	fmt.Println(result.Error, result.RowsAffected)

	// 更新多个列
	result = db.Model(&User{}).Where("username = ?", "zhangsan").Updates(User{FirstName: "zhangsan2", LastName: "zhangsan3"})
	fmt.Println(result.Error, result.RowsAffected)

	// 更新指定列(Select指定last_name)
	result = db.Model(&User{}).Where("username = ?", "zhangsan").Select("last_name").Updates(User{FirstName: "zhangsan2", LastName: "zhangsan4"})
	fmt.Println(result.Error, result.RowsAffected)
}

备注:

  • 具体一些更新的操作和查询类似,具体区分即可;

删除

常用删除操作

func deleteRows(db *gorm.DB){

	// 指定匹配字段删除数据
	result := db.Delete(&User{}, map[string]interface{}{"username": "NJrauTj"})
	fmt.Println(result.Error, result.RowsAffected)

	result = db.Delete(&User{}, "username = ?", "NJrauTj")
	fmt.Println(result.Error, result.RowsAffected)

	// Where指定字段匹配删除数据
	result = db.Where("username = ? and phone_number = ?", "jXQKmPv", "574-821-9631").Delete(&User{})
	fmt.Println(result.Error, result.RowsAffected)

	// 批量删除的两种方式
	result = db.Where("email like ?", "%.com%").Delete(&User{})
	fmt.Println(result.Error, result.RowsAffected)

	result = db.Delete(&User{}, "email like ?", "%.com%")
	fmt.Println(result.Error, result.RowsAffected)
}

备注:

  • 普通删除常用场景匹配指定单挑数据删除以及批量删除,语法和更新类似;
  • Gorm文档中有涉及禁用全局删除,即当执行不带任何条件的批量删除时就会返回错误;以及关于删除的钩子函数有实用 场景的同学,可以看官方文档,这里不再赘述;

原生SQL和SQL生成器

// execSQL 执行原生SQL语句
func execSQL(db *gorm.DB){

	// 将查询SQL的结果映射到指定的单个变量中
	var oneUser User
	result := db.Raw("SELECT * FROM user LIMIT 1").Scan(&oneUser)
	fmt.Println(oneUser)
	fmt.Println(result.Error, result.RowsAffected)

	// 将查询SQL的批量结果映射到列表中
	var users []User
	result = db.Raw("SELECT * FROM user").Scan(&users)
	for _, user := range users {
		fmt.Println(user)
	}
	fmt.Println(result.Error, result.RowsAffected)

	var updateUser User
	result = db.Raw("UPDATE users SET username = ? where id = ?", "toms jobs", "ab6f089b-3272-49b5-858f-a93ed5a43b4f").Scan(&updateUser)
	fmt.Println(updateUser)
	fmt.Println(result.Error, result.RowsAffected)

	// 直接通过Exec函数执行Update操作,不返回任何查询结果?
	result = db.Exec("UPDATE user SET username = ? where id = ?", "toms jobs", "ab6f089b-3272-49b5-858f-a93ed5a43b4f")
	fmt.Println(result.Error, result.RowsAffected)

	// DryRun模式,在不执行的情况下生成SQL及其参数,可以用于准备或测试的SQL
	var tmpUsers []APIUser
	stmt := db.Session(&gorm.Session{DryRun: true}).Model(&User{}).Find(&tmpUsers).Statement
	fmt.Println(stmt.SQL.String())
	fmt.Println(stmt.Vars)
}

备注:

  • Scan函数是会将查询SQL的结果映射到定义的变量,如果不需要返回查询结果可以直接使用Exec函数执行原生SQL;
  • DryRun模式,可以直接生成SQL机器参数,但是不会直接执行;

总结

关于Gorm的CURD日常使用就介绍到这里,如果同学对Gorm感兴趣,可以接去Gorm官网,我这里只是简单介绍一下在日常业务环境经常使用的操作,具体Gorm中文文档地址。

至于Gorm后面可能还会出几篇文章介绍Gorm模型之间的关联关系,以及关联模式下的CRUD;还有关于Gorm的性能优化,具体能搭配一些常用的插件。

上述文章的源码:

https://github.com/libuliduobuqiuqiu/GoDemo/blob/master/GormDemo/gorm_demo.go

标签:CRUD,玩转,fmt,db,gorm,result,Println,Gorm,User
From: https://blog.51cto.com/mbb97/6541502

相关文章

  • 《零死角玩转 STM32》pdf版电子书免费下载
    《零死角玩转STM32》系列教程由初级篇、中级篇、高级篇、系统篇、四个部分组成,根据野火STM32开发板旧版教程升级而来,且经过重新深入编写,重新排版,更适合初学者,步步为营,从入门到精通,从裸奔到系统,让您零死角玩转STM32。适合stm32入门,简单易懂,层层递进,亲测效果良好,适合新手学习,......
  • mongodb-crud基本操作
    删库>db.dropDatabase()创建集合(表)>db.createCollection('a')>db.createCollection('b')直接插入文档,集合会自动创建>db.c.insert({username:'mongodb'})>showcollections删除集合>db.a.drop()重命名集合>db.b.renameCollec......
  • 《Red Hat Linux命令速查》—— 带你玩转字符游戏
    命令行管理,一个玩转字符的战场!忽隐忽现的光标  神秘莫测的符号  闪转腾挪的玄机  直捣黄龙的快意能领略这一切的人,只有你——深谙命令行管理之道的系统管理员和软件开发人员!命令行之于优秀的系统管理员、软件开发人员,恰如武林高手必须修炼的内功心法,一旦掌握,不仅可以大大提高......
  • 5步带你玩转SpringBoot自定义自动配置那些知识点
    目前SpringBoot框架真的深受广大开发者喜爱,毕竟它最大的特点就是:快速构建基于Spring的应用程序的框架,而且它提供了各种默认的功能和配置,可以让开发者快速搭建应用程序的基础结构。但是,当我们需要自定义一些配置时,我们就需要使用自定义自动配置。今天一定让大家深刻体验干货知识点......
  • casbin + gin + gorm
    实际项目中肯定要用rbac这种权限模型,因此model使用如下rbac_model.conf[request_definition]r=sub,obj,act[policy_definition]p=sub,obj,act[role_definition]g=_,_[policy_effect]e=some(where(p.eft==allow))[matchers]#当访问实体为root时直......
  • Git使用教程(带你玩转GitHub)
    Git使用教程(理论实体结合体系版)下载安装:按照这个博客来就好Windows系统Git安装教程(详解Git安装过程)-学为所用-博客园(cnblogs.com)Git命令大全:Git大全-Gitee.com最小配置:在桌面右键点击GitBashHere进入命令行,GUI我们不常用。首先要设置你的用户名称和e-mail......
  • CRUD
    1.Insert//插入一条新的数据,必须全插入insertinto表名values(值)//选择列名插入数据,必须一一对应insertinto表名(字段...)values(值...)2.Update//可以更条件update表名set列名=新值.....[where....]3.Delete//不加条件会删除整个表的数据deletefrom表名......
  • gorm简介
    gorm简介什么是gorm?gorm是一个强大的Go编程语言中的ORM(对象关系映射)库。ORM是一种技术,它将数据库表中的数据映射到面向对象的模型中,从而简化了数据库操作。gorm的特点gorm具有许多令人称赞的特点,使其成为Go开发者的首选ORM库之一。1.简单易用gorm提供了简洁而直观的API,使得......
  • Spring Boot实现高质量的CRUD-5
    (续前文)9、Service实现类代码示例 ​​ 以用户管理模块为例,展示Service实现类代码。用户管理的Service实现类为UserManServiceImpl。​UserManServiceImpl除了没有deleteItems方法外,具备CRUD的其它常规方法。实际上​UserManService还有其它接口方法,如管理员修改密码,用户修改自身......
  • 对象存储?CRUD Boy实现对文件的增删改查
    大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。以下是正文!对象存储是什么?对象存储是一种数据存储方式,它将数据分割成不同的对象,并为每个对象分配一个唯一的标识符,用于访问和操作数......