Golang很流行,但是有些方面資料很少而且不詳實,譬如:gorm的聯合查詢,當然,也不推薦復雜語句使用orm模型。
現將自己總結的寫法和遇到的坑記錄如下: Golang要求使用“駝峰命名法”,比如systemId,因為我以前用的是Python,使用Django的orm序列化后返回的參數和數據庫表字段一致,基於這個不適合Go的思路,我將表字段也建成了systemId,和struct映射參數相同。(其實表字段應該命名為system_id)
一、下面建兩張表,用於聯合查詢(以left join示例)
MySQL > desc go_system_info; +——————+——————-+———+——-+——————-+————————+ | Field | Type | Null | Key | Default | Extra | +——————+——————-+———+——-+——————-+————————+ | id | int(11) | NO | PRI | NULL | auto_increment | | systemId | varchar(30) | NO | MUL | NULL | | | systemName | varchar(50) | NO | | defaultNull | | +——————+——————-+———+——-+——————-+————————+ 3 rows in set (0.01 sec) MySQL > desc go_service_info; +——————-+——————-+———+——-+——————-+————————+ | Field | Type | Null | Key | Default | Extra | +——————-+——————-+———+——-+——————-+————————+ | id | int(11) | NO | PRI | NULL | auto_increment | | systemId | varchar(30) | NO | MUL | NULL | | | serviceId | varchar(50) | NO | MUL | defaultNull | | | serviceName | varchar(50) | NO | | defaultNull | | +——————-+——————-+———+——-+——————-+————————+ 4 rows in set (0.00 sec) MySQL >
二、表建好后,我們來定義表結構體:
type GoSystemInfo struct {
ID int `gorm:"primary_key"` SystemId string `gorm:"column:systemId;type:varchar(30);not null;index:SystemId"` SystemName string `gorm:"column:systemName;type:varchar(50);not null;default:'defaultNull'"` } type GoServiceInfo struct { ID int `gorm:"primary_key"` SystemId string `gorm:"column:systemId;type:varchar(30);not null;index:SystemId"` ServiceId string `gorm:"column:serviceId;type:varchar(50);not null;default:'defaultNull';index:ServiceId"` ServiceName string `gorm:"column:serviceName;type:varchar(50);not null;default:'defaultNull'"` }
小知識:ORM(Object Relation Mapping),對象關系映射,實際上就是對數據庫的操作進行封裝,對上層開發人員屏蔽數據操作的細節,開發人員看到的就是一個個對象,大大簡化了開發工作,提高了生產效率,也可以避免sql注入等問題。
由於gorm是使用的orm映射,所以需要定義要操作的表的model,在go中需要定義一個struct, struct的名字就是對應數據庫中的表名,注意gorm查找struct名對應數據庫中的表名的時候會默認把你的struct中的大寫字母轉換為小寫並加上“s”,所以可以加上 db.SingularTable(true) 讓gorm轉義struct名字的時候不用加上“s”。
golang中,首字母大小寫來表示public或者private,因此結構體中字段首字母必須大寫。
定義model,即struct時,我們可以只定義我們需要從數據庫中取回的特定字段: gorm在轉義表名的時候會把struct的大寫字母(首字母除外) 替換成“_”,所以下面的”GoSystemInfo”會轉義成數據庫中對應的“go_system_info”的表名, 對應的字段名的查找會先按照tag里面的名稱去里面查找,如果沒有定義標簽則按照struct定義的字段查找,查找的時候struct字段中的大寫會被轉義成“_”,如:“SystemId”會去查找表中的system_id字段。
在本例,我們在struct使用如gorm:”column:systemId”,column映射mysql表字段名稱。
三、聯合查詢
單表查詢用上面的原表結構體接收數據就可以了, 聯合查詢涉及兩張表中的全部/部分數據,我們定義新的結構體接收取回的特定字段:
type result struct {
SystemId string `json:"systemId"` SystemName string `json:"systemName"` ServiceId string `json:"serviceId"` ServiceName string `json:"serviceName"` }
我們從go_service_info取serviceId、serviceName,從go_system_info取對應的systemId、systemName:
db.Table("go_service_info").Select("go_service_info.serviceId as service_id, go_service_info.serviceName as service_name, go_system_info.systemId as system_id, go_system_info.systemName as system_name").Joins("left join go_system_info on go_service_info.systemId = go_system_info.systemId").Scan(&results)
where條件:
db.Table("go_service_info").Select("go_service_info.serviceId as service_id, go_service_info.serviceName as service_name, go_system_info.systemId as system_id, go_system_info.systemName as system_name").Joins("left join go_system_info on go_service_info.systemId = go_system_info.systemId where go_service_info.serviceId <> ? and go_system_info.systemId = ?", "xxx", "xxx").Scan(&results)
注意:這里需要使用別名as system_id,映射返回值結構體,並且因為查找的時候struct字段中的大寫會被轉義成“_”,所以別名也要將大寫轉為“_”。
若使用原生語句:
db.Raw("SELECT a.serviceId as service_id,a.serviceName as service_name, b.systemId as system_id, b.systemName as system_name FROM go_service_info a LEFT JOIN go_system_info b ON a.systemId = b.systemId").Scan(&results)
where條件:
db.Raw("SELECT a.serviceId as service_id,a.serviceName as service_name, b.systemId as system_id, b.systemName as system_name FROM go_service_info a LEFT JOIN go_system_info b ON a.systemId = b.systemId where a.serviceId <> ? and b.systemId = ?", "xxx", "xxx").Scan(&results)
結果相同。
避坑建議: 表字段命名為如system_id,默認映射到結構體字段SystemId。當然建表原則上也是用小寫和下划線,不過歷史表難免會有大寫命名的情況,所以新表還是遵照相關規范吧。
源碼:
package main
import ( "fmt" "log" "encoding/json" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type GoSystemInfo struct { ID int `gorm:"primary_key"` SystemId string `gorm:"column:systemId;type:varchar(30);not null;index:SystemId"` SystemName string `gorm:"column:systemName;type:varchar(50);not null;default:'defaultNull'"` } type GoServiceInfo struct { ID int `gorm:"primary_key"` SystemId string `gorm:"column:systemId;type:varchar(30);not null;index:SystemId"` ServiceId string `gorm:"column:serviceId;type:varchar(50);not null;default:'defaultNull';index:ServiceId"` ServiceName string `gorm:"column:serviceName;type:varchar(50);not null;default:'defaultNull'"` } type result struct { SystemId string `json:"systemId"` SystemName string `json:"systemName"` ServiceId string `json:"serviceId"` ServiceName string `json:"serviceName"` } //定義數據庫連接 type ConnInfo struct { MyUser string Password string Host string Port int Db string } func dbConn(MyUser, Password, Host, Db string, Port int) *gorm.DB { connArgs := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", MyUser,Password, Host, Port, Db ) db, err := gorm.Open("mysql", connArgs) if err != nil { log.Fatal(err) } db.SingularTable(true) return db } func mapToJson(result interface{}) string { // map轉 json str jsonBytes, _ := json.Marshal(result) jsonStr := string(jsonBytes) return jsonStr } func main() { var results []result cn := ConnInfo{ "xxx", "xxx", "127.0.0.1", 3306, "xxx", } db := dbConn(cn.MyUser,cn.Password,cn.Host,cn.Db,cn.Port) defer db.Close() /* // 創建表 db.AutoMigrate(&GoSystemInfo{}) product := GoSystemInfo{SystemId:"sysid", SystemName:"sysname"} fmt.Println(db.NewRecord(product)) db.AutoMigrate(&GoServiceInfo{}) products := GoServiceInfo{SystemId:"sysid", ServiceId:"serid", ServiceName:"sername"} fmt.Println(db.NewRecord(products)) */ // 聯合查詢(left join) db.Table("go_service_info").Select("go_service_info.serviceId as service_id, go_service_info.serviceName as service_name, go_system_info.systemId as system_id, go_system_info.systemName as system_name").Joins("left join go_system_info on go_service_info.systemId = go_system_info.systemId").Scan(&results) fmt.Println(mapToJson(results)) // where db.Table("go_service_info").Select("go_service_info.serviceId as service_id, go_service_info.serviceName as service_name, go_system_info.systemId as system_id, go_system_info.systemName as system_name").Joins("left join go_system_info on go_service_info.systemId = go_system_info.systemId where go_service_info.serviceId <> ? and go_system_info.systemId = ?", "xxx", "xxx").Scan(&results) fmt.Println(mapToJson(results)) // 原生sql db.Raw("SELECT a.serviceId as service_id,a.serviceName as service_name, b.systemId as system_id, b.systemName as system_name FROM go_service_info a LEFT JOIN go_system_info b ON a.systemId = b.systemId").Scan(&results) fmt.Println(mapToJson