之前咱們學習過原生的Go連接MYSQL的方法,使用Go自帶的"database/sql"
數據庫連接api,"github.com/go-sql-driver/mysql"
MYSQL驅動,通過比較原生的寫法去寫sql和處理事務。目前開源界也有很多封裝好的orm操作框架,幫我們簡省一些重復的操作,提高代碼可讀性。gorm
就是這樣的一款作品,我們來學習一下gorm的操作流程。
安裝
go get -u github.com/jinzhu/gorm
數據庫連接
要連接到數據庫首先要導入驅動程序。例如
import _ "github.com/go-sql-driver/mysql"
為了方便記住導入路徑,GORM包裝了一些驅動。
import _ "github.com/jinzhu/gorm/dialects/mysql"
// import _ "github.com/jinzhu/gorm/dialects/postgres"
// import _ "github.com/jinzhu/gorm/dialects/sqlite"
// import _ "github.com/jinzhu/gorm/dialects/mssql"
所以包名可以改為如上:
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
func main() {
db, err := gorm.Open("mysql", "user:password@tcp(IP:port)/dbname?charset=utf8&parseTime=True&loc=Local")
db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)
defer db.Close()
}
注:為了處理time.Time
,你需要包括parseTime
作為參數。
數據模型定義
表名,列名如何對應結構體
在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_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='區域表'
那么對應的結構體定義如下:
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
}
如何全局禁用表名復數呢?
可以在創建數據庫連接的時候設置如下參數:
// 全局禁用表名復數
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 (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
var db *gorm.DB
type User struct {
Id int
Name string
Age int
Sex byte
Phone string
}
func init() {
var err error
db, err = gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local")
if err != nil {
panic(err)
}
//設置全局表名禁用復數
db.SingularTable(true)
}
下面所有的操作都是在上面的初始化連接上執行的操作。
插入
//插入數據
func (user *User) Insert() {
//這里使用了Table()函數,如果你沒有指定全局表名禁用復數,或者是表名跟結構體名不一樣的時候
//你可以自己在sql中指定表名。這里是示例,本例中這個函數可以去除。
db.Table("user").Create(user)
}
更新
//注意,Model方法必須要和Update方法一起使用
//使用效果相當於Model中設置更新的主鍵key(如果沒有where指定,那么默認更新的key為id),Update中設置更新的值
//如果Model中沒有指定id值,且也沒有指定where條件,那么將更新全表
//相當於:update user set name='xiaoming' where id=1;
user := User{Id: 1,Name:"xiaoming"}
db.Model(&user).Update(user)
//注意到上面Update中使用了一個Struct,你也可以使用map對象。
//需要注意的是:使用Struct的時候,只會更新Struct中這些非空的字段。
//對於string類型字段的"",int類型字段0,bool類型字段的false都被認為是空白值,不會去更新表
//下面這個更新操作只使用了where條件沒有在Model中指定id
//update user set name='xiaohong' wehre sex=1
db.Model(&User{}).Where("sex = ?",1).Update("name","xiaohong")
如果你想手動將某個字段set為空值, 可以使用單獨選定某些字段的方式來更新:
user := User{Id: 1}
db.Model(&user).Select("name").Update(map[string]interface{}{"name":"","age":0})
忽略掉某些字段:
當你的更新的參數為結構體,而結構體中某些字段你又不想去更新,那么可以使用Omit方法過濾掉這些不想update到庫的字段:
user := User{Id: 1,Name:"xioaming",Age:12}
db.Model(&user).Omit("name").Update(&user)
刪除
//delete from user where id=1;
user := User{Id: 1}
db.Delete(&user)
//delete from user where id > 11;
db.Delete(&User{},"id > ?",11)
事務
func CreateAnimals(db *gorm.DB) err {
tx := db.Begin()
// 注意,一旦你在一個事務中,使用tx作為數據庫句柄
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}
查詢:
func (user *User) query() (u []User) {
//查詢所有記錄
db.Find(&u)
//Find方法可以帶 where 參數
db.Find(&u,"id > ? and age > ?",2,12)
//帶where 子句的查詢,注意where要在find前面
db.Where("id > ?", 2).Find(&u)
// where name in ("xiaoming","xiaohong")
db.Where("name in (?)",[]string{"xiaoming","xiaohong"}).Find(&u)
//獲取第一條記錄,按照主鍵順序排序
db.First(&u)
//First方法可以帶where 條件
db.First(&u,"where sex = ?",1)
//獲取最后一條記錄,按照主鍵順序排序
//同樣 last方法也可以帶where條件
db.Last(&u)
return u
}
注意:方法中帶的&u
表示是返回值用u這個對象來接收。
上面的查詢都將返回表中所有的字段,如果你想指定查詢某些字段該怎么做呢?
指定查詢字段-Select
//指定查詢字段
db.Select("name,age").Where(map[string]interface{}{"age":12,"sex":1}).Find(&u)
使用Struct和map作為查詢條件
//使用Struct,相當於:select * from user where age =12 and sex=1
db.Where(&User{Age:12,Sex:1}).Find(&u)
//等同上一句
db.Where(map[string]interface{}{"age":12,"sex":1}).Find(&u)
not 條件的使用
//where name not in ("xiaoming","xiaohong")
db.Not("name","xiaoming","xiaohong").Find(&u)
//同上
db.Not("name",[]string{"xiaoming","xiaohong"}).Find(&u)
or 的使用
//where age > 12 or sex = 1
db.Where("age > ?",12).Or("sex = ?",1).Find(&u)
order by 的使用
//order by age desc
db.Where("age > ?",12).Or("sex = ?",1).Order("age desc").Find(&u)
limit 的使用
//limit 10
db.Not("name",[]string{"xiaoming","xiaohong"}).Limit(10).Find(&u)
offset 的使用
//limit 300,10
db.Not("name",[]string{"xiaoming","xiaohong"}).Limit(10).Offset(300).Find(&u)
count(*)
//count(*)
var count int
db.Table("user").Where("age > ?",0).Count(&count)
注意:這里你在指定表名的情況下sql為:select count(*) from user where age > 0;
如上代碼如果改為:
var count int
var user []User
db.Where("age > ?",0).Find(&user).Count(&count)
相當於你先查出來[]User,然后統計這個list的長度。跟你預期的sql不相符。
group & having
rows, _ := db.Table("user").Select("count(*),sex").Group("sex").
Having("age > ?", 10).Rows()
for rows.Next() {
fmt.Print(rows.Columns())
}
join
db.Table("user u").Select("u.name,u.age").Joins("left join user_ext ue on u.user_id = ue.user_id").Row()
如果有多個連接,用多個Join方法即可。
原生函數
db.Exec("DROP TABLE user;")
db.Exec("UPDATE user SET name=? WHERE id IN (?)", "xiaoming", []int{11,22,33})
db.Exec("select * from user where id > ?",10).Scan(&user)
一些函數
FirstOrInit 和 FirstOrCreate
獲取第一個匹配的記錄,若沒有,則根據條件初始化一個新的記錄:
//注意:where條件只能使用Struct或者map。如果這條記錄不存在,那么會新增一條name=xiaoming的記錄
db.FirstOrInit(&u,User{Name:"xiaoming"})
//同上
db.FirstOrCreate(&u,User{Name:"xiaoming"})
Attrs
如果沒有找到記錄,則使用Attrs中的數據來初始化一條記錄:
//使用attrs來初始化參數,如果未找到數據則使用attrs中的數據來初始化一條
//注意:attrs 必須 要和FirstOrInit 或者 FirstOrCreate 連用
db.Where(User{Name:"xiaoming"}).Attrs(User{Name:"xiaoming",Age:12}).FirstOrInit(&u)
Assign
//不管是否找的到,最終返回結構中都將帶上Assign指定的參數
db.Where("age > 12").Assign(User{Name:"xiaoming"}).FirstOrInit(&u)
Pluck
如果user表中你只想查詢age這一列,該怎么返回呢,gorm提供了Pluck函數用於查詢單列,返回數組:
var ages []int
db.Find(&u).Pluck("age",&ages)
Scan
Scan函數可以將結果轉存儲到另一個結構體中。
type SubUser struct{
Name string
Age int
}
db.Table("user").Select("name,age").Scan(&SubUser)
sql.Row & sql.Rows
row和rows用戶獲取查詢結果。
//查詢一行
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有內置的日志記錄器支持,默認情況下,它會打印發生的錯誤。
// 啟用Logger,顯示詳細日志
db.LogMode(true)
// 禁用日志記錄器,不顯示任何日志
db.LogMode(false)
// 調試單個操作,顯示此操作的詳細日志
db.Debug().Where("name = ?", "xiaoming").First(&User{})