内容简介:可能看到标题会产生一个疑问:为啥不用ORM?使用ORM的好处显而易见,能够自动帮我们处理好面向对象和数据库之间的映射,Python中 我使用的是SQLAlchemy,但是Go语言目前没有一个比较好用的ORM,目前的ORM都是处于半自动形态的,而且还要处理零值和非零值时的坑, 想要省略某些字段要这样写,不想省略要那样写,很难记忆,因此还不如使用SQL这种已经是事实标准的,学一次就能用很久的东西。 在Go的项目中,我一般使用sqlx + squirrel 进行开发。首先我们来看一个简单的例子:首先我得承认,从写
可能看到标题会产生一个疑问:为啥不用ORM?使用ORM的好处显而易见,能够自动帮我们处理好面向对象和数据库之间的映射,Python中 我使用的是SQLAlchemy,但是 Go 语言目前没有一个比较好用的ORM,目前的ORM都是处于半自动形态的,而且还要处理零值和非零值时的坑, 想要省略某些字段要这样写,不想省略要那样写,很难记忆,因此还不如使用 SQL 这种已经是事实标准的,学一次就能用很久的东西。 在Go的项目中,我一般使用sqlx + squirrel 进行开发。首先我们来看一个简单的例子:
var db *sqlx.DB // InsertPost 插入帖子 func InsertPost(p Post) (int64, error) { now := time.Now() sql, args, err := squirrel.Insert("post").Columns( "created_at", "updated_at", "app", "user_id", "tag", "content", "comment_count", ).Values(now, now, p.App, p.UserID, p.Tag, p.Content, p.CommentCount).ToSql() if err != nil { return 0, err } return db.MustExec(sql, args...).LastInsertId() }
首先我得承认,从写简单语句的复杂度上来看,上述代码比ORM还是要复杂一些,却又比裸写SQL好一些(可维护性好一些,不容易出
SQL注入问题)。squirrel的写法基本上与SQL一致,通过 ToSql()
调用,最后会返回3个参数:sql, args, err,sql是一条sql
语句,args是给sql用的参数,err表明是否出错。
接下来我们继续看几个简单的例子:
import sq "github.com/Masterminds/squirrel" users := sq.Select("*").From("users").Join("emails USING (email_id)") active := users.Where(sq.Eq{"deleted_at": nil}) sql, args, err := active.ToSql() sql == "SELECT * FROM users JOIN emails USING (email_id) WHERE deleted_at IS NULL"
sql, args, err := sq. Insert("users").Columns("name", "age"). Values("moe", 13).Values("larry", sq.Expr("? + 5", 12)). ToSql() sql == "INSERT INTO users (name,age) VALUES (?,?),(?,? + 5)"<Paste>
当然squirrel也不是没有缺点,那就是文档比较缺乏,不过一般来说,看看 项目 里 的测试用例,以及首页文档,基本用法都涵盖在其中了。
源码分析
首先想想,我们现在已经知道 squirrel 的用法了,那么如果你是这个项目的开发者,你会如何实现呢?看上面的例子我们就能想得出来,
答案很简单:记录这些链式函数调用,然后在最后 ToSQL()
这个函数调用的时候,把前面链式调用拼接成SQL。
我们就拿第一个例子,来跳到 squirrel 的源码里看看,分析代码的过程写在注释里:
sql, args, err := squirrel.Insert("post").Columns( "created_at", "updated_at", "app", "user_id", "tag", "content", "comment_count", ).Values(now, now, p.App, p.UserID, p.Tag, p.Content, p.CommentCount).ToSql() // 首先我们看 Insert // Insert returns a new InsertBuilder with the given table name. // // See InsertBuilder.Into. func Insert(into string) InsertBuilder { return StatementBuilder.Insert(into) } // 继续跟进 StatementBuilder.Insert(into) // Insert returns a InsertBuilder for this StatementBuilderType. func (b StatementBuilderType) Insert(into string) InsertBuilder { return InsertBuilder(b).Into(into) } // 继续跟进 InsertBuilder(b).Into(into) // Into sets the INTO clause of the query. func (b InsertBuilder) Into(from string) InsertBuilder { return builder.Set(b, "Into", from).(InsertBuilder) } // 继续看看 InsertBuilder type InsertBuilder builder.Builder type Builder struct { builderMap ps.Map } // ps.Map 来自 https://godoc.org/github.com/lann/ps,看其描述是 Fully persistent data structures. A persistent data // structure is a data structure that always preserves the previous version of itself when it is modified. Such data // structures are effectively immutable, as their operations do not update the structure in-place, but instead always // yield a new structure. // 也就是说ps这个库里提供的数据结构,总是会保持其历史内容,而不是直接覆盖。不过不知道目前引入这个库的作用,先按下不表。 // 继续看一下 .Columns 和 .Values,发现都是差不多的逻辑。 // 接下来我们来看看最重要的 `.ToSql()` // ToSql builds the query into a SQL string and bound args. func (b InsertBuilder) ToSql() (string, []interface{}, error) { data := builder.GetStruct(b).(insertData) return data.ToSql() } // 先看 builder.GetStruct // GetStruct builds a new struct from the given registered builder. // It will return nil if the given builder's type has not been registered with // Register or RegisterValue. // // All values set on the builder with names that start with an uppercase letter // (i.e. which would be exported if they were identifiers) are assigned to the // corresponding exported fields of the struct. // // GetStruct will panic if any of these "exported" values are not assignable to // their corresponding struct fields. func GetStruct(builder interface{}) interface{} { structVal := newBuilderStruct(reflect.TypeOf(builder)) if structVal == nil { return nil } return scanStruct(builder, structVal) } // 所以是这样的,通过传入 InsertBuilder 这个类型,structVal := newBuilderStruct(reflect.TypeOf(builder)) 输出一个 // insertData 的struct实例,再通过 scanStruct(builder, structVal) 把之前我们存储的值放到struct里,这里就要使用到 // Go的反射能力了。 // 而之所以能通过 InsertBuilder 找到 insertData ,是因为 `insert.go` 里有这样几行代码: func init() { builder.Register(InsertBuilder{}, insertData{}) } // 因此在这里我们就拿到了一个 `insertData` 的实例,我们之前链式调用的值都保存在里面了,我们来看看这个struct长啥样: type insertData struct { PlaceholderFormat PlaceholderFormat RunWith BaseRunner Prefixes exprs Options []string Into string Columns []string Values [][]interface{} Suffixes exprs Select *SelectBuilder } // 然后看看 `ToSql` 函数的实现: func (d *insertData) ToSql() (sqlStr string, args []interface{}, err error) { if len(d.Into) == 0 { err = errors.New("insert statements must specify a table") return } if len(d.Values) == 0 && d.Select == nil { err = errors.New("insert statements must have at least one set of values or select clause") return } sql := &bytes.Buffer{} if len(d.Prefixes) > 0 { args, _ = d.Prefixes.AppendToSql(sql, " ", args) sql.WriteString(" ") } sql.WriteString("INSERT ") if len(d.Options) > 0 { sql.WriteString(strings.Join(d.Options, " ")) sql.WriteString(" ") } sql.WriteString("INTO ") sql.WriteString(d.Into) sql.WriteString(" ") if len(d.Columns) > 0 { sql.WriteString("(") sql.WriteString(strings.Join(d.Columns, ",")) sql.WriteString(") ") } if d.Select != nil { args, err = d.appendSelectToSQL(sql, args) } else { args, err = d.appendValuesToSQL(sql, args) } if err != nil { return } if len(d.Suffixes) > 0 { sql.WriteString(" ") args, _ = d.Suffixes.AppendToSql(sql, " ", args) } sqlStr, err = d.PlaceholderFormat.ReplacePlaceholders(sql.String()) return } // 很明显,就是各种根据所输入的条件,进行SQL拼接了
至此,我们简单的看了一下一个insert在 squirrel 里是怎么实现的,其它的例如 select, update 等,逻辑都是类似的。通过阅读 这个库的代码,我们了解了要怎样去实现一个类似的SQL生成库。
参考资料:
以上所述就是小编给大家介绍的《Golang SQL生成库 Squirrel 教程及源码阅读》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Vue源码简析之视图生成(上)
- babel源码分析之一:AST生成
- Django源码解析|Migrations文件的生成
- symfony源码分析之容器的生成与使用
- mybatis源码学习:从SqlSessionFactory到代理对象的生成
- 木兰语言 0.0.17:着手由 Python 语法树生成木兰源码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。