From a2ebf21969236e074cff1b5d5e19a7acba158e95 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 12 May 2013 21:37:10 +0800 Subject: [PATCH] v0.1.2 Insert function now supports both struct and slice pointer parameters, batch inserting and auto transaction --- README.md | 47 ++++++++-------- README_CN.md | 47 ++++++++-------- engine.go | 1 + session.go | 151 ++++++++++++++++++++++++++++++++++++++++++++------- xorm.go | 1 + xorm_test.go | 36 ++++++++---- 6 files changed, 204 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 35965b34..2a0037c2 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Drivers for Go's sql package which currently support database/sql includes: ## Changelog +* **v0.1.2** : Insert function now supports both struct and slice pointer parameters, batch inserting and auto transaction * **v0.1.1** : Add Id, In functions and improved README * **v0.1.0** : Inital release. @@ -39,7 +40,7 @@ Drivers for Go's sql package which currently support database/sql includes: 1.Create a database engine just like sql.Open, commonly you just need create once. -``` +```Go import ( _ "github.com/Go-SQL-Driver/MySQL" "github.com/lunny/xorm" @@ -49,7 +50,7 @@ engine := xorm.Create("mysql", "root:123@/test?charset=utf8") or -``` +```Go import ( _ "github.com/mattn/go-sqlite3" "github.com/lunny/xorm" @@ -59,13 +60,13 @@ engine = xorm.Create("sqlite3", "./test.db") 1.1.If you want to show all generated SQL -``` +```Go engine.ShowSQL = true ``` 2.Define a struct -``` +```Go type User struct { Id int Name string @@ -77,7 +78,7 @@ type User struct { 3.When you set up your program, you can use CreateTables to create database tables. -``` +```Go err := engine.CreateTables(&User{}) // or err := engine.Map(&User{}, &Article{}) // err = engine.CreateAll() @@ -85,13 +86,13 @@ err := engine.CreateTables(&User{}) 4.then, insert an struct to table -``` +```Go id, err := engine.Insert(&User{Name:"lunny"}) ``` or if you want to update records -``` +```Go user := User{Name:"xlw"} rows, err := engine.Update(&user, &User{Id:1}) // or rows, err := engine.Where("id = ?", 1).Update(&user) @@ -100,7 +101,7 @@ rows, err := engine.Update(&user, &User{Id:1}) 5.Fetch a single object by user -``` +```Go var user = User{Id:27} err := engine.Get(&user) // or err := engine.Id(27).Get(&user) @@ -111,42 +112,42 @@ err := engine.Get(&user) 6.Fetch multipe objects, use Find: -``` +```Go var everyone []Userinfo err := engine.Find(&everyone) ``` 6.1 also you can use Where, Limit -``` +```Go var allusers []Userinfo err := engine.Where("id > ?", "3").Limit(10,20).Find(&allusers) //Get id>3 limit 10 offset 20 ``` 6.2 or you can use a struct query -``` +```Go var tenusers []Userinfo err := engine.Limit(10).Find(&tenusers, &Userinfo{Name:"xlw"}) //Get All Name="xlw" limit 10 offset 0 ``` 6.3 or In function -``` +```Go var tenusers []Userinfo err := engine.In("id", 1, 3, 5).Find(&tenusers) //Get All id in (1, 3, 5) ``` 7.Delete -``` +```Go err := engine.Delete(&User{Id:1}) // or err := engine.Id(1).Delete(&User{}) ``` 8.Count -``` +```Go total, err := engine.Count(&User{Name:"xlw"}) ``` @@ -155,14 +156,14 @@ Of course, SQL execution is also provided. 1.if select then use Query -``` +```Go sql := "select * from userinfo" results, err := engine.Query(sql) ``` 2.if insert, update or delete then use Exec -``` +```Go sql = "update userinfo set username=? where id=?" res, err := engine.Exec(sql, "xiaolun", 1) ``` @@ -170,7 +171,7 @@ res, err := engine.Exec(sql, "xiaolun", 1) ##Advanced Usage for deep usage, you should create a session, this func will create a database connection immediatelly -``` +```Go session, err := engine.MakeSession() defer session.Close() if err != nil { @@ -180,7 +181,7 @@ if err != nil { 1.Fetch a single object by where -``` +```Go var user Userinfo session.Where("id=?", 27).Get(&user) @@ -193,7 +194,7 @@ session.Where("name = ? and age < ?", "john", 88).Get(&user4) // even more compl 2.Fetch multiple objects -``` +```Go var allusers []Userinfo err := session.Where("id > ?", "3").Limit(10,20).Find(&allusers) //Get id>3 limit 10 offset 20 @@ -206,7 +207,7 @@ err := session.Find(&everyone) 3.Transaction -``` +```Go // add Begin() before any action session.Begin() user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} @@ -237,7 +238,7 @@ if err != nil { 4.Mixed Transaction -``` +```Go // add Begin() before any action session.Begin() user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} @@ -304,7 +305,7 @@ Another is use field tag, field tag support the below keywords which split with For Example -``` +```Go type Userinfo struct { Uid int `xorm:"id pk not null autoincr"` Username string @@ -321,7 +322,7 @@ Please visit [GoWalker](http://gowalker.org/github.com/lunny/xorm) Use space. -``` +```Go type User struct { Name string `json:"name" xorm:"name"` } diff --git a/README_CN.md b/README_CN.md index 13c6418c..59e14b7f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -14,6 +14,7 @@ xorm是一个Go语言的ORM库. 通过它可以使数据库操作非常简便。 ## 更新日志 +* **v0.1.2** : Insert函数支持混合struct和slice指针传入,并根据数据库类型自动批量插入,同时自动添加事务 * **v0.1.1** : 添加 Id, In 函数,改善 README 文档 * **v0.1.0** : 初始化工程 @@ -36,7 +37,7 @@ xorm是一个Go语言的ORM库. 通过它可以使数据库操作非常简便。 1.创建数据库引擎,这个函数的参数和sql.Open相同,但不会立即创建连接 (例如: mysql) -``` +```Go import ( _ "github.com/Go-SQL-Driver/MySQL" "github.com/lunny/xorm" @@ -46,7 +47,7 @@ engine := xorm.Create("mysql", "root:123@/test?charset=utf8") or -``` +```Go import ( _ "github.com/mattn/go-sqlite3" "github.com/lunny/xorm" @@ -56,13 +57,13 @@ engine = xorm.Create("sqlite3", "./test.db") 1.1.默认将不会显示自动生成的SQL语句,如果要显示,则需要设置 -``` +```Go engine.ShowSQL = true ``` 2.所有的ORM操作都针对一个或多个结构体,一个结构体对应一张表,定义一个结构体如下: -``` +```Go type User struct { Id int Name string @@ -74,19 +75,19 @@ type User struct { 3.在程序初始化时,可能会需要创建表 -``` +```Go err := engine.CreateTables(&User{}) ``` 4.然后,可以将一个结构体作为一条记录插入到表中。 -``` +```Go id, err := engine.Insert(&User{Name:"lunny"}) ``` 或者执行更新操作: -``` +```Go user := User{Name:"xlw"} rows, err := engine.Update(&user, &User{Id:1}) // rows, err := engine.Where("id = ?", 1).Update(&user) @@ -95,7 +96,7 @@ rows, err := engine.Update(&user, &User{Id:1}) 5.获取单个对象,可以用Get方法: -``` +```Go var user = User{Id:27} err := engine.Get(&user) // or err := engine.Id(27).Get(&user) @@ -105,42 +106,42 @@ err := engine.Get(&user) 6.获取多个对象,可以用Find方法: -``` +```Go var everyone []Userinfo err := engine.Find(&everyone) ``` 6.1 你也可以使用Where和Limit方法设定条件和查询数量 -``` +```Go var allusers []Userinfo err := engine.Where("id > ?", "3").Limit(10,20).Find(&allusers) //Get id>3 limit 10 offset 20 ``` 6.2 用一个结构体作为查询条件也是允许的 -``` +```Go var tenusers []Userinfo err := engine.Limit(10).Find(&tenusers, &Userinfo{Name:"xlw"}) //Get All Name="xlw" limit 10 offset 0 ``` 6.3 也可以调用In函数 -``` +```Go var tenusers []Userinfo err := engine.In("id", 1, 3, 5).Find(&tenusers) //Get All id in (1, 3, 5) ``` 7.Delete方法 -``` +```Go err := engine.Delete(&User{Id:1}) // or err := engine.Id(1).Delete(&User{}) ``` 8.Count方法 -``` +```Go total, err := engine.Count(&User{Name:"xlw"}) ``` @@ -149,14 +150,14 @@ total, err := engine.Count(&User{Name:"xlw"}) 如果执行Select,请用Query() -``` +```Go sql := "select * from userinfo" results, err := engine.Query(sql) ``` 如果执行Insert, Update, Delete 等操作,请用Exec() -``` +```Go sql = "update userinfo set username=? where id=?" res, err := engine.Exec(sql, "xiaolun", 1) ``` @@ -165,7 +166,7 @@ res, err := engine.Exec(sql, "xiaolun", 1) 更高级的用法,我们必须要使用session对象,session对象在创建时会立刻创建一个数据库连接。 -``` +```Go session, err := engine.MakeSession() defer session.Close() if err != nil { @@ -175,7 +176,7 @@ if err != nil { 1.session对象同样也可以查询 -``` +```Go var user Userinfo session.Where("id=?", 27).Get(&user) @@ -188,7 +189,7 @@ session.Where("name = ? and age < ?", "john", 88).Get(&user4) // even more compl 2.获取多个对象 -``` +```Go var allusers []Userinfo err := session.Where("id > ?", "3").Limit(10,20).Find(&allusers) //Get id>3 limit 10 offset 20 @@ -201,7 +202,7 @@ err := session.Find(&everyone) 3.事务处理 -``` +```Go // add Begin() before any action session.Begin() user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} @@ -232,7 +233,7 @@ if err != nil { 4.混合型事务,这个事务中,既有直接的SQL语句,又有ORM方法: -``` +```Go // add Begin() before any action session.Begin() user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} @@ -298,7 +299,7 @@ UserInfo中的成员UserName将会自动对应名为user_name的字段。 例如: -``` +```Go type Userinfo struct { Uid int `xorm:"id pk not null autoincr"` Username string @@ -316,7 +317,7 @@ type Userinfo struct { 答案:使用空格分开 -``` +```Go type User struct { Name string `json:"name" xorm:"name"` } diff --git a/engine.go b/engine.go index cfb92402..5ec107bf 100644 --- a/engine.go +++ b/engine.go @@ -23,6 +23,7 @@ type Engine struct { Tables map[reflect.Type]Table AutoIncrement string ShowSQL bool + InsertMany bool QuoteIdentifier string Statement Statement } diff --git a/session.go b/session.go index ab14afab..f0d814c8 100644 --- a/session.go +++ b/session.go @@ -11,16 +11,18 @@ import ( ) type Session struct { - Db *sql.DB - Engine *Engine - Tx *sql.Tx - Statement Statement - IsAutoCommit bool + Db *sql.DB + Engine *Engine + Tx *sql.Tx + Statement Statement + IsAutoCommit bool + IsCommitedOrRollbacked bool } func (session *Session) Init() { session.Statement = Statement{} session.IsAutoCommit = true + session.IsCommitedOrRollbacked = false } func (session *Session) Close() { @@ -69,27 +71,39 @@ func (session *Session) Having(conditions string) *Session { } func (session *Session) Begin() error { - session.IsAutoCommit = false - tx, err := session.Db.Begin() - session.Tx = tx - if session.Engine.ShowSQL { - fmt.Println("BEGIN TRANSACTION") + if session.IsAutoCommit { + session.IsAutoCommit = false + session.IsCommitedOrRollbacked = false + tx, err := session.Db.Begin() + session.Tx = tx + if session.Engine.ShowSQL { + fmt.Println("BEGIN TRANSACTION") + } + return err } - return err + return nil } func (session *Session) Rollback() error { - if session.Engine.ShowSQL { - fmt.Println("ROLL BACK") + if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { + if session.Engine.ShowSQL { + fmt.Println("ROLL BACK") + } + session.IsCommitedOrRollbacked = true + return session.Tx.Rollback() } - return session.Tx.Rollback() + return nil } func (session *Session) Commit() error { - if session.Engine.ShowSQL { - fmt.Println("COMMIT") + if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { + if session.Engine.ShowSQL { + fmt.Println("COMMIT") + } + session.IsCommitedOrRollbacked = true + return session.Tx.Commit() } - return session.Tx.Commit() + return nil } func (session *Session) scanMapIntoStruct(obj interface{}, objMap map[string][]byte) error { @@ -359,13 +373,108 @@ func (session *Session) Query(sql string, paramStr ...interface{}) (resultsSlice func (session *Session) Insert(beans ...interface{}) (int64, error) { var lastId int64 = -1 + var err error = nil + isInTransaction := !session.IsAutoCommit + + if !isInTransaction { + session.Begin() + } + for _, bean := range beans { - lastId, err := session.InsertOne(bean) - if err != nil { - return lastId, err + sliceValue := reflect.Indirect(reflect.ValueOf(bean)) + if sliceValue.Kind() == reflect.Slice { + if session.Engine.InsertMany { + lastId, err = session.InsertMulti(bean) + if err != nil { + if !isInTransaction { + session.Rollback() + } + return lastId, err + } + } else { + size := sliceValue.Len() + for i := 0; i < size; i++ { + lastId, err = session.InsertOne(sliceValue.Index(i).Interface()) + if err != nil { + if !isInTransaction { + session.Rollback() + } + return lastId, err + } + } + } + } else { + lastId, err = session.InsertOne(bean) + if err != nil { + if !isInTransaction { + session.Rollback() + } + return lastId, err + } } } - return lastId, nil + if !isInTransaction { + err = session.Commit() + } + return lastId, err +} + +func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) { + sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) + if sliceValue.Kind() != reflect.Slice { + return -1, errors.New("needs a pointer to a slice") + } + + bean := sliceValue.Index(0).Interface() + sliceElementType := Type(bean) + + table := session.Engine.Tables[sliceElementType] + session.Statement.Table = &table + + size := sliceValue.Len() + + colNames := make([]string, 0) + colMultiPlaces := make([]string, 0) + var args = make([]interface{}, 0) + + for i := 0; i < size; i++ { + elemValue := sliceValue.Index(i).Interface() + colPlaces := make([]string, 0) + + for _, col := range table.Columns { + fieldValue := reflect.Indirect(reflect.ValueOf(elemValue)).FieldByName(col.FieldName) + val := fieldValue.Interface() + if col.IsAutoIncrement && fieldValue.Int() == 0 { + continue + } + args = append(args, val) + if i == 0 { + colNames = append(colNames, col.Name) + } + colPlaces = append(colPlaces, "?") + } + colMultiPlaces = append(colMultiPlaces, strings.Join(colPlaces, ", ")) + } + + statement := fmt.Sprintf("INSERT INTO %v%v%v (%v) VALUES (%v)", + session.Engine.QuoteIdentifier, + table.Name, + session.Engine.QuoteIdentifier, + strings.Join(colNames, ", "), + strings.Join(colMultiPlaces, "),(")) + + res, err := session.Exec(statement, args...) + if err != nil { + return -1, err + } + + id, err := res.LastInsertId() + + if err != nil { + return -1, err + } + + return id, nil } func (session *Session) InsertOne(bean interface{}) (int64, error) { diff --git a/xorm.go b/xorm.go index d37b8fd0..ae5932aa 100644 --- a/xorm.go +++ b/xorm.go @@ -10,6 +10,7 @@ func Create(driverName string, dataSourceName string) Engine { engine.Tables = make(map[reflect.Type]Table) engine.Statement.Engine = &engine + engine.InsertMany = true if driverName == SQLITE { engine.AutoIncrement = "AUTOINCREMENT" } else { diff --git a/xorm_test.go b/xorm_test.go index a5a773b0..b70301d8 100644 --- a/xorm_test.go +++ b/xorm_test.go @@ -106,24 +106,33 @@ func insertAutoIncr(t *testing.T) { } func insertMulti(t *testing.T) { - user1 := Userinfo{Username: "xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()} - user2 := Userinfo{Username: "xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()} - _, err := engine.Insert(&user1, &user2) + users := []*Userinfo{ + {Username: "xlw", Departname: "dev", Alias: "lunny2", Created: time.Now()}, + {Username: "xlw2", Departname: "dev", Alias: "lunny3", Created: time.Now()}, + } + _, err := engine.Insert(&users) if err != nil { t.Error(err) } + + engine.InsertMany = false + + users = []*Userinfo{ + {Username: "xlw9", Departname: "dev", Alias: "lunny9", Created: time.Now()}, + {Username: "xlw10", Departname: "dev", Alias: "lunny10", Created: time.Now()}, + } + _, err = engine.Insert(&users) + if err != nil { + t.Error(err) + } + + engine.InsertMany = true } func insertTwoTable(t *testing.T) { userinfo := Userinfo{Username: "xlw3", Departname: "dev", Alias: "lunny4", Created: time.Now()} - uid, err := engine.Insert(&userinfo) - if err != nil { - t.Error(err) - return - } - - userdetail := Userdetail{Uid: int(uid), Intro: "I'm a very beautiful women.", Profile: "sfsaf"} - _, err = engine.Insert(&userdetail) + userdetail := Userdetail{Uid: 1, Intro: "I'm a very beautiful women.", Profile: "sfsaf"} + _, err := engine.Insert(&userinfo, &userdetail) if err != nil { t.Error(err) } @@ -199,7 +208,8 @@ func in(t *testing.T) { } fmt.Println(users) - err = engine.Where("id > ?", 2).In("id", 1, 2, 3).Find(&users) + ids := []interface{}{1, 2, 3} + err = engine.Where("id > ?", 2).In("id", ids...).Find(&users) if err != nil { t.Error(err) return @@ -352,6 +362,7 @@ func TestMysql(t *testing.T) { exec(t) insertAutoIncr(t) insertMulti(t) + insertTwoTable(t) update(t) delete(t) get(t) @@ -378,6 +389,7 @@ func TestSqlite(t *testing.T) { exec(t) insertAutoIncr(t) insertMulti(t) + insertTwoTable(t) update(t) delete(t) get(t)