diff --git a/base_test.go b/base_test.go index 514369e2..5554d806 100644 --- a/base_test.go +++ b/base_test.go @@ -145,12 +145,18 @@ func mapper(engine *Engine, t *testing.T) { func insert(engine *Engine, t *testing.T) { user := Userinfo{0, "xiaolunwen", "dev", "lunny", time.Now(), Userdetail{Id: 1}, 1.78, []byte{1, 2, 3}, true} - _, err := engine.Insert(&user) + cnt, err := engine.Insert(&user) fmt.Println(user.Uid) if err != nil { t.Error(err) panic(err) } + if cnt != 1 { + err = errors.New("insert not returned 1") + t.Error(err) + panic(err) + return + } if user.Uid <= 0 { err = errors.New("not return id error") t.Error(err) @@ -158,12 +164,18 @@ func insert(engine *Engine, t *testing.T) { } user.Uid = 0 - _, err = engine.Insert(&user) + cnt, err = engine.Insert(&user) if err == nil { err = errors.New("insert failed but no return error") t.Error(err) panic(err) } + if cnt != 0 { + err = errors.New("insert not returned 1") + t.Error(err) + panic(err) + return + } } func testQuery(engine *Engine, t *testing.T) { @@ -210,12 +222,18 @@ func insertAutoIncr(engine *Engine, t *testing.T) { // auto increment insert user := Userinfo{Username: "xiaolunwen2", Departname: "dev", Alias: "lunny", Created: time.Now(), Detail: Userdetail{Id: 1}, Height: 1.78, Avatar: []byte{1, 2, 3}, IsMan: true} - _, err := engine.Insert(&user) + cnt, err := engine.Insert(&user) fmt.Println(user.Uid) if err != nil { t.Error(err) panic(err) } + if cnt != 1 { + err = errors.New("insert not returned 1") + t.Error(err) + panic(err) + return + } if user.Uid <= 0 { t.Error(errors.New("not return id error")) } @@ -229,15 +247,16 @@ func insertMulti(engine *Engine, t *testing.T) { {Username: "xlw11", Departname: "dev", Alias: "lunny2", Created: time.Now()}, {Username: "xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, } - id, err := engine.Insert(&users) + cnt, err := engine.Insert(&users) if err != nil { t.Error(err) panic(err) } - if id <= 0 { - err = errors.New("not return id error") + if cnt != int64(len(users)) { + err = errors.New("insert not returned 1") t.Error(err) panic(err) + return } users2 := []*Userinfo{ @@ -247,16 +266,17 @@ func insertMulti(engine *Engine, t *testing.T) { &Userinfo{Username: "1xlw22", Departname: "dev", Alias: "lunny3", Created: time.Now()}, } - id, err = engine.Insert(&users2) + cnt, err = engine.Insert(&users2) if err != nil { t.Error(err) panic(err) } - if id <= 0 { - err = errors.New("not return id error") + if cnt != int64(len(users2)) { + err = errors.New(fmt.Sprintf("insert not returned %v", len(users2))) t.Error(err) panic(err) + return } } @@ -264,7 +284,7 @@ func insertTwoTable(engine *Engine, t *testing.T) { userdetail := Userdetail{Id: 1, Intro: "I'm a very beautiful women.", Profile: "sfsaf"} userinfo := Userinfo{Username: "xlw3", Departname: "dev", Alias: "lunny4", Created: time.Now(), Detail: userdetail} - _, err := engine.Insert(&userinfo, &userdetail) + cnt, err := engine.Insert(&userinfo, &userdetail) if err != nil { t.Error(err) panic(err) @@ -281,6 +301,13 @@ func insertTwoTable(engine *Engine, t *testing.T) { t.Error(err) panic(err) } + + if cnt != 2 { + err = errors.New("insert not returned 2") + t.Error(err) + panic(err) + return + } } type Condi map[string]interface{} @@ -288,56 +315,106 @@ type Condi map[string]interface{} func update(engine *Engine, t *testing.T) { // update by id user := Userinfo{Username: "xxx", Height: 1.2} - _, err := engine.Id(1).Update(&user) + cnt, err := engine.Id(1).Update(&user) if err != nil { t.Error(err) panic(err) } + if cnt != 1 { + err = errors.New("update not returned 1") + t.Error(err) + panic(err) + return + } condi := Condi{"username": "zzz", "height": 0.0, "departname": ""} - _, err = engine.Table(&user).Id(1).Update(&condi) + cnt, err = engine.Table(&user).Id(1).Update(&condi) + if err != nil { + t.Error(err) + panic(err) + } + if cnt != 1 { + err = errors.New("update not returned 1") + t.Error(err) + panic(err) + return + } + + cnt, err = engine.Update(&Userinfo{Username: "yyy"}, &user) + if err != nil { + t.Error(err) + panic(err) + } + total, err := engine.Count(&user) if err != nil { t.Error(err) panic(err) } - _, err = engine.Update(&Userinfo{Username: "yyy"}, &user) - if err != nil { + if cnt != total { + err = errors.New("insert not returned 1") t.Error(err) panic(err) + return } } func updateSameMapper(engine *Engine, t *testing.T) { // update by id user := Userinfo{Username: "xxx", Height: 1.2} - _, err := engine.Id(1).Update(&user) + cnt, err := engine.Id(1).Update(&user) if err != nil { t.Error(err) panic(err) } + if cnt != 1 { + err = errors.New("update not returned 1") + t.Error(err) + panic(err) + return + } condi := Condi{"Username": "zzz", "Height": 0.0, "Departname": ""} - _, err = engine.Table(&user).Id(1).Update(&condi) + cnt, err = engine.Table(&user).Id(1).Update(&condi) if err != nil { t.Error(err) panic(err) } - _, err = engine.Update(&Userinfo{Username: "yyy"}, &user) + if cnt != 1 { + err = errors.New("insert not returned 1") + t.Error(err) + panic(err) + return + } + + cnt, err = engine.Update(&Userinfo{Username: "yyy"}, &user) if err != nil { t.Error(err) panic(err) } + + if cnt != 1 { + err = errors.New("insert not returned 1") + t.Error(err) + panic(err) + return + } } func testdelete(engine *Engine, t *testing.T) { user := Userinfo{Uid: 1} - _, err := engine.Delete(&user) + cnt, err := engine.Delete(&user) if err != nil { t.Error(err) panic(err) } + if cnt != 1 { + err = errors.New("delete not returned 1") + t.Error(err) + panic(err) + return + } user.Uid = 0 has, err := engine.Id(3).Get(&user) @@ -599,6 +676,7 @@ func transaction(engine *Engine, t *testing.T) { t.Error(err) panic(err) } + user2 := Userinfo{Username: "yyy"} _, err = session.Where("(id) = ?", 0).Update(&user2) if err != nil { @@ -772,17 +850,29 @@ func createMultiTables(engine *Engine, t *testing.T) { func tableOp(engine *Engine, t *testing.T) { user := Userinfo{Username: "tablexiao", Departname: "dev", Alias: "lunny", Created: time.Now()} tableName := fmt.Sprintf("user_%v", len(user.Username)) - id, err := engine.Table(tableName).Insert(&user) + cnt, err := engine.Table(tableName).Insert(&user) if err != nil { t.Error(err) panic(err) } + if cnt != 1 { + err = errors.New("insert not returned 1") + t.Error(err) + panic(err) + return + } - _, err = engine.Table(tableName).Get(&Userinfo{Username: "tablexiao"}) + has, err := engine.Table(tableName).Get(&Userinfo{Username: "tablexiao"}) if err != nil { t.Error(err) panic(err) } + if !has { + err = errors.New("Get has return false") + t.Error(err) + panic(err) + return + } users := make([]Userinfo, 0) err = engine.Table(tableName).Find(&users) @@ -791,7 +881,8 @@ func tableOp(engine *Engine, t *testing.T) { panic(err) } - _, err = engine.Table(tableName).Id(id).Update(&Userinfo{Username: "tableda"}) + id := user.Uid + cnt, err = engine.Table(tableName).Id(id).Update(&Userinfo{Username: "tableda"}) if err != nil { t.Error(err) panic(err) @@ -1027,11 +1118,16 @@ func testColTypes(engine *Engine, t *testing.T) { 21, } - _, err = engine.Insert(ac) + cnt, err := engine.Insert(ac) if err != nil { t.Error(err) panic(err) } + if cnt != 1 { + err = errors.New("insert return not 1") + t.Error(err) + panic(err) + } newAc := &allCols{} has, err := engine.Get(newAc) if err != nil { @@ -1048,7 +1144,7 @@ func testColTypes(engine *Engine, t *testing.T) { newAc.Real = 0 newAc.Float = 0 newAc.Double = 0 - cnt, err := engine.Delete(newAc) + cnt, err = engine.Delete(newAc) if err != nil { t.Error(err) panic(err) @@ -1118,12 +1214,18 @@ func testCustomType(engine *Engine, t *testing.T) { i.UIA8 = []uint8{1, 2, 3, 4} i.NameArray = []string{"ssss", "fsdf", "lllll, ss"} i.MSS = map[string]string{"s": "sfds,ss", "x": "lfjljsl"} - _, err = engine.Insert(&i) + cnt, err := engine.Insert(&i) if err != nil { t.Error(err) panic(err) return } + if cnt != 1 { + err = errors.New("insert not returned 1") + t.Error(err) + panic(err) + return + } fmt.Println(i) has, err := engine.Get(&i) @@ -1186,28 +1288,46 @@ func testCreatedAndUpdated(engine *Engine, t *testing.T) { } u.Name = "sss" - _, err = engine.Insert(u) + cnt, err := engine.Insert(u) if err != nil { t.Error(err) panic(err) } + if cnt != 1 { + err = errors.New("insert not returned 1") + t.Error(err) + panic(err) + return + } u.Name = "xxx" - _, err = engine.Id(u.Id).Update(u) + cnt, err = engine.Id(u.Id).Update(u) if err != nil { t.Error(err) panic(err) } + if cnt != 1 { + err = errors.New("update not returned 1") + t.Error(err) + panic(err) + return + } u.Id = 0 u.Created = time.Now().Add(-time.Hour * 24 * 365) u.Updated = u.Created fmt.Println(u) - _, err = engine.NoAutoTime().Insert(u) + cnt, err = engine.NoAutoTime().Insert(u) if err != nil { t.Error(err) panic(err) } + if cnt != 1 { + err = errors.New("insert not returned 1") + t.Error(err) + panic(err) + return + } } type IndexOrUnique struct { diff --git a/docs/QuickStart.md b/docs/QuickStart.md index 03b6a256..ab8ae3b6 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -6,8 +6,11 @@ xorm 快速入门 * [2.1.名称映射规则](#21) * [2.2.使用Table和Tag改变名称映射](#22) * [2.3.Column属性定义](#23) -* [3.创建表](#30) - * [3.1.同步数据库结构](#31) +* [3.表结构操作](#30) + * [3.1 获取数据库信息](#31) + * [3.2 获取数据库信息](#32) + * [3.3 获取数据库信息](#33) + * [3.4 获取数据库信息](#34) * [4.删除表](#40) * [5.插入数据](#50) * [6.查询和统计数据](#60) @@ -25,7 +28,7 @@ xorm 快速入门 * [12.缓存](#120) * [13.Examples](#130) * [14.案例](#140) -* [15.FAQ](#150) +* [15.那些年我们踩过的坑](#150) * [16.讨论](#160) @@ -71,7 +74,14 @@ NewEngine传入的参数和`sql.Open`传入的参数完全相同,因此,使 在engine创建完成后可以进行一些设置,如: -1.设置`engine.ShowSQL = true`,则会在控制台打印出生成的SQL语句;如果希望用其它方式记录,则可以`engine.Logger`赋值为一个`io.Writer`的实现。比如记录到Log文件,则可以: +1.设置 + +* `engine.ShowSQL = true`,则会在控制台打印出生成的SQL语句; +* `engine.ShowDebug = true`,则会在控制台打印调试信息; +* `engine.ShowError = true`,则会在控制台打印错误信息; +* `engine.ShowWarn = true`,则会在控制台打印警告信息; + +2.如果希望用其它方式记录,则可以`engine.Logger`赋值为一个`io.Writer`的实现。比如记录到Log文件,则可以: ```Go f, err := os.Create("sql.log") @@ -82,10 +92,11 @@ f, err := os.Create("sql.log") engine.Logger = f ``` -2.engine内部支持连接池接口,默认使用的Go所实现的连接池,同时自带了另外两种实现,一种是不使用连接池,另一种为一个自实现的连接池。推荐使用Go所实现的连接池。如果要使用自己实现的连接池,可以实现`xorm.IConnectPool`并通过`engine.SetPool`进行设置。 -如果需要设置连接池的空闲数大小,可以使用`engine.Pool.SetIdleConns()`来实现。 +3.engine内部支持连接池接口,默认使用的Go所实现的连接池,同时自带了另外两种实现:一种是不使用连接池,另一种为一个自实现的连接池。推荐使用Go所实现的连接池。如果要使用自己实现的连接池,可以实现`xorm.IConnectPool`并通过`engine.SetPool`进行设置。 + +* 如果需要设置连接池的空闲数大小,可以使用`engine.SetIdleConns()`来实现。 +* 如果需要设置最大打开连接数,则可以使用`engine.SetMaxConns()`来实现。 -3.设置`engine.ShowDebug = true`,则会在控制台打印调试信息。 ## 2.定义表结构体 @@ -95,7 +106,9 @@ xorm支持将一个struct映射为数据库中对应的一张表。映射规则 ### 2.1.名称映射规则 -名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理,xorm内置了两种IMapper实现:`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名,表结构为下划线命名之间的转换;SameMapper支持相同的命名。当前SnakeMapper为默认值,当需要改变时,在engine创建完成后使用 +名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理,xorm内置了两种IMapper实现:`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名,表结构为下划线命名之间的转换;SameMapper支持相同的命名。 + +当前SnakeMapper为默认值,如果需要改变时,在engine创建完成后使用 ```Go engine.Mapper = SameMapper{} @@ -176,7 +189,7 @@ type User struct { - 2.string类型默认映射为varchar(255),如果需要不同的定义,可以在tag中自定义 -- 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Jsong格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都已二进制方式存储。 +- 3.支持`type MyString string`等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型,如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都以二进制方式存储。 - 4.实现了Conversion接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换。 ```Go @@ -187,39 +200,64 @@ type Conversion interface { ``` -## 3.创建表 +## 3.表结构操作 +xorm提供了一些动态获取和修改表结构的方法。对于一般的应用,很少动态修改表结构,则只需调用Sync()同步下表结构即可。 + + +## 3.1 获取数据库信息 + +* DBMetas() +xorm支持获取表结构信息,通过调用`engine.DBMetas()`可以获取到所有的表的信息 + + +## 3.2.表操作 + +* CreateTables() 创建表使用`engine.CreateTables()`,参数为一个或多个空的对应Struct的指针。同时可用的方法有Charset()和StoreEngine(),如果对应的数据库支持,这两个方法可以在创建表时指定表的字符编码和使用的引擎。当前仅支持Mysql数据库。 -在创建表时会判断表是否已经创建,如果已经创建则不再创建。在创建表的过程中,如果在tag中定义了索引,则索引也会自动创建。 +* IsTableEmpty() +判断表是否为空,参数和CreateTables相同 - -## 3.1.同步数据库结构 -同步表能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现: -1) 自动检测和创建表 -2)自动检测和新增表中的字段 -3)自动检测和创建索引和唯一索引 +* IsTableExist() +判断表是否存在 + +* DropTables() +删除表使用`engine.DropTables()`,参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。 + + +## 3.3.创建索引和唯一索引 + +* CreateIndexes +根据struct中的tag来创建索引 + +* CreateUniques +根据struct中的tag来创建唯一索引 + + +## 3.4.同步数据库结构 + +同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前能够实现: +1) 自动检测和创建表,这个检测是根据表的名字 +2)自动检测和新增表中的字段,这个检测是根据字段名 +3)自动检测和创建索引和唯一索引,这个检测是根据一个或多个字段名,而不根据索引名称 调用方法如下: ```Go err := engine.Sync(new(User)) ``` - -## 4.删除表 - -删除表使用`engine.DropTables()`,参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。 - ## 5.插入数据 插入数据使用Insert方法,Insert方法的参数可以是一个或多个Struct的指针,一个或多个Struct的Slice的指针。 如果传入的是Slice并且当数据库支持批量插入时,Insert会使用批量插入的方式进行插入。 +* 插入一条数据 ```Go user := new(User) user.Name = "myname" -affcted, err := engine.Insert(user) +affected, err := engine.Insert(user) ``` 在插入成功后,如果该结构体有PK字段,则PK字段会被自动赋值为数据库中的id @@ -227,6 +265,45 @@ affcted, err := engine.Insert(user) fmt.Println(user.Id) ``` +* 插入同一个表的多条数据 +```Go +users := make([]User, 0) +users[0].Name = "name0" +... +affected, err := engine.Insert(&users) +``` + +* 插入不同表的一条记录 +```Go +user := new(User) +user.Name = "myname" +question := new(Question) +question.Content = "whywhywhwy?" +affected, err := engine.Insert(user, question) +``` + +* 插入不同表的多条记录 +```Go +users := make([]User, 0) +users[0].Name = "name0" +... +questions := make([]Question, 0) +questions[0].Content = "whywhywhwy?" +affected, err := engine.Insert(&users, &questions) +``` + +* 插入不同表的一条或多条记录 +```Go +user := new(User) +user.Name = "myname" +... +questions := make([]Question, 0) +questions[0].Content = "whywhywhwy?" +affected, err := engine.Insert(user, &questions) +``` + +注意:这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。 + ## 6.查询和统计数据 @@ -243,44 +320,62 @@ fmt.Println(user.Id) * Where(string, …interface{}) 和Where语句中的条件基本相同,作为条件 -* Cols(…string) -只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如: -```Go -engine.Cols("age, name").Update(&user) -``` +* And(string, …interface{}) +和Where函数中的条件基本相同,作为条件 -or +* Or(string, …interface{}) +和Where函数中的条件基本相同,作为条件 -```Go -engine.Cols("age", "name").Update(&user) -``` * Sql(string, …interface{}) 执行指定的Sql语句,并把结果映射到结构体 -* Table() -指定特殊的Table名,如不加此函数,则根据系统的IMapper自动映射的表名进行查询 - * Asc(…string) 指定字段名正序排序 * Desc(…string) 指定字段名逆序排序 -* OrderBy() +* OrderBy(string) 按照指定的顺序进行排序 -* NoAutoTime() -如果此方法执行,则此次生成的语句中Created和Updated字段将不自动赋值为当前时间 - * In(string, …interface{}) 某字段在一些值中 +* Cols(…string) +只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如: +```Go +engine.Cols("age", "name").Find(&users) +// SELECT age, name FROM user +engine.Cols("age", "name").Update(&user) +// UPDATE user SET age=? AND name=? +``` + +其中的参数"age", "name"也可以写成"age, name",两种写法均可 + +* Omit(...string) +和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用 +```Go +engine.Cols("age").Update(&user) +// UPDATE user SET name = ? AND department = ? +``` + +* Distinct(…string) +按照参数中指定的字段归类结果 +```Go +engine.Distinct("age", "department").Find(&users) +// SELECT DISTINCT age, department FROM user +``` +注意:当开启了缓存时,此方法的调用将在当前查询中禁用缓存。因为缓存系统当前依赖Id,而此时无法获得Id + * Table(nameOrStructPtr interface{}) 传入表名称或者结构体指针,如果传入的是结构体指针,则按照IMapper的规则提取出表名 * Limit(int, …int) 限制获取的数目,第一个参数为条数,第二个参数为可选,表示开始位置 +* Top(int) +相当于Limit(int, 0) + * Join(string,string,string) 第一个参数为连接类型,当前支持INNER, LEFT OUTER, CROSS中的一个值,第二个参数为表名,第三个参数为连接条件 @@ -290,11 +385,20 @@ Groupby的参数字符串 * Having(string) Having的参数字符串 + +### 6.2.临时开关方法 + +* NoAutoTime() +如果此方法执行,则此次生成的语句中Created和Updated字段将不自动赋值为当前时间 + +* UseBool(...string) +当从一个struct来生成查询条件或更新字段时,xorm会判断struct的field是否为0,"",nil,如果为以上则不当做查询条件或者更新内容。因为bool类型只有true和false两种值,因此默认所有bool类型不会作为查询条件或者更新字段。如果可以使用此方法,如果默认不传参数,则所有的bool字段都将会被使用,如果参数不为空,则参数中指定的为字段名,则这些字段对应的bool值将被使用。 + * Cascade(bool) 是否自动关联查询field中的数据,如果struct的field也是一个struct并且映射为某个Id,则可以在查询时自动调用Get方法查询出对应的数据。 - -### 6.2.Get方法 + +### 6.3.Get方法 查询单条数据使用`Get`方法,在调用Get方法时需要传入一个对应结构体的指针,同时结构体中的非空field自动成为查询的条件和前面的方法条件组合在一起查询。 @@ -324,8 +428,8 @@ has, err := engine.Get(user) 返回的结果为两个参数,一个`has`为该条记录是否存在,第二个参数`err`为是否有错误。不管err是否为nil,has都有可能为true或者false。 - -### 6.3.Find方法 + +### 6.4.Find方法 查询多条数据使用`Find`方法,Find方法的第一个参数为`slice`的指针或`Map`指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。 @@ -346,8 +450,8 @@ users := make([]Userinfo, 0) err := engine.Where("age > ? or name=?)", 30, "xlw").Limit(20, 10).Find(&users) ``` - -### 6.4.Iterate方法 + +### 6.5.Iterate方法 Iterate方法提供逐条执行查询到的记录的方法,他所能使用的条件和Find方法完全相同 ```Go @@ -357,8 +461,8 @@ err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func }) ``` - -### 6.5.Count方法 + +### 6.6.Count方法 统计数据使用`Count`方法,Count方法的参数为struct的指针并且成为查询条件。 ```Go @@ -366,8 +470,8 @@ user := new(User) total, err := engine.Where("id >?", 1).Count(user) ``` - -### 6.5.匿名结构体成员 + +### 6.7.匿名结构体成员 如果在struct中拥有一个struct,并且在Tag中标记为extends,那么该结构体的成员将作为本结构体的成员进行映射。 @@ -407,6 +511,8 @@ affected, err := engine.Id(id).Delete(user) `Delete`的返回值第一个参数为删除的记录数,第二个参数为错误。 +注意:当删除时,如果user中包含有bool,float64或者float32类型,有可能会使删除失败。具体请查看 FAQ + ## 9.执行SQL查询 @@ -430,6 +536,7 @@ res, err := engine.Exec(sql, "xiaolun", 1) 当使用事务处理时,需要创建Session对象。 ```Go +session := engine.NewSession() // add Begin() before any action err := session.Begin() user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} @@ -494,6 +601,8 @@ engine.Exec("update user set name = ? where id = ?", "xlw", 1) engine.ClearCache(new(User)) ``` +ClearCacheBean + 缓存的实现原理如下图所示: ![cache design](https://raw.github.com/lunny/xorm/master/docs/cache_design.png) @@ -515,5 +624,34 @@ engine.ClearCache(new(User)) * [VeryHour](http://veryhour.com) -## 15.讨论 +## 15.那些年我们踩过的坑 +1. 怎么同时使用xorm的tag和json的tag? + +答:使用空格 + +```Go +type User struct { + Name string `json:"name" xorm:"name"` +} +``` + +2. 我的struct里面包含bool类型,为什么它不能作为条件也没法用Update更新? + +答:默认bool类型因为无法判断是否为空,所以不会自动作为条件也不会作为Update的内容。可以使用UseBool函数,也可以使用Cols函数 +```Go +engine.Cols("bool_field").Update(&Struct{BoolField:true}) +// UPDATE struct SET bool_field = true +``` + +3. 我的struct里面包含float64和float32类型,为什么用他们作为查询条件总是不正确? + +答:默认float32和float64映射到数据库中为float,real,double这几种类型,这几种数据库类型数据库的实现一般都是非精确的。因此作为相等条件查询有可能不会返回正确的结果。如果一定要作为查询条件,请将数据库中的类型定义为Numeric或者Decimal。 +```Go +type account struct { +money float64 `xorm:"Numeric"` +} +``` + + +## 16.FAQ 请加入QQ群:280360085 进行讨论。 diff --git a/engine.go b/engine.go index 117e1ca9..8d479edb 100644 --- a/engine.go +++ b/engine.go @@ -96,6 +96,11 @@ func (engine *Engine) SetMaxConns(conns int) { engine.Pool.SetMaxConns(conns) } +// SetMaxIdleConns +func (engine *Engine) SetMaxIdleConns(conns int) { + engine.Pool.SetMaxIdleConns(conns) +} + // SetDefaltCacher set the default cacher. Xorm's default not enable cacher. func (engine *Engine) SetDefaultCacher(cacher Cacher) { if cacher == nil { diff --git a/session.go b/session.go index 809b769d..4622e7b0 100644 --- a/session.go +++ b/session.go @@ -530,8 +530,12 @@ func (session *Session) cacheGet(bean interface{}, sql string, args ...interface newSession := session.Engine.NewSession() defer newSession.Close() cacheBean = reflect.New(structValue.Type()).Interface() - has, err = newSession.Id(id).NoCache().Get(cacheBean) - if err != nil { + if session.Statement.AltTableName != "" { + has, err = newSession.Id(id).NoCache().Table(session.Statement.AltTableName).Get(cacheBean) + } else { + has, err = newSession.Id(id).NoCache().Get(cacheBean) + } + if err != nil || !has { return has, err } @@ -1251,7 +1255,7 @@ func (session *Session) Query(sql string, paramStr ...interface{}) (resultsSlice // insert one or more beans func (session *Session) Insert(beans ...interface{}) (int64, error) { - var lastId int64 = -1 + var affected int64 = 0 var err error = nil err = session.newDb() if err != nil { @@ -1266,28 +1270,31 @@ func (session *Session) Insert(beans ...interface{}) (int64, error) { sliceValue := reflect.Indirect(reflect.ValueOf(bean)) if sliceValue.Kind() == reflect.Slice { if session.Engine.SupportInsertMany() { - lastId, err = session.innerInsertMulti(bean) + cnt, err := session.innerInsertMulti(bean) if err != nil { - return lastId, err + return affected, err } + affected += cnt } else { size := sliceValue.Len() for i := 0; i < size; i++ { - lastId, err = session.innerInsert(sliceValue.Index(i).Interface()) + cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) if err != nil { - return lastId, err + return affected, err } + affected += cnt } } } else { - lastId, err = session.innerInsert(bean) + cnt, err := session.innerInsert(bean) if err != nil { - return lastId, err + return affected, err } + affected += cnt } } - return lastId, err + return affected, err } func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error) { @@ -1380,50 +1387,16 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error session.Engine.QuoteStr(), strings.Join(colMultiPlaces, "),(")) - if session.Engine.DriverName != POSTGRES || table.PrimaryKey == "" { - res, err := session.exec(statement, args...) - if err != nil { - return 0, err - } - - if table.Cacher != nil && session.Statement.UseCache { - session.cacheInsert(session.Statement.TableName()) - } - - if table.PrimaryKey != "" { - id, err := res.LastInsertId() - if err != nil { - return 0, err - } - - return id, nil - } else { - return 0, err - } - } else { - statement += " RETURNING (id)" - - res, err := session.query(statement, args...) - if err != nil { - return 0, err - } - - if len(res) < 1 { - return 0, err - } - - if table.Cacher != nil && session.Statement.UseCache { - session.cacheInsert(session.Statement.TableName()) - } - - idByte := res[0][table.PrimaryKey] - id, err := strconv.ParseInt(string(idByte), 10, 64) - if err != nil { - return 0, err - } - - return id, nil + res, err := session.exec(statement, args...) + if err != nil { + return 0, err } + + if table.Cacher != nil && session.Statement.UseCache { + session.cacheInsert(session.Statement.TableName()) + } + + return res.RowsAffected() } func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) { @@ -1716,19 +1689,18 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } if table.PrimaryKey == "" { - return 0, nil + return res.RowsAffected() } var id int64 = 0 - id, err = res.LastInsertId() if err != nil || id <= 0 { - return 0, err + return res.RowsAffected() } pkValue := table.PKColumn().ValueOf(bean) if !pkValue.IsValid() || pkValue.Int() != 0 || !pkValue.CanSet() { - return id, nil + return res.RowsAffected() } var v interface{} = id @@ -1740,7 +1712,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } pkValue.Set(reflect.ValueOf(v)) - return id, nil + return res.RowsAffected() } else { sql = sql + " RETURNING (id)" res, err := session.query(sql, args...) @@ -1759,12 +1731,12 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { idByte := res[0][table.PrimaryKey] id, err := strconv.ParseInt(string(idByte), 10, 64) if err != nil { - return 0, err + return 1, err } pkValue := table.PKColumn().ValueOf(bean) if !pkValue.IsValid() || pkValue.Int() != 0 || !pkValue.CanSet() { - return id, nil + return 1, nil } var v interface{} = id @@ -1776,7 +1748,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } pkValue.Set(reflect.ValueOf(v)) - return id, nil + return 1, nil } }