Gorm的使用心得和一些經常使用擴展(一)

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 != ''
複製代碼

未完待續……

代碼地址:Github:Ksloveyuan/gorm-ex

相關文章
相關標籤/搜索