mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
fix(database/gdb): Resolve the cache error overwriting caused by the use of fixed cache keys in pagination queries. (#4339)
```golang
func main() {
adapter := gcache.NewAdapterRedis(g.Redis())
g.DB().GetCache().SetAdapter(adapter)
result, count, err := g.Model("TBL_USER").Cache(gdb.CacheOption{
Duration: 100 * time.Minute,
Name: "VIP",
}).AllAndCount(false)
g.DumpJson(result)
fmt.Println(count, err)
}
```
执行这段查询后`g.DumpJson(result)`的结果是`[
{
"COUNT(1)": 5
}
]`,但是正确结果应该是五条用户信息,查看源代码后发现先执行的count查询和后来select查询都是直接使用了`VIP`这个缓存key,在redis中实际缓存key是`SelectCache:VIP`,第二步查询select获得的是count查询的缓存,所以查询结果是错的。
因此为`Model`增加一个`PageCache`方法允许用户分别设置`count query`和`data query`的缓存参数
```golang
// PageCache sets the cache feature for pagination queries. It allows to configure
// separate cache options for count query and data query in pagination.
//
// Note that, the cache feature is disabled if the model is performing select statement
// on a transaction.
func (m *Model) PageCache(countOption CacheOption, dataOption CacheOption) *Model {
model := m.getModel()
model.pageCacheOption = []CacheOption{countOption, dataOption}
model.cacheEnabled = true
return model
}
```
然后`AllAndCount`在查询时分别给两个查询设置对应的缓存参数`ScanAndCount`同理
```golang
// AllAndCount retrieves all records and the total count of records from the model.
// If useFieldForCount is true, it will use the fields specified in the model for counting;
// otherwise, it will use a constant value of 1 for counting.
// It returns the result as a slice of records, the total count of records, and an error if any.
// The where parameter is an optional list of conditions to use when retrieving records.
//
// Example:
//
// var model Model
// var result Result
// var count int
// where := []any{"name = ?", "John"}
// result, count, err := model.AllAndCount(true)
// if err != nil {
// // Handle error.
// }
// fmt.Println(result, count)
func (m *Model) AllAndCount(useFieldForCount bool) (result Result, totalCount int, err error) {
// Clone the model for counting
countModel := m.Clone()
// If useFieldForCount is false, set the fields to a constant value of 1 for counting
if !useFieldForCount {
countModel.fields = []any{Raw("1")}
}
if len(m.pageCacheOption) > 0 {
countModel = countModel.Cache(m.pageCacheOption[0])
}
// Get the total count of records
totalCount, err = countModel.Count()
if err != nil {
return
}
// If the total count is 0, there are no records to retrieve, so return early
if totalCount == 0 {
return
}
resultModel := m.Clone()
if len(m.pageCacheOption) > 1 {
resultModel = resultModel.Cache(m.pageCacheOption[1])
}
// Retrieve all records
result, err = resultModel.doGetAll(m.GetCtx(), SelectTypeDefault, false)
return
}
```
---------
Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
@ -17,44 +17,45 @@ import (
|
||||
|
||||
// Model is core struct implementing the DAO for ORM.
|
||||
type Model struct {
|
||||
db DB // Underlying DB interface.
|
||||
tx TX // Underlying TX interface.
|
||||
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
|
||||
schema string // Custom database schema.
|
||||
linkType int // Mark for operation on master or slave.
|
||||
tablesInit string // Table names when model initialization.
|
||||
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
|
||||
fields []any // Operation fields, multiple fields joined using char ','.
|
||||
fieldsEx []any // Excluded operation fields, it here uses slice instead of string type for quick filtering.
|
||||
withArray []any // Arguments for With feature.
|
||||
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
|
||||
extraArgs []any // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
|
||||
whereBuilder *WhereBuilder // Condition builder for where operation.
|
||||
groupBy string // Used for "group by" statement.
|
||||
orderBy string // Used for "order by" statement.
|
||||
having []any // Used for "having..." statement.
|
||||
start int // Used for "select ... start, limit ..." statement.
|
||||
limit int // Used for "select ... start, limit ..." statement.
|
||||
option int // Option for extra operation features.
|
||||
offset int // Offset statement for some databases grammar.
|
||||
partition string // Partition table partition name.
|
||||
data any // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
|
||||
batch int // Batch number for batch Insert/Replace/Save operations.
|
||||
filter bool // Filter data and where key-value pairs according to the fields of the table.
|
||||
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, which is mainly for indicating cache duration(especially 0) usage.
|
||||
cacheOption CacheOption // Cache option for query statement.
|
||||
hookHandler HookHandler // Hook functions for model hook feature.
|
||||
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 any // onDuplicate is used for on Upsert clause.
|
||||
onDuplicateEx any // onDuplicateEx is used for excluding some columns on Upsert clause.
|
||||
onConflict any // onConflict is used for conflict keys on Upsert clause.
|
||||
tableAliasMap map[string]string // Table alias to true table name, usually used in join statements.
|
||||
softTimeOption SoftTimeOption // SoftTimeOption is the option to customize soft time feature for Model.
|
||||
shardingConfig ShardingConfig // ShardingConfig for database/table sharding feature.
|
||||
shardingValue any // Sharding value for sharding feature.
|
||||
db DB // Underlying DB interface.
|
||||
tx TX // Underlying TX interface.
|
||||
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
|
||||
schema string // Custom database schema.
|
||||
linkType int // Mark for operation on master or slave.
|
||||
tablesInit string // Table names when model initialization.
|
||||
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
|
||||
fields []any // Operation fields, multiple fields joined using char ','.
|
||||
fieldsEx []any // Excluded operation fields, it here uses slice instead of string type for quick filtering.
|
||||
withArray []any // Arguments for With feature.
|
||||
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
|
||||
extraArgs []any // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
|
||||
whereBuilder *WhereBuilder // Condition builder for where operation.
|
||||
groupBy string // Used for "group by" statement.
|
||||
orderBy string // Used for "order by" statement.
|
||||
having []any // Used for "having..." statement.
|
||||
start int // Used for "select ... start, limit ..." statement.
|
||||
limit int // Used for "select ... start, limit ..." statement.
|
||||
option int // Option for extra operation features.
|
||||
offset int // Offset statement for some databases grammar.
|
||||
partition string // Partition table partition name.
|
||||
data any // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
|
||||
batch int // Batch number for batch Insert/Replace/Save operations.
|
||||
filter bool // Filter data and where key-value pairs according to the fields of the table.
|
||||
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, which is mainly for indicating cache duration(especially 0) usage.
|
||||
cacheOption CacheOption // Cache option for query statement.
|
||||
pageCacheOption []CacheOption // Cache option for paging query statement.
|
||||
hookHandler HookHandler // Hook functions for model hook feature.
|
||||
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 any // onDuplicate is used for on Upsert clause.
|
||||
onDuplicateEx any // onDuplicateEx is used for excluding some columns on Upsert clause.
|
||||
onConflict any // onConflict is used for conflict keys on Upsert clause.
|
||||
tableAliasMap map[string]string // Table alias to true table name, usually used in join statements.
|
||||
softTimeOption SoftTimeOption // SoftTimeOption is the option to customize soft time feature for Model.
|
||||
shardingConfig ShardingConfig // ShardingConfig for database/table sharding feature.
|
||||
shardingValue any // Sharding value for sharding feature.
|
||||
}
|
||||
|
||||
// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
|
||||
|
||||
@ -50,6 +50,18 @@ func (m *Model) Cache(option CacheOption) *Model {
|
||||
return model
|
||||
}
|
||||
|
||||
// PageCache sets the cache feature for pagination queries. It allows to configure
|
||||
// separate cache options for count query and data query in pagination.
|
||||
//
|
||||
// Note that, the cache feature is disabled if the model is performing select statement
|
||||
// on a transaction.
|
||||
func (m *Model) PageCache(countOption CacheOption, dataOption CacheOption) *Model {
|
||||
model := m.getModel()
|
||||
model.pageCacheOption = []CacheOption{countOption, dataOption}
|
||||
model.cacheEnabled = true
|
||||
return model
|
||||
}
|
||||
|
||||
// checkAndRemoveSelectCache checks and removes the cache in insert/update/delete statement if
|
||||
// cache feature is enabled.
|
||||
func (m *Model) checkAndRemoveSelectCache(ctx context.Context) {
|
||||
|
||||
@ -56,6 +56,9 @@ func (m *Model) AllAndCount(useFieldForCount bool) (result Result, totalCount in
|
||||
if !useFieldForCount {
|
||||
countModel.fields = []any{Raw("1")}
|
||||
}
|
||||
if len(m.pageCacheOption) > 0 {
|
||||
countModel = countModel.Cache(m.pageCacheOption[0])
|
||||
}
|
||||
|
||||
// Get the total count of records
|
||||
totalCount, err = countModel.Count()
|
||||
@ -68,8 +71,13 @@ func (m *Model) AllAndCount(useFieldForCount bool) (result Result, totalCount in
|
||||
return
|
||||
}
|
||||
|
||||
resultModel := m.Clone()
|
||||
if len(m.pageCacheOption) > 1 {
|
||||
resultModel = resultModel.Cache(m.pageCacheOption[1])
|
||||
}
|
||||
|
||||
// Retrieve all records
|
||||
result, err = m.doGetAll(m.GetCtx(), SelectTypeDefault, false)
|
||||
result, err = resultModel.doGetAll(m.GetCtx(), SelectTypeDefault, false)
|
||||
return
|
||||
}
|
||||
|
||||
@ -337,7 +345,9 @@ func (m *Model) ScanAndCount(pointer any, totalCount *int, useFieldForCount bool
|
||||
if !useFieldForCount {
|
||||
countModel.fields = []any{Raw("1")}
|
||||
}
|
||||
|
||||
if len(m.pageCacheOption) > 0 {
|
||||
countModel = countModel.Cache(m.pageCacheOption[0])
|
||||
}
|
||||
// Get the total count of records
|
||||
*totalCount, err = countModel.Count()
|
||||
if err != nil {
|
||||
@ -348,7 +358,11 @@ func (m *Model) ScanAndCount(pointer any, totalCount *int, useFieldForCount bool
|
||||
if *totalCount == 0 {
|
||||
return
|
||||
}
|
||||
err = m.Scan(pointer)
|
||||
scanModel := m.Clone()
|
||||
if len(m.pageCacheOption) > 1 {
|
||||
scanModel = scanModel.Cache(m.pageCacheOption[1])
|
||||
}
|
||||
err = scanModel.Scan(pointer)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user