type SelectBuilder struct {
builder *strings.Builder
column []string
tableName string
where[]func(s *SelectBuilder)
args []interface{}
orderby string
offset *int64
limit*int64
}
func (s *SelectBuilder)Select(field ...string)*SelectBuilder {
s.column= append(s.column, field...)
return s
}
func (s *SelectBuilder)From(name string)*SelectBuilder {
s.tabelName= name
return s
}
func (s *SelectBuilder)Where(f ...func(s *SelectBuilder))*SelectBuilder {
s.where= append(s.where, f...)
return s
}
func (s *SelectBuilder) OrderBy(field string)*SelectBuilder {
s.orderby= field
return s
}
func (s *SelectBuilder)Limit(offset,limit int64)*SelectBuilder {
s.offset=&offset
s.limit=&limit
return s
}
func GT(field string, arg interface{}) func(s *SelectBuilder){
return func(s *SelectBuilder){
s.builder.WriteString("`"+ field +"`"+" > ?")
s.args= append(s.args, arg)}}
func (s *SelectBuilder) Query()(string,[]interface{}){
s.builder.WriteString("SELECT ")
for k, v := range s.column{
if k >0{
s.builder.WriteString(",")}
s.builder.WriteString("`"+ v +"`")}
s.builder.WriteString(" FROM ")
s.builder.WriteString("`"+ s.tableName+"` ")
if len(s.where)>0{
s.builder.WriteString("WHERE ")
for k, f := range s.where{
if k >0{
s.builder.WriteString(" AND ")}
f(s)}}
if s.orderby!=""{
s.builder.WriteString(" ORDER BY "+ s.orderby)}
if s.limit!= nil {
s.builder.WriteString(" LIMIT ")
s.builder.WriteString(strconv.FormatInt(*s.limit,10))}
if s.offset!= nil {
s.builder.WriteString(" OFFSET ")
s.builder.WriteString(strconv.FormatInt(*s.offset,10))}
return s.builder.String(), s.args}
func ScanSlice(rows *sql.Rows, dst interface{}) error {
defer rows.Close()// dst的地址
val := reflect.ValueOf(dst)//&[]*main.User// 判断是否是指针类型,go是值传递,只有传指针才能让更改生效
if val.Kind()!= reflect.Ptr{
return errors.New("dst not a pointer")}// 指针指向的Value
val = reflect.Indirect(val)//[]*main.User
if val.Kind()!= reflect.Slice{
return errors.New("dst not a pointer to slice")}// 获取slice中的类型
struPointer := val.Type().Elem()//*main.User// 指针指向的类型 具体结构体
stru := struPointer.Elem()// main.User
cols, err := rows.Columns()//[id,name,age,ctime,mtime]
if err != nil {
return err
}// 判断查询的字段数是否大于 结构体的字段数
if stru.NumField()< len(cols){//5,5
return errors.New("NumField and cols not match")}//结构体的json tag的value对应字段在结构体中的index
tagIdx := make(map[string]int)//map tag -> field idx
for i :=0; i < stru.NumField(); i++{
tagname := stru.Field(i).Tag.Get("json")
if tagname !=""{
tagIdx[tagname]= i
}}
resultType := make([]reflect.Type,0, len(cols))//[int64,string,int64,time.Time,time.Time]
index := make([]int,0, len(cols))//[0,1,2,3,4,5]// 查找和列名相对应的结构体jsontag name的字段类型,保存类型和序号到resultType和index中
for _, v := range cols {
if i, ok := tagIdx[v]; ok {
resultType = append(resultType, stru.Field(i).Type)
index = append(index, i)}}
for rows.Next(){// 创建结构体指针,获取指针指向的对象
obj := reflect.New(stru).Elem()// main.User
result := make([]interface{},0, len(resultType))//[]// 创建结构体字段类型实例的指针,并转化为interface{} 类型
for _, v := range resultType {
result = append(result, reflect.New(v).Interface())//*Int64 ,*string ....
}// 扫描结果
err := rows.Scan(result...)
if err != nil {
return err
}
for i, v := range result {// 找到对应的结构体index
fieldIndex := index[i]// 把scan 后的值通过反射得到指针指向的value,赋值给对应的结构体字段
obj.Field(fieldIndex).Set(reflect.ValueOf(v).Elem())// 给obj 的每个字段赋值
}// append 到slice
vv := reflect.Append(val, obj.Addr())// append到 []*main.User, maybe addr change
val.Set(vv)//[]*main.User}
return rows.Err()}
type Table struct {
TableName string //table name
GoTableName string // go struct name
PackageName string // package name
Fields []*Column // columns
}
type Column struct {
ColumnName string // column_name
ColumnType string // column_type
ColumnComment string // column_comment
}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
使用以上Table对象的模板代码:
type {{.GoTableName}} struct {{{- range .Fields}}{{.GoColumnName}}{{.GoColumnType}} `json:"{{ .ColumnName }}"` //{{.ColumnComment}}{{- end}}}
const (table="{{.TableName}}"{{- range .Fields}}{{.GoColumnName}}="{{.ColumnName}}"{{- end }})
var columns =[]string{{{- range .Fields}}{{.GoColumnName}},{{- end }}}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
通过上面的模板我们用user表的建表SQL语句生成如下代码:
type User struct {
Id int64 `json:"id"` // id字段
Name string `json:"name"` // 名称
Age int64 `json:"age"` // 年龄
Ctime time.Time `json:"ctime"` // 创建时间
Mtime time.Time `json:"mtime"` // 更新时间
}
const (table="user"
Id ="id"
Name ="name"
Age ="age"
Ctime ="ctime"
Mtime ="mtime")
var Columns =[]string{"id","name","age","ctime","mtime"}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
那么我们在查询的时候就可以这样使用
Select(Columns...)
1.
通过模板自动生成代码,可以大大的减轻开发编码负担,使我们从繁重的代码中解放出来。
reflect真的有必要吗?
由于我们SELECT时选择查找的字段和顺序是不固定的,我们有可能 SELECT id, name, age FROM user,也可能 SELECT name, id FROM user,有很大的任意性,这种情况使用反射出来的结构体tag和查询的列名来确定映射关系是必须的。但是有一种情况我们不需要用到反射,而且是一种最常用的情况,即:查询的字段名和表结构的列名一致,且顺序一致。这时候我们可以这么写,通过DeepEqual来判断查询字段和表结构字段是否一致且顺序一致来决定是否通过反射还是通过传统方法来创建对象。用传统方式创建对象(如下图第12行)令我们编码痛苦,不过可以通过模板来自动生成下面的代码,以避免手写,这样既灵活方便好用,性能又没有损耗,看起来是一个比较完美的解决方案。
func FindUserNoReflect(b *SelectBuilder)([]*User, error){
sql, args := b.Query()
rows, err := db.QueryContext(ctx, sql, args...)
if err != nil {
return nil, err
}
result :=[]*User{}
if DeepEqual(b.column, Columns){
defer rows.Close()
for rows.Next(){
a :=&User{}
if err := rows.Scan(&a.Id,&a.Name,&a.Age,&a.Ctime,&a.Mtime); err != nil {
return nil, err
}
result = append(result, a)}
if rows.Err()!= nil {
return nil, rows.Err()}
return result, nil
}
err = ScanSlice(rows,&result)
if err != nil {
return nil, err
}
return result, nil
}