refactor ContextCache

This commit is contained in:
Lunny Xiao 2018-09-23 10:18:23 +08:00
parent a035a9297c
commit 949e8ec5ea
No known key found for this signature in database
GPG Key ID: C3B7C91B632F738A
7 changed files with 186 additions and 62 deletions

View File

@ -34,7 +34,7 @@ Xorm is a simple and powerful ORM for Go.
* Postgres schema support * Postgres schema support
* Context Get Cache * Context Cache support
## Drivers Support ## Drivers Support
@ -360,14 +360,37 @@ if _, err := session.Exec("delete from userinfo where username = ?", user2.Usern
return session.Commit() 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. * Context Cache, if enabled, current query result will be cached on session and be used by next same statement on the same session.
```Go ```Go
sess := engine.NewSession() sess := engine.NewSession()
defer sess.Close() defer sess.Close()
var context = xorm.NewContextCache()
var c2 ContextGetStruct 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.NoError(t, err)
assert.True(t, has) assert.True(t, has)
assert.EqualValues(t, 1, c2.Id) assert.EqualValues(t, 1, c2.Id)
@ -377,7 +400,7 @@ return session.Commit()
assert.True(t, len(args) > 0) assert.True(t, len(args) > 0)
var c3 ContextGetStruct var c3 ContextGetStruct
has, err = sess.ID(1).Get(&c3) has, err = sess.ID(1).ContextCache(context).Get(&c3)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, has) assert.True(t, has)
assert.EqualValues(t, 1, c3.Id) assert.EqualValues(t, 1, c3.Id)

View File

@ -32,6 +32,8 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作
* 内置SQL Builder支持 * 内置SQL Builder支持
* 上下文缓存支持
## 驱动支持 ## 驱动支持
目前支持的Go数据库驱动和对应的数据库如下 目前支持的Go数据库驱动和对应的数据库如下
@ -360,6 +362,54 @@ if _, err := session.Exec("delete from userinfo where username = ?", user2.Usern
return session.Commit() 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群 技术帮助和讨论。 如果您也想为Xorm贡献您的力量请查看 [CONTRIBUTING](https://github.com/go-xorm/xorm/blob/master/CONTRIBUTING.md)。您也可以加入QQ群 技术帮助和讨论。

27
context_cache.go Normal file
View File

@ -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]
}

View File

@ -5,7 +5,6 @@
package xorm package xorm
import ( import (
"context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
@ -52,8 +51,7 @@ type Session struct {
lastSQL string lastSQL string
lastSQLArgs []interface{} lastSQLArgs []interface{}
err error err error
context context.Context
} }
// Clone copy all the session's content and return a new session // Clone copy all the session's content and return a new session
@ -84,7 +82,6 @@ func (session *Session) Init() {
session.lastSQL = "" session.lastSQL = ""
session.lastSQLArgs = []interface{}{} session.lastSQLArgs = []interface{}{}
session.context = nil
} }
// Close release the connection from pool // Close release the connection from pool
@ -106,8 +103,8 @@ func (session *Session) Close() {
} }
// ContextCache enable context cache or not // ContextCache enable context cache or not
func (session *Session) ContextCache() *Session { func (session *Session) ContextCache(context ContextCache) *Session {
session.statement.enableContextCache = true session.statement.context = context
return session return session
} }

View File

