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

相关文章
相关标签/搜索