mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
```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>
351 lines
12 KiB
Go
351 lines
12 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||
//
|
||
// This Source Code Form is subject to the terms of the MIT License.
|
||
// If a copy of the MIT was not distributed with this file,
|
||
// You can obtain one at https://github.com/gogf/gf.
|
||
|
||
package gdb
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
"github.com/gogf/gf/v2/text/gregex"
|
||
"github.com/gogf/gf/v2/text/gstr"
|
||
"github.com/gogf/gf/v2/util/gconv"
|
||
)
|
||
|
||
// 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.
|
||
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.
|
||
type ModelHandler func(m *Model) *Model
|
||
|
||
// ChunkHandler is a function that is used in function Chunk, which handles given Result and error.
|
||
// It returns true if it wants to continue chunking, or else it returns false to stop chunking.
|
||
type ChunkHandler func(result Result, err error) bool
|
||
|
||
const (
|
||
linkTypeMaster = 1
|
||
linkTypeSlave = 2
|
||
defaultField = "*"
|
||
whereHolderOperatorWhere = 1
|
||
whereHolderOperatorAnd = 2
|
||
whereHolderOperatorOr = 3
|
||
whereHolderTypeDefault = "Default"
|
||
whereHolderTypeNoArgs = "NoArgs"
|
||
whereHolderTypeIn = "In"
|
||
)
|
||
|
||
// Model creates and returns a new ORM model from given schema.
|
||
// The parameter `tableNameQueryOrStruct` can be more than one table names, and also alias name, like:
|
||
// 1. Model names:
|
||
// db.Model("user")
|
||
// db.Model("user u")
|
||
// db.Model("user, user_detail")
|
||
// db.Model("user u, user_detail ud")
|
||
// 2. Model name with alias:
|
||
// db.Model("user", "u")
|
||
// 3. Model name with sub-query:
|
||
// db.Model("? AS a, ? AS b", subQuery1, subQuery2)
|
||
func (c *Core) Model(tableNameQueryOrStruct ...any) *Model {
|
||
var (
|
||
ctx = c.db.GetCtx()
|
||
tableStr string
|
||
tableName string
|
||
extraArgs []any
|
||
)
|
||
// Model creation with sub-query.
|
||
if len(tableNameQueryOrStruct) > 1 {
|
||
conditionStr := gconv.String(tableNameQueryOrStruct[0])
|
||
if gstr.Contains(conditionStr, "?") {
|
||
whereHolder := WhereHolder{
|
||
Where: conditionStr,
|
||
Args: tableNameQueryOrStruct[1:],
|
||
}
|
||
tableStr, extraArgs = formatWhereHolder(ctx, c.db, formatWhereHolderInput{
|
||
WhereHolder: whereHolder,
|
||
OmitNil: false,
|
||
OmitEmpty: false,
|
||
Schema: "",
|
||
Table: "",
|
||
})
|
||
}
|
||
}
|
||
// Normal model creation.
|
||
if tableStr == "" {
|
||
tableNames := make([]string, len(tableNameQueryOrStruct))
|
||
for k, v := range tableNameQueryOrStruct {
|
||
if s, ok := v.(string); ok {
|
||
tableNames[k] = s
|
||
} else if tableName = getTableNameFromOrmTag(v); tableName != "" {
|
||
tableNames[k] = tableName
|
||
}
|
||
}
|
||
if len(tableNames) > 1 {
|
||
tableStr = fmt.Sprintf(
|
||
`%s AS %s`, c.QuotePrefixTableName(tableNames[0]), c.QuoteWord(tableNames[1]),
|
||
)
|
||
} else if len(tableNames) == 1 {
|
||
tableStr = c.QuotePrefixTableName(tableNames[0])
|
||
}
|
||
}
|
||
m := &Model{
|
||
db: c.db,
|
||
schema: c.schema,
|
||
tablesInit: tableStr,
|
||
tables: tableStr,
|
||
start: -1,
|
||
offset: -1,
|
||
filter: true,
|
||
extraArgs: extraArgs,
|
||
tableAliasMap: make(map[string]string),
|
||
}
|
||
m.whereBuilder = m.Builder()
|
||
if defaultModelSafe {
|
||
m.safe = true
|
||
}
|
||
return m
|
||
}
|
||
|
||
// Raw creates and returns a model based on a raw sql not a table.
|
||
// Example:
|
||
//
|
||
// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result)
|
||
func (c *Core) Raw(rawSql string, args ...any) *Model {
|
||
model := c.Model()
|
||
model.rawSql = rawSql
|
||
model.extraArgs = args
|
||
return model
|
||
}
|
||
|
||
// Raw sets current model as a raw sql model.
|
||
// Example:
|
||
//
|
||
// db.Raw("SELECT * FROM `user` WHERE `name` = ?", "john").Scan(&result)
|
||
//
|
||
// See Core.Raw.
|
||
func (m *Model) Raw(rawSql string, args ...any) *Model {
|
||
model := m.db.Raw(rawSql, args...)
|
||
model.db = m.db
|
||
model.tx = m.tx
|
||
return model
|
||
}
|
||
|
||
func (tx *TXCore) Raw(rawSql string, args ...any) *Model {
|
||
return tx.Model().Raw(rawSql, args...)
|
||
}
|
||
|
||
// With creates and returns an ORM model based on metadata of given object.
|
||
func (c *Core) With(objects ...any) *Model {
|
||
return c.db.Model().With(objects...)
|
||
}
|
||
|
||
// Partition sets Partition name.
|
||
// Example:
|
||
// dao.User.Ctx(ctx).Partition("p1","p2","p3").All()
|
||
func (m *Model) Partition(partitions ...string) *Model {
|
||
model := m.getModel()
|
||
model.partition = gstr.Join(partitions, ",")
|
||
return model
|
||
}
|
||
|
||
// Model acts like Core.Model except it operates on transaction.
|
||
// See Core.Model.
|
||
func (tx *TXCore) Model(tableNameQueryOrStruct ...any) *Model {
|
||
model := tx.db.Model(tableNameQueryOrStruct...)
|
||
model.db = tx.db
|
||
model.tx = tx
|
||
return model
|
||
}
|
||
|
||
// With acts like Core.With except it operates on transaction.
|
||
// See Core.With.
|
||
func (tx *TXCore) With(object any) *Model {
|
||
return tx.Model().With(object)
|
||
}
|
||
|
||
// Ctx sets the context for current operation.
|
||
func (m *Model) Ctx(ctx context.Context) *Model {
|
||
if ctx == nil {
|
||
return m
|
||
}
|
||
model := m.getModel()
|
||
model.db = model.db.Ctx(ctx)
|
||
if m.tx != nil {
|
||
model.tx = model.tx.Ctx(ctx)
|
||
}
|
||
return model
|
||
}
|
||
|
||
// GetCtx returns the context for current Model.
|
||
// It returns `context.Background()` is there's no context previously set.
|
||
func (m *Model) GetCtx() context.Context {
|
||
if m.tx != nil && m.tx.GetCtx() != nil {
|
||
return m.tx.GetCtx()
|
||
}
|
||
return m.db.GetCtx()
|
||
}
|
||
|
||
// As sets an alias name for current table.
|
||
func (m *Model) As(as string) *Model {
|
||
if m.tables != "" {
|
||
model := m.getModel()
|
||
split := " JOIN "
|
||
if gstr.ContainsI(model.tables, split) {
|
||
// For join table.
|
||
array := gstr.Split(model.tables, split)
|
||
array[len(array)-1], _ = gregex.ReplaceString(`(.+) ON`, fmt.Sprintf(`$1 AS %s ON`, as), array[len(array)-1])
|
||
model.tables = gstr.Join(array, split)
|
||
} else {
|
||
// For base table.
|
||
model.tables = gstr.TrimRight(model.tables) + " AS " + as
|
||
}
|
||
return model
|
||
}
|
||
return m
|
||
}
|
||
|
||
// DB sets/changes the db object for current operation.
|
||
func (m *Model) DB(db DB) *Model {
|
||
model := m.getModel()
|
||
model.db = db
|
||
return model
|
||
}
|
||
|
||
// TX sets/changes the transaction for current operation.
|
||
func (m *Model) TX(tx TX) *Model {
|
||
model := m.getModel()
|
||
model.db = tx.GetDB()
|
||
model.tx = tx
|
||
return model
|
||
}
|
||
|
||
// Schema sets the schema for current operation.
|
||
func (m *Model) Schema(schema string) *Model {
|
||
model := m.getModel()
|
||
model.schema = schema
|
||
return model
|
||
}
|
||
|
||
// Clone creates and returns a new model which is a Clone of current model.
|
||
// Note that it uses deep-copy for the Clone.
|
||
func (m *Model) Clone() *Model {
|
||
newModel := (*Model)(nil)
|
||
if m.tx != nil {
|
||
newModel = m.tx.Model(m.tablesInit)
|
||
} else {
|
||
newModel = m.db.Model(m.tablesInit)
|
||
}
|
||
// Basic attributes copy.
|
||
*newModel = *m
|
||
// WhereBuilder copy, note the attribute pointer.
|
||
newModel.whereBuilder = m.whereBuilder.Clone()
|
||
newModel.whereBuilder.model = newModel
|
||
// Shallow copy slice attributes.
|
||
if n := len(m.fields); n > 0 {
|
||
newModel.fields = make([]any, n)
|
||
copy(newModel.fields, m.fields)
|
||
}
|
||
if n := len(m.fieldsEx); n > 0 {
|
||
newModel.fieldsEx = make([]any, n)
|
||
copy(newModel.fieldsEx, m.fieldsEx)
|
||
}
|
||
if n := len(m.extraArgs); n > 0 {
|
||
newModel.extraArgs = make([]any, n)
|
||
copy(newModel.extraArgs, m.extraArgs)
|
||
}
|
||
if n := len(m.withArray); n > 0 {
|
||
newModel.withArray = make([]any, n)
|
||
copy(newModel.withArray, m.withArray)
|
||
}
|
||
if n := len(m.having); n > 0 {
|
||
newModel.having = make([]any, n)
|
||
copy(newModel.having, m.having)
|
||
}
|
||
return newModel
|
||
}
|
||
|
||
// Master marks the following operation on master node.
|
||
func (m *Model) Master() *Model {
|
||
model := m.getModel()
|
||
model.linkType = linkTypeMaster
|
||
return model
|
||
}
|
||
|
||
// Slave marks the following operation on slave node.
|
||
// Note that it makes sense only if there's any slave node configured.
|
||
func (m *Model) Slave() *Model {
|
||
model := m.getModel()
|
||
model.linkType = linkTypeSlave
|
||
return model
|
||
}
|
||
|
||
// Safe marks this model safe or unsafe. If safe is true, it clones and returns a new model object
|
||
// whenever the operation done, or else it changes the attribute of current model.
|
||
func (m *Model) Safe(safe ...bool) *Model {
|
||
if len(safe) > 0 {
|
||
m.safe = safe[0]
|
||
} else {
|
||
m.safe = true
|
||
}
|
||
return m
|
||
}
|
||
|
||
// Args sets custom arguments for model operation.
|
||
func (m *Model) Args(args ...any) *Model {
|
||
model := m.getModel()
|
||
model.extraArgs = append(model.extraArgs, args)
|
||
return model
|
||
}
|
||
|
||
// Handler calls each of `handlers` on current Model and returns a new Model.
|
||
// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
|
||
func (m *Model) Handler(handlers ...ModelHandler) *Model {
|
||
model := m.getModel()
|
||
for _, handler := range handlers {
|
||
model = handler(model)
|
||
}
|
||
return model
|
||
}
|