@ -5,7 +5,6 @@
package xorm package xorm
import ( import (
"context"
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
@ -68,9 +67,9 @@ func (session *Session) get(bean interface{}) (bool, error) {
} }
} }
var enableContextCache = session.statement.enableContextCache context := session.statement.context
if session.context != nil { if context != nil {
res := session.context.Value(fmt.Sprintf("%v-%v", sqlStr, args)) res := context.Get(fmt.Sprintf("%v-%v", sqlStr, args))
if res != nil { if res != nil {
structValue := reflect.Indirect(reflect.ValueOf(bean)) structValue := reflect.Indirect(reflect.ValueOf(bean))
structValue.Set(reflect.Indirect(reflect.ValueOf(res))) structValue.Set(reflect.Indirect(reflect.ValueOf(res)))
@ -85,11 +84,8 @@ func (session *Session) get(bean interface{}) (bool, error) {
return has, err return has, err
} }
if enableContextCache { if context != nil {
if session.context == nil { context.Put(fmt.Sprintf("%v-%v", sqlStr, args), bean)
session.context = context.Background()
}
session.context = context.WithValue(session.context, fmt.Sprintf("%v-%v", sqlStr, args), bean)
} }
return true, nil return true, nil

View File

@ -335,8 +335,10 @@ func TestContextGet(t *testing.T) {
sess := testEngine.NewSession() sess := testEngine.NewSession()
defer sess.Close() defer sess.Close()
context := NewMemoryContextCache()
var c2 ContextGetStruct 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.NoError(t, err)
assert.True(t, has) assert.True(t, has)
assert.EqualValues(t, 1, c2.Id) assert.EqualValues(t, 1, c2.Id)
@ -346,7 +348,7 @@ func TestContextGet(t *testing.T) {
assert.True(t, len(args) > 0) assert.True(t, len(args) > 0)
var c3 ContextGetStruct var c3 ContextGetStruct
has, err = sess.ID(1).Get(&c3) has, err = sess.ID(1).ContextCache(context).Get(&c3)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, has) assert.True(t, has)
assert.EqualValues(t, 1, c3.Id) 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(sql) == 0)
assert.True(t, len(args) == 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)
}

View File

@ -19,47 +19,47 @@ import (
// Statement save all the sql info for executing SQL // Statement save all the sql info for executing SQL
type Statement struct { type Statement struct {
RefTable *core.Table RefTable *core.Table
Engine *Engine Engine *Engine
Start int Start int
LimitN int LimitN int
idParam *core.PK idParam *core.PK
OrderStr string OrderStr string
JoinStr string JoinStr string
joinArgs []interface{} joinArgs []interface{}
GroupByStr string GroupByStr string
HavingStr string HavingStr string
ColumnStr string ColumnStr string
selectStr string selectStr string
useAllCols bool useAllCols bool
OmitStr string OmitStr string
AltTableName string AltTableName string
tableName string tableName string
RawSQL string RawSQL string
RawParams []interface{} RawParams []interface{}
UseCascade bool UseCascade bool
UseAutoJoin bool UseAutoJoin bool
StoreEngine string StoreEngine string
Charset string Charset string
UseCache bool UseCache bool
UseAutoTime bool UseAutoTime bool
noAutoCondition bool noAutoCondition bool
IsDistinct bool IsDistinct bool
IsForUpdate bool IsForUpdate bool
TableAlias string TableAlias string
allUseBool bool allUseBool bool
checkVersion bool checkVersion bool
unscoped bool unscoped bool
columnMap columnMap columnMap columnMap
omitColumnMap columnMap omitColumnMap columnMap
mustColumnMap map[string]bool mustColumnMap map[string]bool
nullableMap map[string]bool nullableMap map[string]bool
incrColumns map[string]incrParam incrColumns map[string]incrParam
decrColumns map[string]decrParam decrColumns map[string]decrParam
exprColumns map[string]exprParam exprColumns map[string]exprParam
cond builder.Cond cond builder.Cond
bufferSize int bufferSize int
enableContextCache bool context ContextCache
} }
// Init reset all the statement's fields // Init reset all the statement's fields
@ -100,7 +100,7 @@ func (statement *Statement) Init() {
statement.exprColumns = make(map[string]exprParam) statement.exprColumns = make(map[string]exprParam)
statement.cond = builder.NewCond() statement.cond = builder.NewCond()
statement.bufferSize = 0 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 // NoAutoCondition if you do not want convert bean's field as query condition, then use this function