blog icon indicating copy to clipboard operation
blog copied to clipboard

动手写ORM框架 - GeeORM第三天 记录新增和查询 | 极客兔兔

Open geektutu opened this issue 5 years ago • 28 comments
trafficstars

https://geektutu.com/post/geeorm-day3.html

7天用 Go语言/golang 从零实现 ORM 框架 GeeORM 教程(7 days implement golang object relational mapping framework from scratch tutorial),动手写 ORM 框架,参照 gorm, xorm 的实现。实现新增(insert)记录的功能;使用反射(reflect)将数据库的记录转换为对应的结构体实例,实现查询(select)功能。

geektutu avatar Mar 08 '20 17:03 geektutu

for i, value := range values {
		v := value.([]interface{})
		if bindStr == "" {
			bindStr = genBindVars(len(v))
		}
		sql.WriteString(fmt.Sprintf("(%v)", bindStr))
		if i+1 != len(values) {
			sql.WriteString(", ")
		}
		vars = append(vars, v...)
	}

value.([]interface{})这里是什么意图没看懂?能解释下吗为什么是一个空接口的切片呢

walkmiao avatar Mar 31 '20 09:03 walkmiao

我在一个测试文件中看到TestMain方法 那里面有一句 _ = TestDB.Close() 为什么db close后紧接着的测试还能正常进行呢 DB不是已经Close了吗

walkmiao avatar Apr 03 '20 03:04 walkmiao

@walkmiao

我在一个测试文件中看到TestMain方法 那里面有一句 _ = TestDB.Close() 为什么db close后紧接着的测试还能正常进行呢 DB不是已经Close了吗

func TestMain(m *testing.M) {
	TestDB, _ = sql.Open("sqlite3", "../gee.db")
	code := m.Run()
	_ = TestDB.Close()
	os.Exit(code)
}

这个是 Go 测试工程的 setupteardown 的机制,允许在每个用例运行m.Run()前后做一些事情,Open 是用例执行前运行的,Close 是用例执行后运行的。

参考 Go语言单元测试简明教程#setup 和 teardown

geektutu avatar Apr 03 '20 03:04 geektutu

谢谢我去学习下 lch 邮箱:[email protected] 签名由 网易邮箱大师 定制 在2020年04月03日 11:57,Dai Jie 写道: @walkmiao 我在一个测试文件中看到TestMain方法 那里面有一句 _ = TestDB.Close() 为什么db close后紧接着的测试还能正常进行呢 DB不是已经Close了吗 func TestMain(m *testing.M) {

TestDB, _ = sql.Open("sqlite3", "../gee.db")

code := m.Run()

_ = TestDB.Close()

os.Exit(code)

} 这个是 Go 测试工程的 setup 和 teardown 的机制,允许在每个用例运行m.Run()前后做一些事情,Open 是用例执行前运行的,Close 是用例执行后运行的。 参考 Go语言单元测试简明教程#setup 和 teardown — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

walkmiao avatar Apr 03 '20 11:04 walkmiao

Find 的代码,真的看不懂,咋整

yoursNicholas avatar Aug 27 '20 04:08 yoursNicholas

@walkmiao

for i, value := range values {
		v := value.([]interface{})
		if bindStr == "" {
			bindStr = genBindVars(len(v))
		}
		sql.WriteString(fmt.Sprintf("(%v)", bindStr))
		if i+1 != len(values) {
			sql.WriteString(", ")
		}
		vars = append(vars, v...)
	}

value.([]interface{})这里是什么意图没看懂?能解释下吗为什么是一个空接口的切片呢

每个Model的RecordValues都是一个切片,这里个values是一个二维数组

furthergo avatar Oct 06 '20 10:10 furthergo

func (s *Session) Insert(values ...interface{}) (int64, error) {
	recordValues := make([]interface{}, 0)
	for _, value := range values {
		table := s.Model(value).RefTable()
		s.clause.Set(clause.INSERT, table.Name, table.FieldNames)
		recordValues = append(recordValues, table.RecordValues(value))
	}

	s.clause.Set(clause.VALUES, recordValues...)
	sql, vars := s.clause.Build(clause.INSERT, clause.VALUES)
	result, err := s.Raw(sql, vars...).Exec()
	if err != nil {
		return 0, err
	}

	return result.RowsAffected()
}

