简介 关于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驱动 1 2 go get gorm.io/gorm go get gorm.io/driver/mysql
初始化Mysql连接(参考官网) 1 2 3 4 5 6 7 8 9 10 import ( "gorm.io/driver/mysql" "gorm.io/gorm" ) func main () { 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数据库连接;
这是我自己定义初始化连接,读取对应配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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 } 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参考
1 2 3 4 5 6 7 8 9 { "mysql" : { "username" : "root" , "password" : "root" , "host" : "127.0.0.1" , "prefix" : "GormDemo" , "port" : 3306 } }
定义模型 由于Gorm模型的结构体名的蛇形复数作为表名,字段名的蛇形作为列名,如果Mysql表的设计遵循了GORM约定,则可少写很多代码, 但是实际情况,往往不是这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 创建 单挑记录插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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之后就能生成对应的假数据;
基本创建单条记录没啥好说的,只要定义模型的结构体,补充数据,然后插入即可;
批量记录插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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) } }
定义钩子函数
1 2 3 4 func (u *User) BeforeCreate(tx *gorm.DB) (err error ){ u.ID = uuid.New() return nil }
备注:
在插入记录之前,生成uuid(具体根据实际业务场景补充处理的业务逻辑,包括检验等)
官方还提供提供很多额外的钩子函数(BeforeSave,AfterSave等)具体参考官方提供的文档
查询 简单查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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) 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) }
备注:
这几个都是按照官方给出的示例,整理了一下日常比较常用到的查询操作
条件查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func condQueryRow (db *gorm.DB) { var tmpUser1 *User result := db.Where(&User{UserName: "qNptxqb" }).First(&tmpUser1) printRecord(tmpUser1, result) var tmpUser2 *User result = db.Where(map [string ]interface {}{"username" : "qNptxqb" }).First(&tmpUser2) printRecord(tmpUser2, result) var tmpUser3 []User result = db.Where(&User{Century: "VII" , UserName: "jaQlaFs" }, "Century" ).Find(&tmpUser3) printRecords(tmpUser3, result) 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) 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查询;
高级查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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) var users []map [string ]interface {} result = db.Model(&User{}).Find(&users) for _, user := range users{ fmt.Println(user) } fmt.Println(result.Error, result.RowsAffected) var emails []string result = db.Model(&User{}).Pluck("email" ,&emails) fmt.Println(emails) fmt.Println(result.Error, result.RowsAffected) 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
更新 常用更新操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func updateRow (db *gorm.DB) { 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) result = db.Model(&User{}).Where("username = ?" , "zhangsan" ).Select("last_name" ).Updates(User{FirstName: "zhangsan2" , LastName: "zhangsan4" }) fmt.Println(result.Error, result.RowsAffected) }
备注:
删除 常用删除操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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) 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生成器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 func execSQL (db *gorm.DB) { var oneUser User result := db.Raw("SELECT * FROM user LIMIT 1" ).Scan(&oneUser) fmt.Println(oneUser) fmt.Println(result.Error, result.RowsAffected) 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) result = db.Exec("UPDATE user SET username = ? where id = ?" , "toms jobs" , "ab6f089b-3272-49b5-858f-a93ed5a43b4f" ) fmt.Println(result.Error, result.RowsAffected) 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