除了这些高层次的抽象方法,Sqlx也对更低层次的查询方法进行了扩展:
查询单行:
row = db.QueryRowx("select * from Person where Name=?", "Zhang San") if row.Err() == sql.ErrNoRows { log.Println("Not found Zhang San") } else { queryPerson := &Person{} err = row.StructScan(queryPerson) if err != nil { log.Println(err) return } log.Println("QueryRowx-StructScan:", queryPerson.City) }
查询多行:
rows, err := db.Queryx("select * from Person where Name=?", "Zhang San") if err != nil { log.Println(err) return } for rows.Next() { rowSlice, err := rows.SliceScan() if err != nil { log.Println(err) return } log.Println("Queryx-SliceScan:", string(rowSlice[2].([]byte))) }
命名参数Query:
rows, err = db.NamedQuery("select * from Person where Name=:n", map[string]interface{}{"n": "Zhang San"})
查询出数据行后,这里有多种映射方法:StructScan、SliceScan和MapScan,分别对应映射后的不同数据结构。
预处理语句
对于重复使用的SQL语句,可以采用预处理的方式,减少SQL解析的次数,减少网络通信量,从而提高SQL操作的吞吐量。
下面的代码展示了sqlx中如何使用stmt查询数据,分别采用了命名参数和通用占位符两种传参方式。
bosima := Person{} bossma := Person{} nstmt, err := db.PrepareNamed("SELECT * FROM Person WHERE Name = :n") if err != nil { log.Println(err) return } err = nstmt.Get(&bossma, map[string]interface{}{"n": "BOSSMA"}) if err != nil { log.Println(err) return } log.Println("NamedStmt-Get1:", bossma.City) err = nstmt.Get(&bosima, map[string]interface{}{"n": "BOSIMA"}) if err != nil { log.Println(err) return } log.Println("NamedStmt-Get2:", bosima.City) stmt, err := db.Preparex("SELECT * FROM Person WHERE Name=?") if err != nil { log.Println(err) return } err = stmt.Get(&bosima, "BOSIMA") if err != nil { log.Println(err) return } log.Println("Stmt-Get1:", bosima.City) err = stmt.Get(&bossma, "BOSSMA") if err != nil { log.Println(err) return } log.Println("Stmt-Get2:", bossma.City)
对于上文增删改查的方法,sqlx都有相应的扩展方法。与上文不同的是,需要先使用SQL模版创建一个stmt实例,然后执行相关SQL操作时,不再需要传递SQL语句。
数据库事务
为了在事务中执行sqlx扩展的增删改查方法,sqlx必然也对数据库事务做一些必要的扩展支持。
tx, err = db.Beginx() if err != nil { log.Println(err) return } tx.MustExec("INSERT INTO Person (Name, City, AddTime, UpdateTime) VALUES (?, ?, ?, ?)", "Zhang San", "Beijing", time.Now(), time.Now()) tx.MustExec("INSERT INTO Person (Name, City, AddTime, UpdateTime) VALUES (?, ?, ?, ?)", "Li Si Hai", "Dong Bei", time.Now(), time.Now()) err = tx.Commit() if err != nil { log.Println(err) return } log.Println("tx-Beginx is successful")
上面这段代码就是一个简单的sqlx数据库事务示例,先通过db.Beginx开启事务,然后执行SQL语句,最后提交事务。
如果想要更改默认的数据库隔离级别,可以使用另一个扩展方法:
tx, err = db.BeginTxx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
sqlx干了什么
通过上边的实战,基本上就可以使用sqlx进行开发了。为了更好的使用sqlx,我们可以再了解下sqlx是怎么做到上边这些扩展的。
Go的标准库中没有提供任何具体数据库的驱动,只是通过database/sql库定义了操作数据库的通用接口。sqlx中也没有包含具体数据库的驱动,它只是封装了常用SQL的操作方法,让我们的SQL写起来更爽。
MustXXX
sqlx提供两个几个MustXXX方法。
Must方法是为了简化错误处理而出现的,当开发者确定SQL操作不会返回错误的时候就可以使用Must方法,但是如果真的出现了未知错误的时候,这个方法内部会触发panic,开发者需要有一个兜底的方案来处理这个panic,比如使用recover。
这里是MustExec的源码:
func MustExec(e Execer, query string, args ...interface{}) sql.Result { res, err := e.Exec(query, args...) if err != nil { panic(err) } return res }
NamedXXX
对于需要传递SQL参数的方法, sqlx都扩展了命名参数的传参方式。这让我们可以在更高的抽象层次处理数据库操作,而不必关心数据库操作的细节。
这种方法的内部会解析我们的SQL语句,然后从传递的struct、map或者slice中提取命名参数对应的值,然后形成新的SQL语句和参数集合,再交给底层database/sql的方法去执行。
这里摘抄一些代码:
func NamedExec(e Ext, query string, arg interface{}) (sql.Result, error) { q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e)) if err != nil { return nil, err } return e.Exec(q, args...) }
NamedExec 内部调用了 bindNamedMapper,这个方法就是用于提取参数值的。其内部分别对Map、Slice和Struct有不同的处理。
func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) { ... switch { case k == reflect.Map && t.Key().Kind() == reflect.String: ... return bindMap(bindType, query, m) case k == reflect.Array || k == reflect.Slice: return bindArray(bindType, query, arg, m) default: return bindStruct(bindType, query, arg, m) } }
以批量插入为例,我们的代码是这样写的:
insertPersonArray := []Person{ {Name: "BOSIMA", City: "Wu Han", AddTime: time.Now(), UpdateTime: time.Now()}, {Name: "BOSSMA", City: "Xi An", AddTime: time.Now(), UpdateTime: time.Now()}, {Name: "BOMA", City: "Cheng Du", AddTime: time.Now(), UpdateTime: time.Now()}, } insertPersonArrayResult, err := db.NamedExec("INSERT INTO Person (Name, City, AddTime, UpdateTime) VALUES(:Name, :City, :AddTime, :UpdateTime)", insertPersonArray)
经过bindNamedMapper处理后SQL语句和参数是这样的:
这里使用了反射,有些人可能会担心性能的问题,对于这个问题的常见处理方式就是缓存起来,sqlx也是这样做的。
XXXScan
这些Scan方法让数据行到对象的映射更为方便,sqlx提供了StructScan、SliceScan和MapScan,看名字就可以知道它们映射的数据结构。而且在这些映射能力的基础上,sqlx提供了更为抽象的Get和Select方法。
这些Scan内部还是调用了database/sql的Row.Scan方法。
以StructScan为例,其使用方法为:
queryPerson := &Person{} err = row.StructScan(queryPerson)
经过sqlx处理后,调用Row.Scan的参数是:
Demo程序
以上就是go第三方库sqlx操作MySQL及ORM原理的详细内容,更多关于go sqlx操作MySQLORM的资料请关注其它相关文章!
更多SQL内容来自木庄网络博客