# Golang orm框架gorm学习 ## GORM - ORM(Object Relation Mapping),对象关系映射,实际上就是对数据库的操作进行封装,对上层开发人员屏蔽数据操作的细节,开发人员看到的就是一个个对象,大大简化了开发工作,提高了生产效率 - O = Object对象(程序中的对象/实例 如:Struct结构体) - R = Relational关系(关系型数据库 如:mysql) - M = Mapping映射 - 结构体 -> 数据表 - 结构体实例 -> 数据行 - 结构体字段 -> 表字段 - ORM 优缺点 - 优点 - 提高开发效率,不需要使用SQL语句。 - 缺点 - 执行性能比较差、灵活性较差、弱化了SQL能力 ## GORM概览 1. 全功能ORM(无线接近) 2. 关联(Has One, Has Many,Belongs To,Many To Many,多态) 3. 钩子(在创建/保存/更新/删除/查找之前或之后) 4. 预加载 5. 事务 6. 复合主键 7. SQL生成器 8. 数据库自动迁移 9. 自定义日志 10. 可扩展性,可基于GORM回调编写插件 11. 所有功能都被测试覆盖 12. 开发者友好 ### 安装 ``` go get -u github.com/jinzhu/gorm ``` ### 数据库连接 ``` package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" ) func main(){ //dsn格式 user:pass@tcp(ip:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local dsn := "root:root@tcp(192.168.116.90:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local" db,err := gorm.Open("mysql",dsn) if err != nil{ fmt.Printf("gorm open failed ,error: %v\n",err) return } //设置最大连接数 db.DB().SetMaxOpenConns(100) //设置最大空闲连接数 db.DB().SetMaxIdleConns(50) defer db.Close() } ``` ### 数据模型定义 #### 表名、列名如何对应结构体 在Gorm中,表名是结构体名的复数形式,列名是字段名的蛇形小写 即,如果有一个user表,那么如果你定义的结构体名为:User ,gorm会默认表名为users而不是user。 例如有数据库如下表结构定义 ``` CREATE TABLE `areas` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id', `area_id` int(11) NOT NULL COMMENT '区县id', `area_name` varchar(45) NOT NULL COMMENT '区县名', `city_id` int(11) NOT NULL COMMENT '城市id', `city_name` varchar(45) NOT NULL COMMENT '城市名称', `province_id` int(11) NOT NULL COMMENT '省份id', `province_name` varchar(45) NOT NULL COMMENT '省份名称', `area_status` tinyint(3) NOT NULL DEFAULT '1' COMMENT '该条区域信息是否可用 : 1:可用 2:不可用', `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_IN ``` 那么他在gorm中对应的结构体定义如下: ``` type Area struct { Id int AreaId int AreaName string CityId int CityName string ProvinceId int ProvinceName string AreaStatus int CreatedAt time.Time UpdatedAt time.Time } ``` - 从上面实例可以看到结构体Area 对应的数据库表名为 areas - 结构体的字段如 AreaId 对应的则是 数据表中字段area_id 如何全局禁用表名复数呢? 可以在创建数据库连接的时候设置如下参数: ``` // 全局禁用表名复数 db.SingularTable(true) //如果设置为true,结构体User的默认表名为user,使用TableName 设置的表名不会受到影响 ``` 这样的话,表名默认即为结构体的首字母小写形式。 ### CRUD使用 #### 环境准备 创建一张User表来做CRUD,表结构如下: ``` CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(30) NOT NULL DEFAULT '', `age` int(3) NOT NULL DEFAULT '0', `sex` tinyint(3) NOT NULL DEFAULT '0', `phone` varchar(40) NOT NULL DEFAULT '', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4; ``` #### 首先初始化数据库连接 ``` package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" ) var db *gorm.DB type User struct { Id int Name string Age int Sex byte Phone string } func init() { var err error //dsn格式 user:pass@tcp(ip:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local dsn := "root:root@tcp(192.168.116.90:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local" db,err = gorm.Open("mysql",dsn) if err != nil{ panic(err) } //设置最大连接数 db.DB().SetMaxOpenConns(100) //设置最大空闲连接数 db.DB().SetMaxIdleConns(50) //设置全局表名禁用复数 db.SingularTable(true) } ``` 下面所有的操作都是在上面的初始化连接上执行的操作 #### 插入 ``` func(user *User) Insert(){ //这里使用Table()函数,如果没有指定全局表名禁用复数,或者是表名跟结构体名不一样的时候 //可以在自己的sql中指定表名。如下示例 db.Table("user").Create(user) } func main(){ var u =User{ Name: "xieys", Age: 20, } u.Insert() } ``` #### 更新 - 注意,Model方法必须要和Update方法一起使用 - 使用效果相当于Model中设置更新的主键key(如果没有where指定,那么默认更新的key为id),Update中设置更新的值 - 如果Model中没有指定id值,且也没有指定where条件,那么将更新全表 > 示例1: ``` func main(){ var u =User{ Id: 10, //注意如果是更新,并且没有where条件的话,必须制定id值,否则会更新全表 Name: "xieys", Age: 21, } db.Model(&u).Update(u) } 相当于sql语句:update user set name="xieys",age="21" where id=10 ``` > 示例2: ``` func main(){ var u =User{ Age: 24, } db.Model(&u).Where("id = 8").Update(u) } 这里没有携带Id,所以在更新的时候需要加Where条件,否则更新全表 相当于sql语句:update user set age=24 where id=8 ``` 注意到上面Update中使用了一个Struct,也可以使用map对象。 **需要注意的是:使用struct的时候,只会更新struct中这些非空的字段。对于string类型字段的"",int类型字段0,bool类型字段的false都会认为是空白纸,不会去更新表** > 示例3 ``` func main(){ db.Model(&User{}).Where("age = ?",22).Update("name","xxx") } 相当于sql语句:update user set name="xxx" where age = 22 func main(){ db.Model(&User{}).Where("name = ?","xxx").Update("age",0) } 相当于sql语句:update user set age = 0 where name="xxx" ``` > 示例4 ``` Select 指定只更新某个字段,没有指定的字段将被忽略 func main(){ db.Model(&u).Select("name").Update(map[string]interface{}{"name":"","age":0}) //这里age字段将被忽略,不会被更新,更新的只有name字段 // 如果想要更新name和age字段应该这样写 //db.Model(&u).Select("name","age").Update(map[string]interface{}{"name":"","age":0}) } ``` > 示例5 ``` 忽略掉某些字段 当你的更新的参数为结构体,而结构体中某些字段你又不想去更新,那么可以使用Omit方法过滤掉这些不想update到库的字段,忽略字段使用Select也可以实现,具体看示例4 func main(){ var u =User{ Id: 8, Name: "xx" Age: 24, } db.Model(&u).Omit("name").Update(&u) //这里name字段不会更新,只会更新age字段 //db.Model(&u).Select("name").Update(&u) //这里是name字段会更新,age字段被忽略 } ``` #### 删除 > 单记录删除 ``` func main(){ user := User{Id : 8} db.Delete(&user) } // delete user where id = 8 ``` > 批量删除 ``` func main(){ db.Delete(&User{},"id > ?",8) } // delete user where id > 8 ``` #### 事务 ``` func CreateUsers()error{ tx := db.Begin() // 注意,一旦在事务中,使用tx作为数据库句柄 if err := tx.Create(&User{Name: "XXX"}).Error;err != nil{ tx.Rollback() // 回滚 return err } if err := tx.Create(&User{Name: "yyy"}).Error;err != nil{ tx.Rollback() return err } tx.Commit() return nil } func main(){ CreateUsers() } ``` #### 查询 ##### 查询所有字段 > 查询所有记录 ``` func main(){ var user []User db.Find(&user) fmt.Printf("%v\n",user) } ``` > 带where条件的查询 ``` func main(){ var user []User // 带where子句的查询,注意where要再find的前面 db.Where("id > ?",11).Find(&user) fmt.Printf("%v\n",user) } ``` > in 查询 ``` func main(){ var user []User // 带where子句的查询,注意where要再find的前面 db.Where("id in (?)",[]int{11,12}).Find(&user) fmt.Printf("%v\n",user) } ``` > 获取第一条记录,按照主键顺序排序 ``` func main(){ var user []User db.First(&user) fmt.Printf("%v\n",user) } ``` > 获取第一条记录,带where条件 ``` func main(){ var user []User db.First(&user,"id = ?",12) fmt.Printf("%v\n",user) } ``` > 获取最后一条记录 ``` func main(){ var user []User db.Last(&user) fmt.Printf("%v\n",user) } 同样也可以加where条件 func main(){ var user []User db.Last(&user,"id = ?",11) fmt.Printf("%v\n",user) } ``` ##### 查询指定字段 - Select > 示例 ``` //指定查询字段 func main(){ var user []User db.Select("name,age").Where(map[string]interface{}{"age":0,"sex":0}).Find(&user) fmt.Printf("%v\n",user) } //相当于sql语句 select name,age from user where age = 0 and sex = 0 ``` ##### 使用struct或map作为查询条件 > 示例 Struct ``` func main(){ var user []User //使用struct,相当于: select * from user where age = 10 and sex = 0 db.Where(&User{Age: 10,Sex: 0}).Find(&user) fmt.Printf("%v\n",user) } ``` > 示例 map ``` func main(){ var user []User db.Where(map[string]interface{}{"age":10,"sex":0}).Find(&user) fmt.Printf("%v\n",user) } 相当于: select * from user where age = 10 and sex = 0 ``` ##### not in 条件的使用 > 示例 ``` func main(){ var user []User db.Not("name",[]string{"xxx","aaa"}).Find(&user) fmt.Printf("%v\n",user) } //相当于 : select * from user where name not in ("xxx","aaa") ``` ##### or的使用 > 示例 ``` func main(){ var user []User db.Where("age > ?",10).Or("sex = ?",0).Find(&user) fmt.Printf("%v\n",user) } 相当于 : select * from user where age > 10 or sex = 0 ``` ##### order by的使用 ``` func main(){ var user []User db.Where("age > ?",10).Or("sex = ?",0).Order("id desc").Find(&user) fmt.Printf("%v\n",user) } 相当于 : select * from user where age > 10 or sex = 0 order by id desc ``` ##### limit的使用 > 示例 ``` func main(){ var user []User db.Not("name",[]string{"aa","bb"}).Limit(1).Find(&user) fmt.Printf("%v\n",user) } 相当于 : select * from user where name not in ("aa","bb") limit 1 ``` ##### offset的使用 > 示例 ``` func main(){ var user []User db.Not("name",[]string{"aa","bb"}).Limit(1).Offset(1).Find(&user) fmt.Printf("%v\n",user) } 相当于 : select * from user where name not in ("aa","bb") limit 1,1 相当于从偏移量为1,从第二条开始取1条数据 ``` ##### count(*) > 示例 ``` func main(){ var count int db.Table("user").Where("age = ?",0).Count(&count) fmt.Println(count) } 相当于 select count(*) from user where age = 0 ``` ##### group & having > 示例 ``` func main(){ //var user []User rows,err := db.Table("user").Select("count(age),sex").Group("sex").Having("count(age)>0").Rows() if err != nil{ panic(err) } for rows.Next(){ fmt.Println(rows.Columns()) } } 相当于sql : select count(age),sex from user group by sex having count(age) > 0 ``` ##### join > 示例 ``` 新建一个关联表 CREATE TABLE `user_ext` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `address` varchar(50) default '', `uid` bigint(20) ,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4; insert into user_ext(address,uid) values('xxxx',11); func main(){ var res []Result db.Table("user u").Select("u.name,u.age,ue.address").Joins("left join user_ext ue on u.id = ue.uid").Find(&res) fmt.Println(res) } 相当于sql : SELECT u.name,u.age,ue.address from user u left join user_ext ue on u.id = ue.uid; ``` 如果有多个连接,可以用多个Joins方法 ##### 原生函数 > 示例 ``` func main(){ db.Exec("UPDATE user SET name = ? where id IN (?)","xxxx",[]int{11,13}) } func main(){ var users []User db.Exec("select * from user where id > ?",10).Find(&users) fmt.Println(users) //db.Exec("DROP TABLE user;") } ``` ##### 其他函数 > **FirstOrInit 和 FirstOrCreate** 获取第一个匹配的记录,若没有,则根据条件初始化一个新的记录(并不会存到数据库里): ``` //注意:where条件只能使用Struct或者map。如果这条记录不存在,那么会新增一条name=xiaoming的记录 db.FirstOrInit(&u,User{Name:"xiaoming"}) //同上 db.FirstOrCreate(&u,User{Name:"xiaoming"}) ``` > **Attrs** 如果没有找到记录,则使用Attrs中的数据来初始化一条记录: ```go //使用attrs来初始化参数,如果未找到数据则使用attrs中的数据来初始化一条 //注意:attrs 必须 要和FirstOrInit 或者 FirstOrCreate 连用 db.Where(User{Name:"xiaoming"}).Attrs(User{Name:"xiaoming",Age:12}).FirstOrInit(&u) ``` > **Assign** ```go //不管是否找的到,最终返回结构中都将带上Assign指定的参数 db.Where("age > 12").Assign(User{Name:"xiaoming"}).FirstOrInit(&u) ``` > **Pluck** 如果user表中你只想查询age这一列,该怎么返回呢,gorm提供了Pluck函数用于查询单列,返回数组: ```go var ages []int db.Find(&u).Pluck("age",&ages) ``` > **Scan** Scan函数可以将结果转存储到另一个结构体中。 ```go type SubUser struct{ Name string Age int } db.Table("user").Select("name,age").Scan(&SubUser) ``` > **sql.Row & sql.Rows** row和rows用户获取查询结果。 ```go //查询一行 row := db.Table("user").Where("name = ?", "xiaoming").Select("name, age").Row() // (*sql.Row) //获取一行的结果后,调用Scan方法来将返回结果赋值给对象或者结构体 row.Scan(&name, &age) //查询多行 rows, err := db.Model(&User{}).Where("sex = ?",1).Select("name, age, phone").Rows() // (*sql.Rows, error) defer rows.Close() for rows.Next() { ... rows.Scan(&name, &age, &email) ... } ``` ##### 日志 Gorm有内置的日志记录器支持,默认情况下,它会打印发生的错误。 ```go // 启用Logger,显示详细日志 db.LogMode(true) // 禁用日志记录器,不显示任何日志 db.LogMode(false) // 调试单个操作,显示此操作的详细日志 db.Debug().Where("name = ?", "xiaoming").First(&User{}) ```