improve cache feature for package gdb

This commit is contained in:
John Guo 2021-11-09 16:06:31 +08:00
parent 02e1d01f29
commit e0a0fcbde2
4 changed files with 102 additions and 39 deletions

View File

@ -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.

View File

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

View File

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

View File

@ -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")
})