Gorm是golang的一個orm框架,它提供了對數據庫操做的封裝,使用起來至關便利。mysql
但在項目開發中,代碼寫的多了,仍是發如今它之上仍是有再次封裝的空間,好比說添加錯誤日誌、或者是一些使用頻率很是高的對單個表的條件查詢、分頁查詢、數據更新等。再則是,關於相同的功能操做,gorm也提供多種實現方式,對新學多少有些困惑,不知道該用哪一個好。git
因而,我基於本身在項目中的使用經驗和編碼習慣,作了以下一些擴展,供你們參考。github
爲了兼容gorm的使用方法,我使用了內嵌類型來擴展。 定義以下:golang
type DBExtension struct {
*gorm.DB
logger DBLogger
}
複製代碼
這樣子定義的wrapper對象是最小侵入式的擴展,不只能夠直接點出gorm的原有方法,也能夠點出擴展的方法。sql
關於新建數據,我建議使用Save方法,當匹配主鍵的數據不存在時,它的效果是插入一條新數據,而當匹配主鍵的數據存在時,則更新所有字段,再說一遍,它會更新所有字段!數據庫
不管字段是否作了修改或者是不是定義類型的默認值。bash
請再次注意:默認值是否生效在gorm的不一樣方法中處理的方式是不同的,須要很是當心才行。app
舉個例子,若是你定義了一個User的結構體,裏面有個Age的字段類型是int。(注:之後的例子,都默認已定義這個結構體)框架
type User struct {
Id int `gorm:"column:id; type:int(11);primary_key"`
Name string `gorm:"column:name; type:varchar(32);"`
Age int `gorm:"column:age; type:int(11);"`
Description string `gorm:"column:description; type:varchar(512);"`
}
func (User) TableName() string {
return "test.user"
}
複製代碼
++請特別注意Id的定義中的primary_key, 若是沒有加個這個Save方法是沒法正常工做的。++ui
若是在定義時,沒有給Age賦值,那麼這條數據的Age將被置爲0。
對於新增數據,可能問題不大,可是對於數據更新,那這就可就是一個隱晦的bug了!
那既然Save方法有這樣一個坑,爲何還要用它呢?
簡單來講,不用顯示的判斷是新增數據和更新數據,可讓代碼更加簡潔,利大於弊,不是嗎?
擴展代碼以下,增長了一些錯誤判斷和日誌:
type TableNameAble interface {
TableName() string
}
// Update All Fields
func (dw *DBExtension) SaveOne(value TableNameAble) error {
tableNameAble, ok := value.(TableNameAble)
if !ok {
return errors.New("value doesn't implement TableNameAble")
}
var err error
if err = dw.Save(value).Error; err != nil {
dw.logger.LogErrorc("mysql", err, fmt.Sprintf("Failed to save %s, the value is %+v", tableNameAble.TableName(), value))
}
return err
}
複製代碼
使用代碼以下:
user1 := User{Id:1, Name:"Jeremy", Age: 30, Description: "A gopher"}
if err := dw.SaveOne(&instInfo); err != nil{
// error handling
return err
}
複製代碼
當記錄不存在時,執行的Sql語句是:
insert into test.user(id ,name, age, description) values(1, "Jeremy", 30, "A gopher")
複製代碼
當記錄存在時,執行的語句就是:
update test.user set name = "Jeremy", age = 30, description = "A gohper" where id = 1
複製代碼
這樣寫新建,還兼顧了全字段更新的狀況,是否是一箭雙鵰呢?
PS: 若是主鍵Id是一個自增列,在新建時,能夠不用給Id賦值。當數據成功插入後,這條數據的Id還會自動更新到Id字段,這個特性在一些場景下特別有用。
SaveOne方法是全量更新,但大部分狀況是,可能只是更新某條數據的部分字段,又或者是隻想更新改過的字段。關於這部分操做,gorm雖然提供了不少操做方法,但也是最讓人困惑的。
在這種場景我經常使用的處理方式有兩種,一是定義一個專門的結構體,如:
type UserDesc struct {
Id int `gorm:"column:id; type:int(11);primary_key"`
Description string `gorm:"column:description; type:varchar(512);"`
}
func (UserDesc) TableName() string {
return "test.user"
}
複製代碼
這時就可使用SaveOne方法,用以下方式更新:
userDesc := UserDesc{Id:1, Description: "A programmer"}
if err := dw.SaveOne(&userDesc); err != nil{
// error handling
return err
}
複製代碼
執行的sql語句是:
update test.user set description = "A programmer" where id = 1
複製代碼
可是更多的時候,是想按匹配條件更新的匹配的數據,這時SaveOne就沒法知足了。因而,我作了以下擴展:
const table_name = "$Table_Name$"
type UpdateAttrs map[string]interface{}
func NewUpdateAttrs(tableName string) UpdateAttrs {
attrMap := make(map[string]interface{})
attrMap[table_name] = tableName
return attrMap
}
// Update selected Fields, if attrs is an object, it will ignore default value field; if attrs is map, it will ignore unchanged field.
func (dw *DBExtension) Update(attrs interface{}, query interface{}, args ...interface{}) error {
var (
tableNameAble TableNameAble
ok bool
tableName string
)
if tableNameAble, ok = query.(TableNameAble); ok {
tableName = tableNameAble.TableName()
}else if tableNameAble, ok = attrs.(TableNameAble); ok {
tableName = tableNameAble.TableName()
} else if attrMap, isUpdateAttrs := attrs.(UpdateAttrs); isUpdateAttrs {
tableName = attrMap[table_name].(string)
delete(attrMap, table_name)
}
if tableName == "" {
return errors.New("can't get table name from both attrs and query")
}
var err error
db := dw.Table(tableName).Where(query, args...).Update(attrs)
if err = db.Error; err != nil {
dw.logger.LogErrorc("mysql", err, fmt.Sprintf("failed to update %s, query is %+v, args are %+v, attrs is %+v", tableName, query, args, attrs))
}
if db.RowsAffected == 0 {
dw.logger.LogWarnc("mysql",nil, fmt.Sprintf("No rows is updated.For %s, query is %+v, args are %+v, attrs is %+v", tableName, query, args, attrs))
}
return err
}
複製代碼
下面,我將結合Sql語句,逐一解釋如何使用。
仍是先以要執行下面這條語句爲例:
update test.user set description = "A programmer" where id = 1
複製代碼
如今,能夠有以下幾種實現方式
udateAttrs := User{Description: "A programmer"}
condition := User{Id: 1}
if err := dw.Update(&udateAttrs, condition); err != nil{
// error handling
return err
}
複製代碼
udateAttrs := User{Description: "A programmer"}
if err := dw.Update(&udateAttrs, "id = ?", 1); err != nil{
// error handling
return err
}
複製代碼
udateAttrs := NewUpdateAttrs("test.user")
udateAttrs["description"] = "A programmer"
if err := dw.Update(&udateAttrs, "id = ?", 1); err != nil{
// error handling
return err
}
複製代碼
udateAttrs := NewUpdateAttrs("test.user")
udateAttrs["description"] = "A programmer"
condition := User{Id: 1}
if err := dw.Update(&udateAttrs, condition); err != nil{
// error handling
return err
}
複製代碼
咋一看,四種寫法很類似。那麼,爲何要搞這麼多種寫法呢?
這但是不是爲了炫耀回字的幾種寫法, 而是由於gorm原生的Update方法對於struct的參數是會忽略默認值的。
好比說,若是你想把descritpion清空,若是像下面這樣寫:
udateAttrs := User{Description: ""}
condition := User{Id: 1}
if err := dw.Update(&udateAttrs, condition); err != nil{
// error handling
return err
}
複製代碼
descritpion是不會被更新的,這是就須要寫法三或者寫法四了,以寫法四爲例
udateAttrs := NewUpdateAttrs("test.user")
udateAttrs["description"] = ""
condition := User{Id: 1}
if err := dw.Update(&udateAttrs, condition); err != nil{
// error handling
return err
}
複製代碼
纔會如願執行:
update test.user set description = "" where id = 1
複製代碼
而寫法二(三)的強大之處在於能夠更自由的指定匹配條件,好比:
udateAttrs := User{Description: "A programmer"}
if err := dw.Update(&udateAttrs, "id in (?) and age > ? and description != ?", []int{1,2}, 30, ""); err != nil{
// error handling
return err
}
複製代碼
執行的sql是:
update test.user set description = "A programmer" where id in (1,2) and age > 30 and description != ''
複製代碼
未完待續……