關於ORM的介紹可以看這邊 ORM 物件關聯對映,這邊就有說明了~~~
用raw sql語法操作db跟用orm其實沒有什麼誰好誰不好,如果sql語法寫起來跟吃飯喝水一樣easy的話,
不一定要用orm。
如果sql語法沒寫的那麼精準,也不想常常被DBA手刀打下去的話,是可以考慮使用orm來滿足工作需求。
Golang首推的ORM套件還是以 GORM為首選,
特點如下
以下與GORM V2版的安裝與實作流程
$go get -u gorm.io/gorm
//v1不需要下面的mysql driver
$go get -u gorm.io/driver/mysql
因為目前GORM為v2版,跟v1在初始化時有很大的不同
V2版,mysql可參考 go-gorm/mysql的設定,跟v1版的差距也太大了~~
package main
import (
"fmt"
"time"
//V2需要引用這package
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
const (
UserName string = "root"
Password string = "password"
Addr string = "127.0.0.1"
Port int = 3306
Database string = "test"
MaxLifetime int = 10
MaxOpenConns int = 10
MaxIdleConns int = 10
)
func main() {
//組合sql連線字串
addr := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True", UserName, Password, Addr, Port, Database)
//連接MySQL
conn, err := gorm.Open(mysql.Open(addr), &gorm.Config{})
if err != nil {
fmt.Println("connection to mysql failed:", err)
return
}
//設定ConnMaxLifetime/MaxIdleConns/MaxOpenConns
db, err1 := conn.DB()
if err1 != nil {
fmt.Println("get db failed:", err)
return
}
db.SetConnMaxLifetime(time.Duration(MaxLifetime) * time.Second)
db.SetMaxIdleConns(MaxIdleConns)
db.SetMaxOpenConns(MaxOpenConns)
}
對gorm來說,model struct定義好,gorm才能進行mapping,mapping表請參考 models
type User struct {
//gorm為model的tag標籤,v2版的auto_increment要放在type裡面,v1版是放獨立定義
ID int64 `gorm:"type:bigint(20) NOT NULL auto_increment;primary_key;" json:"id,omitempty"`
Username string `gorm:"type:varchar(20) NOT NULL;" json:"username,omitempty"`
Password string `gorm:"type:varchar(100) NOT NULL;" json:"password,omitempty"`
Status int32 `gorm:"type:int(5);" json:"status,omitempty"`
CreatedAt time.Time `gorm:"type:timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP" json:"created_at,omitempty"`
UpdatedAt time.Time `gorm:"type:timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP" json:"updated_at,omitempty"`
}
V2與V1有個很明顯的不同點
原本的gorm.DB底下的HasTable(),AddIndex()等行為都移至gorm.DB).Migrator()底下
使用AutoMigrate來維持table一直都是最新的狀態,基本上如果model的struct有新增欄位才有影響,已存在或是刪除掉欄位不會自動drop column。
Migrator()支持CreateTable/DropTable/RenameTable/AddColumn/AlterColumn...等等會對table結構的操作,但是強烈建議不要這樣子做XD
package main
import (
"fmt"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
const (
UserName string = "root"
Password string = "password"
Addr string = "127.0.0.1"
Port int = 3306
Database string = "test"
MaxLifetime int = 10
MaxOpenConns int = 10
MaxIdleConns int = 10
)
type User struct {
ID int64 `gorm:"type:bigint(20) NOT NULL auto_increment;primary_key;" json:"id,omitempty"`
Username string `gorm:"type:varchar(20) NOT NULL;" json:"username,omitempty"`
Password string `gorm:"type:varchar(100) NOT NULL;" json:"password,omitempty"`
Status int32 `gorm:"type:int(5);" json:"status,omitempty"`
CreatedAt time.Time `gorm:"type:timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP" json:"created_at,omitempty"`
UpdatedAt time.Time `gorm:"type:timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP" json:"updated_at,omitempty"`
}
func main() {
//組合sql連線字串
addr := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True", UserName, Password, Addr, Port, Database)
//連接MySQL
conn, err := gorm.Open(mysql.Open(addr), &gorm.Config{})
if err != nil {
fmt.Println("connection to mysql failed:", err)
return
}
db, err1 := conn.DB()
if err1 != nil {
fmt.Println("get db failed:", err)
return
}
db.SetConnMaxLifetime(time.Duration(MaxLifetime) * time.Second)
db.SetMaxIdleConns(MaxIdleConns)
db.SetMaxOpenConns(MaxOpenConns)
//產生table
conn.Debug().AutoMigrate(&User{})
//判斷有沒有table存在
migrator := conn.Migrator()
has := migrator.HasTable(&User{})
//has := migrator.HasTable("GG")
if !has {
fmt.Println("table not exist")
}
}
create可透過model或是語法進行新增資料
//新增model資料
user := User{UserName: "tester", Password: "12333", Status: 1}
//
result := conn.Debug().Create(&user)
if result.Error != nil {
fmt.Println("Create failt")
}
if result.RowsAffected != 1 {
fmt.Println("RowsAffected Number failt")
}
//執行結果:[rows:1] INSERT INTO `users` (`user_name`,`password`,`status`,`created_at`,`updated_at`) VALUES ('tester','12333',1,'2020-09-30 13:52:49.913','2020-09-30 13:52:49.913')
//只insert特定欄位值
conn.Debug().Select("UserName", "Password").Create(&user)
//執行結果:INSERT INTO `users` (`user_name`,`password`) VALUES ('tester','12333')
//不insert特定欄位值,但是這個語法好像遇到auto_increment的pk會Duplicate pk
conn.Debug().Omit("status").Create(&user)
//執行結果:INSERT INTO `users` (`user_name`,`password`,`created_at`,`updated_at`,`id`) VALUES ('tester','12333','2020-09-30 14:00:46.006','2020-09-30 14:00:46.006',10),ID自己產生於語法中
//BATCH INSERT,v2版新增的特點
users := []User{{UserName: "tester", Password: "12333", Status: 1}, {UserName: "gger", Password: "132333", Status: 1}, {UserName: "ininder", Password: "12333", Status: 1}}
result := conn.Debug().Create(&users)
if result.Error != nil {
fmt.Println("Create failt")
}
fmt.Println("result.RowsAffected:", result.RowsAffected)
//執行結果:INSERT INTO `users` (`user_name`,`password`,`status`,`created_at`,`updated_at`) VALUES ('tester','12333',1,'2020-09-30 14:07:00.133','2020-09-30 14:07:00.133'),('gger','132333',1,'2020-09-30 14:07:00.133','2020-09-30 14:07:00.133'),('ininder','12333',1,'2020-09-30 14:07:00.133','2020-09-30 14:07:00.133')
//除了用slice struct外,也可以用map進行新增資料,map跟struct有蠻大的差異,struct會有default值,map是沒指定的就不會出現在insert語法上
conn.Debug().Model(&User{}).Create(map[string]interface{}{
"UserName": "gg", "Password": "18",
})
//執行結果:INSERT INTO `users` (`password`,`user_name`) VALUES ('18','gg')
//也可以執行raw語法
conn.Exec("INSERT INTO `users` (`password`,`user_name`) VALUES (?,?)", "G123", "999")
var user User
var users []User
res := conn.Debug().Find(&users)
fmt.Println(res.RowsAffected)
//SELECT * FROM `users`
conn.Debug().First(&user)
//SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
conn.Debug().Take(&user)
//SELECT * FROM `users` WHERE `users`.`id` = 1 LIMIT 1
conn.Debug().Last(&user)
//SELECT * FROM `users` WHERE `users`.`id` = 1 ORDER BY `users`.`id` DESC LIMIT 1
where裡面的寫法類似raw sql語法,使用?做佔位符號
conn.Debug().Where("user_name = ?", "tester4").Find(&user)
//SELECT * FROM `users` WHERE user_name = 'tester4'
conn.Debug().Where("user_name IN ?", []string{"tester4", "tester3"}).Find(&users)
//SELECT * FROM `users` WHERE user_name IN ('tester4','tester3'),IN語法可以用slice
conn.Debug().Where("user_name LIKE ?", "%tester%").Find(&users)
//SELECT * FROM `users` WHERE user_name LIKE '%tester%'
有個很好用的語法Pluck,可以把select出來的值變成slice的
conn.Debug().Table("users").Pluck("user_name", &us)
//us: [tester1 tester2 tester3 tester4 tester tester tester tester tester tester tester gger ininder jinzhu gg gg 999]
還有更多的語法可以參考官方文件 GORM QUERY
//使用Save
conn.First(&user)
user.Password = "GG"
conn.Debug().Save(&user)
//UPDATE `users` SET `user_name`='tester1',`password`='GG',`status`=1,`created_at`='2020-09-30 05:46:22',`updated_at`='2020-09-30 15:46:07.403' WHERE `id` = 1
//使用Update
conn.Debug().Model(&User{}).Where("id = ?", 1).Update("password", "helloGG")
//UPDATE `users` SET `password`='helloGG',`updated_at`='2020-09-30 15:48:45.676' WHERE id = 1
//使用Updates
conn.Debug().Model(&User{}).Where("id = ?", 1).Updates(User{UserName: "hello", Password: "GG"})
//UPDATE `users` SET `user_name`='hello',`password`='GG',`updated_at`='2020-09-30 15:51:27.78' WHERE id = 1
conn.Debug().Model(&User{}).Where("id = ?", 1).Updates(map[string]interface{}{"UserName": "hello", "Password": "GG"})
//UPDATE `users` SET `password`='GG',`user_name`='hello',`updated_at`='2020-09-30 15:51:27.784' WHERE id = 1
//DELETE
conn.Debug().Where("id = ?", "1").Delete(&User{})
//DELETE FROM `users` WHERE id = '1'
GORM V2版真的跟V1版差蠻多的,有興趣真的看官方文件,玩法多超多 XD