golang使用builder生成器模式组装sql语句

文章目录

    • 一、前言
      • 1、背景
      • 2、生成器模式
    • 二、builder生成sql语句
      • 1、定义要生成的对象和builder
      • 2、每个表基于自己的字段实现细节
      • 3、调用方式
    • 三、一些思考

一、前言

1、背景

      在code review的时候,发现项目中有如下代码:

// GetUpdateFieldStr 组装更新的sql和参数
func GetUpdateFieldStr(ctx context.Context, updateFields *infra.UpdateFeilds) (sqlUpdate string, args []interface{}) {
	// 拼接update字段
	log.Debugf("getUpdateFieldStr: (%#v)", updateFields)
	sqlUpdate = ""
	// 拼接update字段
	if updateFields.MapId != "" {
		sqlUpdate += " map_id = ?,"
		args = append(args, updateFields.MapId)
	}
	if updateFields.ObjBase != "" {
		sqlUpdate += " obj_base = ?,"
		args = append(args, updateFields.ObjBase)
	}
....

	log.Debugf("sqlUpdate : %s,args:(%#v)", sqlUpdate, args)
	return
}

// GetSqlWhereStr 组装where条件和参数
func GetSqlWhereStr(ctx context.Context, wheres *infra.WhereFields) (sqlWhere string, args []interface{}) {}

      项目中使用的是sqlx来进行增删改查,一般来说sqlx相对于orm来说是比较原始的,要自己写sql或者封装方法去拼接sql,但是上面的代码存在几个问题。

1、表字段越多,函数越长,出错概率越大
2、多个表的话,这两个方法就会不断增大,冗余
3、如果是要把字段更新为空,比如字符串类型的字段,目标就是更新成""空字符串,以上方法明显是不符合的。

      而针对以上代码,比较好的方式就是用builder生成器模式去做,构造一个builder对象,不断的通过链式调用去拼接字段,最终通过build()方法来输出即可。周末闲着没事,那就重构一下吧。

2、生成器模式

参考:
golang实现生成器模式1
golang实现生成器模式2
golang实现生成器模式3

生成器模式的目标:

  1. 将复杂对象的构造与其表示分开。
  2. 允许相同的构建过程创建不同的表示。
  3. 将构造逻辑封装在单独的构建器类中。
  4. 允许创建具有许多可选或变化部分的对象。

二、builder生成sql语句

1、定义要生成的对象和builder

package mysql

import (
	"strings"
)

// SqlClause 定义要生成的对象,组装表sql
type SqlClause struct {
	table string
	sql   string
	args  []interface{}
}
// 为了节省空间,具体的SelectBuilder暂时不写
type SelectInterface interface {
	Select(field string) SelectInterface
	Count(field string) SelectInterface
	Build() (sql string, args []interface{})
}

// 定义update语句的接口
type UpdateInterface interface {
	Update(field string, value interface{}) UpdateInterface
	Build() (sql string, args []interface{})
}

// 定义where语句的接口
type WhereInterface interface {
	Where(field string, value interface{}) WhereInterface
	In(field string, valStr string) WhereInterface
	Build() (sql string, args []interface{})
}

// UpdateBuilder update语句生成器
type UpdateBuilder struct {
	UpdateSql *SqlClause
	WhereSql  *WhereBuilder
}

func NewUpdateBuilder(table string) *UpdateBuilder {
	return &UpdateBuilder{
		UpdateSql: &SqlClause{
			table: table,
			sql:   "update " + table + " set ",
			args:  make([]interface{}, 0),
		},
		WhereSql: NewWhereBuilder(),
	}
}

// 通过update语句组装
func (u *UpdateBuilder) Update(field string, value interface{}) UpdateInterface {
	u.UpdateSql.sql += " " + field + " = ?,"
	u.UpdateSql.args = append(u.UpdateSql.args, value)
	return u
}

// 输出完整的update语句
func (u *UpdateBuilder) Build() (sql string, args []interface{}) {
	if len(u.UpdateSql.args) < 1 {
		return
	}
	if len(u.WhereSql.sqlClause.args) < 1 {
		return
	}
	// 拼接sql
	sql = strings.TrimRight(u.UpdateSql.sql, ", ")
	args = append(args, u.UpdateSql.args...)

	sql += strings.TrimRight(u.WhereSql.sqlClause.sql, "and ")
	args = append(args, u.WhereSql.sqlClause.args...)
	return
}

// WhereBuilder where语句生成器
type WhereBuilder struct {
	sqlClause *SqlClause
}

func NewWhereBuilder() *WhereBuilder {
	return &WhereBuilder{
		sqlClause: &SqlClause{
			sql:  " where ",
			args: make([]interface{}, 0),
		},
	}
}

// 通用where条件组装
func (w *WhereBuilder) Where(field string, value interface{}) WhereInterface {
	w.sqlClause.sql += " " + field + " = ? and "
	w.sqlClause.args = append(w.sqlClause.args, value)
	return w
}

// where语句的in查询
func (w *WhereBuilder) In(field string, valStr string) WhereInterface {
	fieldSlice := strings.Split(valStr, ",")
	var inStr string
	for i, v := range fieldSlice {
		if i == 0 {
			inStr = "?"
		} else {
			inStr += ", ?"
		}
		w.sqlClause.args = append(w.sqlClause.args, v)
	}
	w.sqlClause.sql += " " + field + " in ( " + inStr + ") and "
	return w
}

