diff --git a/README.md b/README.md index 909dd06b..6177c0a7 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Xorm is a simple and powerful ORM for Go. * Postgres schema support -* Context Get Cache +* Context Cache support ## Drivers Support @@ -360,14 +360,37 @@ if _, err := session.Exec("delete from userinfo where username = ?", user2.Usern return session.Commit() ``` +* Or you can use `Transaction` to replace above codes. + +```Go +res, err := engine.Transaction(func(sess *xorm.Session) (interface{}, error) { + user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} + if _, err := session.Insert(&user1); err != nil { + return nil, err + } + + user2 := Userinfo{Username: "yyy"} + if _, err := session.Where("id = ?", 2).Update(&user2); err != nil { + return nil, err + } + + if _, err := session.Exec("delete from userinfo where username = ?", user2.Username); err != nil { + return nil, err + } + return nil, nil +}) +``` + * Context Cache, if enabled, current query result will be cached on session and be used by next same statement on the same session. ```Go sess := engine.NewSession() defer sess.Close() + var context = xorm.NewContextCache() + var c2 ContextGetStruct - has, err := sess.ID(1).ContextCache().Get(&c2) + has, err := sess.ID(1).ContextCache(context).Get(&c2) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 1, c2.Id) @@ -377,7 +400,7 @@ return session.Commit() assert.True(t, len(args) > 0) var c3 ContextGetStruct - has, err = sess.ID(1).Get(&c3) + has, err = sess.ID(1).ContextCache(context).Get(&c3) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 1, c3.Id) diff --git a/README_CN.md b/README_CN.md index 5a1a0c7e..f4d70e7d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -32,6 +32,8 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * 内置SQL Builder支持 +* 上下文缓存支持 + ## 驱动支持 目前支持的Go数据库驱动和对应的数据库如下: @@ -360,6 +362,54 @@ if _, err := session.Exec("delete from userinfo where username = ?", user2.Usern return session.Commit() ``` +* 事物的简写方法 + +```Go +res, err := engine.Transaction(func(sess *xorm.Session) (interface{}, error) { + user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} + if _, err := session.Insert(&user1); err != nil { + return nil, err + } + + user2 := Userinfo{Username: "yyy"} + if _, err := session.Where("id = ?", 2).Update(&user2); err != nil { + return nil, err + } + + if _, err := session.Exec("delete from userinfo where username = ?", user2.Username); err != nil { + return nil, err + } + return nil, nil +}) +``` + +* Context Cache, if enabled, current query result will be cached on session and be used by next same statement on the same session. + +```Go + sess := engine.NewSession() + defer sess.Close() + + var c2 ContextGetStruct + has, err := sess.ID(1).ContextCache().Get(&c2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c2.Id) + assert.EqualValues(t, "1", c2.Name) + sql, args := sess.LastSQL() + assert.True(t, len(sql) > 0) + assert.True(t, len(args) > 0) + + var c3 ContextGetStruct + has, err = sess.ID(1).Get(&c3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c3.Id) + assert.EqualValues(t, "1", c3.Name) + sql, args = sess.LastSQL() + assert.True(t, len(sql) == 0) + assert.True(t, len(args) == 0) +``` + ## 贡献 如果您也想为Xorm贡献您的力量,请查看 [CONTRIBUTING](https://github.com/go-xorm/xorm/blob/master/CONTRIBUTING.md)。您也可以加入QQ群 技术帮助和讨论。 diff --git a/context_cache.go b/context_cache.go new file mode 100644 index 00000000..cfe4f17f --- /dev/null +++ b/context_cache.go @@ -0,0 +1,27 @@ +// Copyright 2018 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +// ContextCache is the interface that operates the cache data. +type ContextCache interface { + // Put puts value into cache with key and expire time. + Put(key string, val interface{}) + // Get gets cached value by given key. + Get(key string) interface{} +} + +type memoryContextCache map[string]interface{} + +func NewMemoryContextCache() memoryContextCache { + return make(map[string]interface{}) +} + +func (m memoryContextCache) Put(key string, val interface{}) { + m[key] = val +} + +func (m memoryContextCache) Get(key string) interface{} { + return m[key] +} diff --git a/session.go b/session.go index d4042bfd..b4750891 100644 --- a/session.go +++ b/session.go @@ -5,7 +5,6 @@ package xorm import ( - "context" "database/sql" "encoding/json" "errors" @@ -52,8 +51,7 @@ type Session struct { lastSQL string lastSQLArgs []interface{} - err error - context context.Context + err error } // Clone copy all the session's content and return a new session @@ -84,7 +82,6 @@ func (session *Session) Init() { session.lastSQL = "" session.lastSQLArgs = []interface{}{} - session.context = nil } // Close release the connection from pool @@ -106,8 +103,8 @@ func (session *Session) Close() { } // ContextCache enable context cache or not -func (session *Session) ContextCache() *Session { - session.statement.enableContextCache = true +func (session *Session) ContextCache(context ContextCache) *Session { + session.statement.context = context return session } diff --git a/session_get.go b/session_get.go index 6ade8025..887a0aeb 100644 --- a/session_get.go +++ b/session_get.go @@ -5,7 +5,6 @@ package xorm import ( - "context" "database/sql" "errors" "fmt" @@ -68,9 +67,9 @@ func (session *Session) get(bean interface{}) (bool, error) { } } - var enableContextCache = session.statement.enableContextCache - if session.context != nil { - res := session.context.Value(fmt.Sprintf("%v-%v", sqlStr, args)) + context := session.statement.context + if context != nil { + res := context.Get(fmt.Sprintf("%v-%v", sqlStr, args)) if res != nil { structValue := reflect.Indirect(reflect.ValueOf(bean)) structValue.Set(reflect.Indirect(reflect.ValueOf(res))) @@ -85,11 +84,8 @@ func (session *Session) get(bean interface{}) (bool, error) { return has, err } - if enableContextCache { - if session.context == nil { - session.context = context.Background() - } - session.context = context.WithValue(session.context, fmt.Sprintf("%v-%v", sqlStr, args), bean) + if context != nil { + context.Put(fmt.Sprintf("%v-%v", sqlStr, args), bean) } return true, nil diff --git a/session_get_test.go b/session_get_test.go index 3e7b47b5..39dd427e 100644 --- a/session_get_test.go +++ b/session_get_test.go @@ -335,8 +335,10 @@ func TestContextGet(t *testing.T) { sess := testEngine.NewSession() defer sess.Close() + context := NewMemoryContextCache() + var c2 ContextGetStruct - has, err := sess.ID(1).ContextCache().Get(&c2) + has, err := sess.ID(1).ContextCache(context).Get(&c2) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 1, c2.Id) @@ -346,7 +348,7 @@ func TestContextGet(t *testing.T) { assert.True(t, len(args) > 0) var c3 ContextGetStruct - has, err = sess.ID(1).Get(&c3) + has, err = sess.ID(1).ContextCache(context).Get(&c3) assert.NoError(t, err) assert.True(t, has) assert.EqualValues(t, 1, c3.Id) @@ -355,3 +357,32 @@ func TestContextGet(t *testing.T) { assert.True(t, len(sql) == 0) assert.True(t, len(args) == 0) } + +func TestContextGet2(t *testing.T) { + type ContextGetStruct2 struct { + Id int64 + Name string + } + + assert.NoError(t, prepareEngine()) + assertSync(t, new(ContextGetStruct2)) + + _, err := testEngine.Insert(&ContextGetStruct2{Name: "1"}) + assert.NoError(t, err) + + context := NewMemoryContextCache() + + var c2 ContextGetStruct2 + has, err := testEngine.ID(1).ContextCache(context).Get(&c2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c2.Id) + assert.EqualValues(t, "1", c2.Name) + + var c3 ContextGetStruct2 + has, err = testEngine.ID(1).ContextCache(context).Get(&c3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c3.Id) + assert.EqualValues(t, "1", c3.Name) +} diff --git a/statement.go b/statement.go index 43142994..a7f7010a 100644 --- a/statement.go +++ b/statement.go @@ -19,47 +19,47 @@ import ( // Statement save all the sql info for executing SQL type Statement struct { - RefTable *core.Table - Engine *Engine - Start int - LimitN int - idParam *core.PK - OrderStr string - JoinStr string - joinArgs []interface{} - GroupByStr string - HavingStr string - ColumnStr string - selectStr string - useAllCols bool - OmitStr string - AltTableName string - tableName string - RawSQL string - RawParams []interface{} - UseCascade bool - UseAutoJoin bool - StoreEngine string - Charset string - UseCache bool - UseAutoTime bool - noAutoCondition bool - IsDistinct bool - IsForUpdate bool - TableAlias string - allUseBool bool - checkVersion bool - unscoped bool - columnMap columnMap - omitColumnMap columnMap - mustColumnMap map[string]bool - nullableMap map[string]bool - incrColumns map[string]incrParam - decrColumns map[string]decrParam - exprColumns map[string]exprParam - cond builder.Cond - bufferSize int - enableContextCache bool + RefTable *core.Table + Engine *Engine + Start int + LimitN int + idParam *core.PK + OrderStr string + JoinStr string + joinArgs []interface{} + GroupByStr string + HavingStr string + ColumnStr string + selectStr string + useAllCols bool + OmitStr string + AltTableName string + tableName string + RawSQL string + RawParams []interface{} + UseCascade bool + UseAutoJoin bool + StoreEngine string + Charset string + UseCache bool + UseAutoTime bool + noAutoCondition bool + IsDistinct bool + IsForUpdate bool + TableAlias string + allUseBool bool + checkVersion bool + unscoped bool + columnMap columnMap + omitColumnMap columnMap + mustColumnMap map[string]bool + nullableMap map[string]bool + incrColumns map[string]incrParam + decrColumns map[string]decrParam + exprColumns map[string]exprParam + cond builder.Cond + bufferSize int + context ContextCache } // Init reset all the statement's fields @@ -100,7 +100,7 @@ func (statement *Statement) Init() { statement.exprColumns = make(map[string]exprParam) statement.cond = builder.NewCond() statement.bufferSize = 0 - statement.enableContextCache = false + statement.context = nil } // NoAutoCondition if you do not want convert bean's field as query condition, then use this function