前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教你学之golang反射(下)

手把手教你学之golang反射(下)

作者头像
李海彬
发布2020-11-23 11:04:07
2720
发布2020-11-23 11:04:07
举报

原文作者:柔顺的灵魂 来源:简书

Update方法

分析update sql语句:

update user set first_name = "z", last_name = "zy" where first_name = "Tom" and last_name = "Curise"

比较简单,直接复用之前写的sKV()和mKV()函数:

//Update src can be *user, user, map[string]interface{}, string
func (q *Query) Update(src interface{}) (int64, error) {
    if len(q.errs) != 0 {
        return 0, errors.New(strings.Join(q.errs, "
"))
    }
    v := reflect.ValueOf(src)
    for v.Kind() == reflect.Ptr {
        v = v.Elem()
    }
    var toBeUpdated, where string
    var keys, values []string
    switch v.Kind() {
    case reflect.String:
        toBeUpdated = src.(string)
    case reflect.Struct:
        keys, values = sKV(v)
    case reflect.Map:
        keys, values = mKV(v)
    default:
        return 0, errors.New("method Update error: type error")
    }
    if toBeUpdated == "" {
        if len(keys) != len(values) {
            return 0, errors.New("method Update error: keys not match values")
        }
        var kvs []string
        for idx, key := range keys {
            kvs = append(kvs, fmt.Sprintf("%s = %s", key, values[idx]))
        }
        toBeUpdated = strings.Join(kvs, ",")
    }
    if len(q.wheres) > 0 {
        where = fmt.Sprintf(`where %s`, strings.Join(q.wheres, " and "))
    }
    query := fmt.Sprintf("update %s set %s %s", q.table, toBeUpdated, where)
    st, err := q.DB.Prepare(query)
    if err != nil {
        return 0, err
    }
    result, err := st.Exec()
    if err != nil {
        return 0, err
    }
    return result.RowsAffected()
}

调用方式:

u1 := "age = 100"
u2 := map[string]interface{}{
    "age":        100,
    "first_name": "z",
    "last_name":  "zy",
}
u3 := &User{
    Age:       100,
    FirstName: "z",
    LastName:  "zy",
}
_, _ = users().Where("age > 10").Update(u1)
_, _ = users().Where("age > 10").Update(u2)
_, _ = users().Where("age > 10").Update(u3)

Delete方法

这个最简单,没啥好说的:

//Delete no args
func (q *Query) Delete() (int64, error) {
    if len(q.errs) != 0 {
        return 0, errors.New(strings.Join(q.errs, "
"))
    }
    var where string
    if len(q.wheres) > 0 {
        where = fmt.Sprintf(`where %s`, strings.Join(q.wheres, " and "))
    }
    st, err := q.DB.Prepare(fmt.Sprintf(`delete from %s %s`, q.table, where))
    if err != nil {
        return 0, err
    }
    result, err := st.Exec()
    if err != nil {
        return 0, err
    }
    return result.RowsAffected()
}

删除id为1,2,3,4,并且age大于10的用户的调用方式:

w := map[string]interface{}{
    "id": []int{1, 2, 3, 4},
}
_, _ = users().Where(w, "age > 10").Delete()

最后,写一个简单的事务处理函数Transaction()

Transaction函数

事务有三个关键动作beginrollbackcommit。 begin后,要求所有操作要不全部成功,要不全部失败,所以我们要检查所有error,一旦出现错误就rollback,并且还要recover程序的panic,发现panic时也要rollback,直到最后确保无错,才能commit。 调用*sql.DB.Begin()方法后,我们会得到一个事务具柄,事务内的mysql交互都要通过它来进行,它也实现了Query()Prepare()等方法。 所以我们定义一个接口:

//Dba *sql.DB or *sql.Tx
type Dba interface {
    Query(string, ...interface{}) (*sql.Rows, error)
    Prepare(string) (*sql.Stmt, error)
}

然后把Query结构体的DB属性的类型改成这个接口:

//Query will build a sql
type Query struct {
    DB     Dba
    ...
}

同时, 改造Table()函数:

//Table bind db and table
func Table(db *sql.DB, tableName string) func(...Dba) *Query {
    return func(tx ...Dba) *Query {
        if len(tx) == 1 {
            return &Query{
                DB:    tx[0],
                table: tableName,
            }
        }
        return &Query{
            DB:    db,
            table: tableName,
        }
    }
}

这样我们就可以有选择性的和mysql进行普通交互或者事务交互。 然后把Transaction()函数写成这样:

//Transaction .
func Transaction(db *sql.DB, f func(Dba) error) (err error) {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        p := recover()
        if err != nil {
            if rerr := tx.Rollback(); rerr != nil {
                panic(rerr)
            }
            return
        }
        if p != nil {
            if rerr := tx.Rollback(); rerr != nil {
                panic(rerr)
            }
            err = fmt.Errorf("function Transaction error: %v", p)
            return
        }
        if cerr := tx.Commit(); cerr != nil {
            panic(cerr)
        }
    }()
    err = f(tx)
    return err
}

第二个参数是一个接受事务具柄,返回error的函数,我们将需要事务的操作全部封装在这个函数里,就能抓到所有的panic和error。 调用方式示例:

unc doTx() error {
    ormDB, err := Connect("root@tcp(127.0.0.1:3306)/orm_db?parseTime=true&loc=Local")
    if err != nil {
        panic(err)
    }
    users := Table(ormDB, "user")
    args := something()
    //利用闭包传递变量
    f := func(tx Dba) error {
        var id int
        //select语句无需在事务具柄上进行
        if err := users().Where(args).Select(&id); err != nil {
            return err
        }
        //增删改需要在事务上进行
        if _, err = users(tx).Insert(args); err != nil {
            return err
        }
        if _, err = users(tx).Update(args); err != nil {
            return err
        }
        if _, err = users(tx).Where(args).Delete(); err != nil {
            return err
        }
        return nil
    }
    //开始事务
    if err := Transaction(ormDB, f); err != nil {
        return err
    }
    return nil
}

到此,这个迷你orm的增删改查和事务功能全部都实现了,代码大概600行,比我预想的多了一倍。

后记

golang的反射虽然强大(其实并不,没有ruby的元编程那么方便),但还是比较烦琐的,而且类型不对时动不动就panic,使用的时候要尽量检查一下Kind。

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Update方法
  • Delete方法
  • Transaction函数
  • 后记
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档