// 输出结果
func (w *WhereBuilder) Build() (sql string, args []interface{}) {
	if len(w.sqlClause.args) < 1 {
		return
	}
	// 拼接where sql
	sqlWhere := strings.TrimRight(w.sqlClause.sql, "and ")
	sql += sqlWhere
	args = append(args, w.sqlClause.args...)
	return
}


2、每个表基于自己的字段实现细节

// =========================== table级别方法  =================================
// MappingTaskBuilder map表的builder,组装select,update等sql,调用方式如下:
// 完整的update sql : MappingTaskBuilder.Update().UpdatemapId().WhereTaskId().Build()
// 完整的select sql : MappingTaskBuilder.Select().SelectFields().WhereTaskId().Build()
// 单独的where sql : MappingTaskBuilder.WhereCodes().WhereTaskId().BuildWhereSql()
type MappingTaskBuilder struct {
	updateBuilder *mysql.UpdateBuilder
	selectBuilder *mysql.SelectBuilder
	whereBuilder  *mysql.WhereBuilder
}

func NewMappingTaskBuilder() *MappingTaskBuilder {
	return &MappingTaskBuilder{
		whereBuilder: mysql.NewWhereBuilder(),
	}
}

func (m *MappingTaskBuilder) Update(table string) *MappingTaskBuilder {
	m.updateBuilder = mysql.NewUpdateBuilder(table)
	return m
}

func (m *MappingTaskBuilder) Select(table string) *MappingTaskBuilder {
	m.selectBuilder = mysql.NewSelectBuilder(table)
	return m
}

// ------- update条件,定义具体字段所属函数是为了编码的清晰
// map_id
func (m *MappingTaskBuilder) UpdateMapId(field string, value interface{}) *MappingTaskBuilder {
	m.updateBuilder.Update(field, value)
	return m
}

// obj_base
func (m *MappingTaskBuilder) UpdateObjBase(field string, value interface{}) *MappingTaskBuilder {
	m.updateBuilder.Update(field, value)
	return m
}

// ---- select条件,可以传单个字段,也可以一次性传输

// Select 组装select语句,selectStr为空则返回
func (m *MappingTaskBuilder) SelectFields(selectStr string) *MappingTaskBuilder {
	if selectStr == "" {
		return m
	}
	m.selectBuilder.Select(selectStr)
	return m
}

// ---- where条件

// codes
func (m *MappingTaskBuilder) WhereCodes(field string, valStr string) *MappingTaskBuilder {
	m.whereBuilder.In(field, valStr)
	return m
}

// task_id
func (m *MappingTaskBuilder) WhereTaskId(field string, value interface{}) *MappingTaskBuilder {
	m.whereBuilder.Where(field, value)
	return m
}

// --------- 输出
// 输出完整update语句
func (m *MappingTaskBuilder) BuildUpdateSql() (sql string, args []interface{}) {
	// where语句赋值
	m.updateBuilder.WhereSql = m.whereBuilder
	sql, args = m.updateBuilder.Build()
	return
}

// 输出完整select语句
func (m *MappingTaskBuilder) BuildSelectSql() (sql string, args []interface{}) {
	// where语句赋值
	m.selectBuilder.WhereSql = m.whereBuilder
	sql, args = m.selectBuilder.Build()
	return
}

// 输出 where的sql
func (m *MappingTaskBuilder) BuildWhereSql() (sql string, args []interface{}) {
	sql, args = m.whereBuilder.Build()
	return
}

// 输出完整的sql
func (m *MappingTaskBuilder) Build() (sql string, args []interface{}) {
	if m.selectBuilder == nil && m.updateBuilder == nil {
		return
	}

	if m.selectBuilder == nil {
		return m.BuildUpdateSql()
	}

	if m.updateBuilder == nil {
		return m.BuildSelectSql()
	}
	// 没有设置update和select则只输出where sql语句
	return m.BuildWhereSql()
}

// Reset 重置
func (m *MappingTaskBuilder) Reset() {
	m = nil
}

3、调用方式

	builder := xengine.NewMappingTaskBuilder()
	sqlUpdate, args = builder.Update("mapping_task").
		UpdateMapId("map_id", updateFields.MapId).
    	UpdateObjBase("obj_base", updateFields.ObjBase).
		WhereMapId("map_id", wheres.MapId).
    	WhereDel("del", wheres.Del).
    	Builder()

三、一些思考

      写完以上代码之后发现这跟orm有点像啊,每个表都是一个对象,通过链式调用组装和执行sql… 我们上面的封装就是最简单的组装sql

      某种意义上也说明是技术选型的问题,业务没那么复杂的情况下,选用orm虽然重一点,但起码代码是便于编写和方便维护的。sqlx在复杂sql比如统计类项目中还是可以的,动不动几十行的sql起步,用orm就有些痛苦了。

      可惜项目已经成型,想换个sql引擎改动实在有点大,那就只能不断的提升代码水平了,不然code review实在是一种折磨。。

end

你可能感兴趣的:(golang,golang,sql,数据库)