请教一下,这里s.clause.Set(clause.INSERT, table.Name, table.FieldNames)是不是只用设置一次,同一个Type一次执行应该只能设置一次query string吧,这里是为了书写方便是吗?

furthergo avatar Oct 06 '20 10:10 furthergo

@furthergo 你的理解是对的,table.Name 只需要设置一次,的确是为了书写方便。

geektutu avatar Oct 06 '20 11:10 geektutu

type generator func(values ...interface{}) (string, []interface{})

var generators map[Type]generator 请问这段是什么意思,函数作为MAP的VALUE吗?

xiezhenyu19970913 avatar Dec 05 '20 07:12 xiezhenyu19970913

@xiezhenyu19970913 对的,把函数看做普通变量来使用即可。

geektutu avatar Dec 05 '20 07:12 geektutu

看到geeorm这一章感觉有点吃力,主要是对反射和函数型编程不熟

kekemuyu avatar Jan 12 '21 05:01 kekemuyu

func (s *Session) Find(values interface{}) error { destSlice := reflect.Indirect(reflect.ValueOf(values)) destType := destSlice.Type().Elem() table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

会不会这样写 table := s.Model(reflect.New(destType).Interface()).RefTable() 好点

tmphuang6 avatar Jan 13 '21 08:01 tmphuang6

先介绍generator,然后当中的map[Type]generator的Type会比较让人费解,通过github代码cmd+A发现实际上Type定义在clause里面,但是介绍顺序导致读者理解起来比较尴尬。

BrianQy avatar Aug 18 '21 12:08 BrianQy

这就是大佬在Top公司一天的任务量吗,换做是我可能要一周。没有比较,就没有暴击!

DurantVivado avatar Jan 10 '22 08:01 DurantVivado

太难了😂

qixiasu avatar May 09 '22 09:05 qixiasu

看来不把反射搞清楚是看不下去了……

qixiasu avatar May 11 '22 14:05 qixiasu

@tmphuang6 func (s *Session) Find(values interface{}) error { destSlice := reflect.Indirect(reflect.ValueOf(values)) destType := destSlice.Type().Elem() table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

会不会这样写 table := s.Model(reflect.New(destType).Interface()).RefTable() 好点

不会,因为需要destSlice,destType

niconical avatar Jul 18 '22 07:07 niconical

@tmphuang6 func (s *Session) Find(values interface{}) error { destSlice := reflect.Indirect(reflect.ValueOf(values)) destType := destSlice.Type().Elem() table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

会不会这样写 table := s.Model(reflect.New(destType).Interface()).RefTable() 好点

不太行,reflect.New注释里面 New returns a Value representing a pointer to a new zero value ,the returned Value's Type is PointerTo(typ),给的是一个指针的Value,需要使用Elem方法提取具体的值

ShiMaRing avatar Jul 27 '22 15:07 ShiMaRing

Find方法里给values传递dest各个field的指针然后通过scan(values...)这样间接给dest赋值这个方法感觉很巧妙。

yc-2027 avatar Aug 08 '22 14:08 yc-2027

	for i, value := range values {
		v := value.([]interface{})
		if bindStr == "" {
			bindStr = genBindVars(len(v))
		}
		sql.WriteString(fmt.Sprintf("(%v)", bindStr))
		if i+1 != len(values) {
			sql.WriteString(", ")
		}
		vars = append(vars, v...)
	}

_value函数中for循环中是否应该将关于bindStr的判断去掉呢?如果有if判断的话只会在第一个循环中给bindStr赋值,但是每个value的长度应该是不同的吧?

bowenddd avatar Aug 31 '22 10:08 bowenddd

@bowenddd

	for i, value := range values {
		v := value.([]interface{})
		if bindStr == "" {
			bindStr = genBindVars(len(v))
		}
		sql.WriteString(fmt.Sprintf("(%v)", bindStr))
		if i+1 != len(values) {
			sql.WriteString(", ")
		}
		vars = append(vars, v...)
	}

_value函数中for循环中是否应该将关于bindStr的判断去掉呢?如果有if判断的话只会在第一个循环中给bindStr赋值,但是每个value的长度应该是不同的吧?

v := value.([]interface{})      // 将value转成了切片,切片长度只有1
bindStr = genBindVars(len(v))   // len(v) = 1, bindStr = (?)

这里应该没有问题。

drone789 avatar Nov 08 '22 13:11 drone789

func (s *Session) Find(values interface{}) error {
	destSlice := reflect.Indirect(reflect.ValueOf(values))
	destType := destSlice.Type().Elem()
	table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

	s.clause.Set(clause.SELECT, table.Name, table.FieldNames)
	sql, vars := s.clause.Build(clause.SELECT, clause.WHERE, clause.ORDERBY, clause.LIMIT)
	rows, err := s.Raw(sql, vars...).QueryRows()
	if err != nil {
		return err
	}

	for rows.Next() {
		dest := reflect.New(destType).Elem()
		var values []interface{}
		for _, name := range table.FieldNames {
			values = append(values, dest.FieldByName(name).Addr().Interface())
		}
		if err := rows.Scan(values...); err != nil {
			return err
		}
		destSlice.Set(reflect.Append(destSlice, dest))
	}
	return rows.Close()
}

destSlice.Set(reflect.Append(destSlice, dest)) 每次循环Set一次,能否改成循环结束后,一次Set?

drone789 avatar Nov 08 '22 13:11 drone789

@bowenddd

	for i, value := range values {
		v := value.([]interface{})
		if bindStr == "" {
			bindStr = genBindVars(len(v))
		}
		sql.WriteString(fmt.Sprintf("(%v)", bindStr))
		if i+1 != len(values) {
			sql.WriteString(", ")
		}
		vars = append(vars, v...)
	}

_value函数中for循环中是否应该将关于bindStr的判断去掉呢?如果有if判断的话只会在第一个循环中给bindStr赋值,但是每个value的长度应该是不同的吧?

是的 我也检测出了这个问题 我改了一下

func _values(values ...interface{}) (string, []interface{}) {
	// VALUES ($v1), ($v2), ...
	var bindStr string
	var sql strings.Builder
	var vars []interface{}
	sql.WriteString("VALUES ")
	for i, value := range values {
		v := value.([]interface{}) // 将value转成了切片,切片长度只有1
		// 转换成对应v切片数量的问号
		bindStr = genBindVars(len(v))
		sql.WriteString(fmt.Sprintf("(%v)", bindStr))
		if i+1 != len(values) { // 不结束就分割一下
			sql.WriteString(", ")
		}
		vars = append(vars, v...)
	}
	return sql.String(), vars
}

paopaoshuaige avatar Feb 03 '23 06:02 paopaoshuaige

@drone789

func (s *Session) Find(values interface{}) error {
	destSlice := reflect.Indirect(reflect.ValueOf(values))
	destType := destSlice.Type().Elem()
	table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

	s.clause.Set(clause.SELECT, table.Name, table.FieldNames)
	sql, vars := s.clause.Build(clause.SELECT, clause.WHERE, clause.ORDERBY, clause.LIMIT)
	rows, err := s.Raw(sql, vars...).QueryRows()
	if err != nil {
		return err
	}

	for rows.Next() {
		dest := reflect.New(destType).Elem()
		var values []interface{}
		for _, name := range table.FieldNames {
			values = append(values, dest.FieldByName(name).Addr().Interface())
		}
		if err := rows.Scan(values...); err != nil {
			return err
		}
		destSlice.Set(reflect.Append(destSlice, dest))
	}
	return rows.Close()
}

destSlice.Set(reflect.Append(destSlice, dest)) 每次循环Set一次,能否改成循环结束后,一次Set?

不可以 一个dest是一个对应的结构体而不是切片,不能存储多个值,只能一次一次往里加

paopaoshuaige avatar Feb 03 '23 08:02 paopaoshuaige

@paopaoshuaige

@bowenddd

	for i, value := range values {
		v := value.([]interface{})
		if bindStr == "" {
			bindStr = genBindVars(len(v))
		}
		sql.WriteString(fmt.Sprintf("(%v)", bindStr))
		if i+1 != len(values) {
			sql.WriteString(", ")
		}
		vars = append(vars, v...)
	}

_value函数中for循环中是否应该将关于bindStr的判断去掉呢?如果有if判断的话只会在第一个循环中给bindStr赋值,但是每个value的长度应该是不同的吧?

是的 我也检测出了这个问题 我改了一下

func _values(values ...interface{}) (string, []interface{}) {
	// VALUES ($v1), ($v2), ...
	var bindStr string
	var sql strings.Builder
	var vars []interface{}
	sql.WriteString("VALUES ")
	for i, value := range values {
		v := value.([]interface{}) // 将value转成了切片,切片长度只有1
		// 转换成对应v切片数量的问号
		bindStr = genBindVars(len(v))
		sql.WriteString(fmt.Sprintf("(%v)", bindStr))
		if i+1 != len(values) { // 不结束就分割一下
			sql.WriteString(", ")
		}
		vars = append(vars, v...)
	}
	return sql.String(), vars
}

这里没问题,只是实现的方式不同。s.Insert这样写想达到的目的是批量插入同类型多笔数据。
INSERT INTO table (c1,c2,c3) VALUES(1,2,3),(1,2,3)

如果想一次插入多种类型的多笔数据的话需要改造一下,类似于

func (s *Session) Insert(values ...interface{}) (int64, error) {
	for _, value := range values {
		table := s.Model(value).RefTable()
		s.clause.Set(clause.INSERT, table.Name, table.FieldNames)

        // 类似于这样改,思路是这样
	    s.clause.Set(clause.VALUES, table.RecordValues(value))
	    sql, vars := s.clause.Build(clause.INSERT, clause.VALUES)
	    result, err := s.Raw(sql, vars...).Exec()
	    if err != nil {
		    return 0, err
	    }
	}
    ......
	return result.RowsAffected()
}

这样每个value都会反射,SQL都会有一个INSERT开头
s.Insert(&User{}, &Student{}) 可以这样用,但是不推荐。
INSERT INTO table1 (c1,c2,c3) VALUES(1,2,3)
INSERT INTO table2 (c1,c2) VALUES(1,2)

a2cd avatar Jun 08 '23 15:06 a2cd

@drone789

func (s *Session) Find(values interface{}) error {
	destSlice := reflect.Indirect(reflect.ValueOf(values))
	destType := destSlice.Type().Elem()
	table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

	s.clause.Set(clause.SELECT, table.Name, table.FieldNames)
	sql, vars := s.clause.Build(clause.SELECT, clause.WHERE, clause.ORDERBY, clause.LIMIT)
	rows, err := s.Raw(sql, vars...).QueryRows()
	if err != nil {
		return err
	}

	for rows.Next() {
		dest := reflect.New(destType).Elem()
		var values []interface{}
		for _, name := range table.FieldNames {
			values = append(values, dest.FieldByName(name).Addr().Interface())
		}
		if err := rows.Scan(values...); err != nil {
			return err
		}
		destSlice.Set(reflect.Append(destSlice, dest))
	}
	return rows.Close()
}

destSlice.Set(reflect.Append(destSlice, dest)) 每次循环Set一次,能否改成循环结束后,一次Set?

这个不是挺正常的吗,dest保存一行查询数据赋值给相应结构体的值,每次赋好一个值就向slice里面append一下

GuiQuQu avatar Jul 01 '23 14:07 GuiQuQu

    for rows.Next() {
		dest := reflect.New(destType).Elem()
		var values []interface{}
		for _, name := range table.FieldNames {
			values = append(values, dest.FieldByName(name).Addr().Interface())
		}
		if err := rows.Scan(values...); err != nil {
            // 此处
			return err
		}
		destSlice.Set(reflect.Append(destSlice, dest))
	}

大佬们,为什么上面的“此处”报错返回时,不做rows.Close()?

hjrbill avatar Jan 12 '24 20:01 hjrbill