mirror of
https://gitee.com/johng/gf.git
synced 2024-11-30 03:07:45 +08:00
improve cache feature for package gdb
This commit is contained in:
parent
02e1d01f29
commit
e0a0fcbde2
@ -9,10 +9,8 @@ package gdb
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
@ -45,8 +43,7 @@ type Model struct {
|
||||
distinct string // Force the query to only return distinct results.
|
||||
lockInfo string // Lock for update or in shared lock.
|
||||
cacheEnabled bool // Enable sql result cache feature.
|
||||
cacheDuration time.Duration // Cache TTL duration (< 1 for removing cache, >= 0 for saving cache).
|
||||
cacheName string // Cache name for custom operation.
|
||||
cacheOption CacheOption // Cache option for query statement.
|
||||
unscoped bool // Disables soft deleting features when select/delete operations.
|
||||
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
|
||||
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
|
||||
|
@ -11,26 +11,32 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type CacheOption struct {
|
||||
// Duration is the TTL for the cache.
|
||||
// If the parameter `Duration` < 0, which means it clear the cache with given `Name`.
|
||||
// If the parameter `Duration` = 0, which means it never expires.
|
||||
// If the parameter `Duration` > 0, which means it expires after `Duration`.
|
||||
Duration time.Duration
|
||||
|
||||
// Name is an optional unique name for the cache.
|
||||
// The Name is used to bind a name to the cache, which means you can later control the cache
|
||||
// like changing the `duration` or clearing the cache with specified Name.
|
||||
Name string
|
||||
|
||||
// Force caches the query result whatever the result is nil or not.
|
||||
// It is used to avoid Cache Penetration.
|
||||
Force bool
|
||||
}
|
||||
|
||||
// Cache sets the cache feature for the model. It caches the result of the sql, which means
|
||||
// if there's another same sql request, it just reads and returns the result from cache, it
|
||||
// but not committed and executed into the database.
|
||||
//
|
||||
// If the parameter `duration` < 0, which means it clear the cache with given `name`.
|
||||
// If the parameter `duration` = 0, which means it never expires.
|
||||
// If the parameter `duration` > 0, which means it expires after `duration`.
|
||||
//
|
||||
// The optional parameter `name` is used to bind a name to the cache, which means you can
|
||||
// later control the cache like changing the `duration` or clearing the cache with specified
|
||||
// `name`.
|
||||
//
|
||||
// Note that, the cache feature is disabled if the model is performing select statement
|
||||
// on a transaction.
|
||||
func (m *Model) Cache(duration time.Duration, name ...string) *Model {
|
||||
func (m *Model) Cache(option CacheOption) *Model {
|
||||
model := m.getModel()
|
||||
model.cacheDuration = duration
|
||||
if len(name) > 0 {
|
||||
model.cacheName = name[0]
|
||||
}
|
||||
model.cacheOption = option
|
||||
model.cacheEnabled = true
|
||||
return model
|
||||
}
|
||||
@ -38,9 +44,9 @@ func (m *Model) Cache(duration time.Duration, name ...string) *Model {
|
||||
// checkAndRemoveCache checks and removes the cache in insert/update/delete statement if
|
||||
// cache feature is enabled.
|
||||
func (m *Model) checkAndRemoveCache() {
|
||||
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
|
||||
if m.cacheEnabled && m.cacheOption.Duration < 0 && len(m.cacheOption.Name) > 0 {
|
||||
var ctx = m.GetCtx()
|
||||
_, err := m.db.GetCache().Remove(ctx, m.cacheName)
|
||||
_, err := m.db.GetCache().Remove(ctx, m.cacheOption.Name)
|
||||
if err != nil {
|
||||
intlog.Error(ctx, err)
|
||||
}
|
||||
|
@ -437,7 +437,7 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
|
||||
)
|
||||
// Retrieve from cache.
|
||||
if m.cacheEnabled && m.tx == nil {
|
||||
cacheKey = m.cacheName
|
||||
cacheKey = m.cacheOption.Name
|
||||
if len(cacheKey) == 0 {
|
||||
cacheKey = sql + ", @PARAMS:" + gconv.String(args)
|
||||
}
|
||||
@ -461,16 +461,16 @@ func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, e
|
||||
)
|
||||
// Cache the result.
|
||||
if cacheKey != "" && err == nil {
|
||||
if m.cacheDuration < 0 {
|
||||
if m.cacheOption.Duration < 0 {
|
||||
if _, err := cacheObj.Remove(ctx, cacheKey); err != nil {
|
||||
intlog.Error(m.GetCtx(), err)
|
||||
}
|
||||
} else {
|
||||
// In case of Cache Penetration.
|
||||
if result == nil {
|
||||
if result.IsEmpty() && m.cacheOption.Force {
|
||||
result = Result{}
|
||||
}
|
||||
if err := cacheObj.Set(ctx, cacheKey, result, m.cacheDuration); err != nil {
|
||||
if err := cacheObj.Set(ctx, cacheKey, result, m.cacheOption.Duration); err != nil {
|
||||
intlog.Error(m.GetCtx(), err)
|
||||
}
|
||||
}
|
||||
|
@ -2479,7 +2479,11 @@ func Test_Model_Cache(t *testing.T) {
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Model(table).Cache(time.Second, "test1").WherePri(1).One()
|
||||
one, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test1",
|
||||
Force: false,
|
||||
}).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_1")
|
||||
|
||||
@ -2489,63 +2493,107 @@ func Test_Model_Cache(t *testing.T) {
|
||||
t.AssertNil(err)
|
||||
t.Assert(n, 1)
|
||||
|
||||
one, err = db.Model(table).Cache(time.Second, "test1").WherePri(1).One()
|
||||
one, err = db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test1",
|
||||
Force: false,
|
||||
}).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_1")
|
||||
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
one, err = db.Model(table).Cache(time.Second, "test1").WherePri(1).One()
|
||||
one, err = db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test1",
|
||||
Force: false,
|
||||
}).WherePri(1).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_100")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Model(table).Cache(time.Second, "test2").WherePri(2).One()
|
||||
one, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test2",
|
||||
Force: false,
|
||||
}).WherePri(2).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_2")
|
||||
|
||||
r, err := db.Model(table).Data("passport", "user_200").Cache(-1, "test2").WherePri(2).Update()
|
||||
r, err := db.Model(table).Data("passport", "user_200").Cache(gdb.CacheOption{
|
||||
Duration: -1,
|
||||
Name: "test2",
|
||||
Force: false,
|
||||
}).WherePri(2).Update()
|
||||
t.AssertNil(err)
|
||||
n, err := r.RowsAffected()
|
||||
t.AssertNil(err)
|
||||
t.Assert(n, 1)
|
||||
|
||||
one, err = db.Model(table).Cache(time.Second, "test2").WherePri(2).One()
|
||||
one, err = db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test2",
|
||||
Force: false,
|
||||
}).WherePri(2).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_200")
|
||||
})
|
||||
// transaction.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// make cache for id 3
|
||||
one, err := db.Model(table).Cache(time.Second, "test3").WherePri(3).One()
|
||||
one, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test3",
|
||||
Force: false,
|
||||
}).WherePri(3).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_3")
|
||||
|
||||
r, err := db.Model(table).Data("passport", "user_300").Cache(time.Second, "test3").WherePri(3).Update()
|
||||
r, err := db.Model(table).Data("passport", "user_300").Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test3",
|
||||
Force: false,
|
||||
}).WherePri(3).Update()
|
||||
t.AssertNil(err)
|
||||
n, err := r.RowsAffected()
|
||||
t.AssertNil(err)
|
||||
t.Assert(n, 1)
|
||||
|
||||
err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
|
||||
one, err := tx.Model(table).Cache(time.Second, "test3").WherePri(3).One()
|
||||
one, err := tx.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test3",
|
||||
Force: false,
|
||||
}).WherePri(3).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_300")
|
||||
return nil
|
||||
})
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err = db.Model(table).Cache(time.Second, "test3").WherePri(3).One()
|
||||
one, err = db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test3",
|
||||
Force: false,
|
||||
}).WherePri(3).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_3")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
// make cache for id 4
|
||||
one, err := db.Model(table).Cache(time.Second, "test4").WherePri(4).One()
|
||||
one, err := db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test4",
|
||||
Force: false,
|
||||
}).WherePri(4).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_4")
|
||||
|
||||
r, err := db.Model(table).Data("passport", "user_400").Cache(time.Second, "test3").WherePri(4).Update()
|
||||
r, err := db.Model(table).Data("passport", "user_400").Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test3",
|
||||
Force: false,
|
||||
}).WherePri(4).Update()
|
||||
t.AssertNil(err)
|
||||
n, err := r.RowsAffected()
|
||||
t.AssertNil(err)
|
||||
@ -2553,12 +2601,20 @@ func Test_Model_Cache(t *testing.T) {
|
||||
|
||||
err = db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
|
||||
// Cache feature disabled.
|
||||
one, err := tx.Model(table).Cache(time.Second, "test4").WherePri(4).One()
|
||||
one, err := tx.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test4",
|
||||
Force: false,
|
||||
}).WherePri(4).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_400")
|
||||
// Update the cache.
|
||||
r, err := tx.Model(table).Data("passport", "user_4000").
|
||||
Cache(-1, "test4").WherePri(4).Update()
|
||||
Cache(gdb.CacheOption{
|
||||
Duration: -1,
|
||||
Name: "test4",
|
||||
Force: false,
|
||||
}).WherePri(4).Update()
|
||||
t.AssertNil(err)
|
||||
n, err := r.RowsAffected()
|
||||
t.AssertNil(err)
|
||||
@ -2567,7 +2623,11 @@ func Test_Model_Cache(t *testing.T) {
|
||||
})
|
||||
t.AssertNil(err)
|
||||
// Read from db.
|
||||
one, err = db.Model(table).Cache(time.Second, "test4").WherePri(4).One()
|
||||
one, err = db.Model(table).Cache(gdb.CacheOption{
|
||||
Duration: time.Second,
|
||||
Name: "test4",
|
||||
Force: false,
|
||||
}).WherePri(4).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["passport"], "user_4000")
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user