在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()
方法来输出即可。周末闲着没事,那就重构一下吧。
参考:
golang实现生成器模式1
golang实现生成器模式2
golang实现生成器模式3
生成器模式的目标:
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
}
// =========================== 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
}
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