原文作者:柔顺的灵魂 来源:简书
分析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 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()
。
事务有三个关键动作begin
,rollback
,commit
。
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。
版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。