github.com/gogf/gf/v2@v2.7.4/database/gdb/gdb_model_cache.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gdb 8 9 import ( 10 "context" 11 "time" 12 13 "github.com/gogf/gf/v2/internal/intlog" 14 ) 15 16 // CacheOption is options for model cache control in query. 17 type CacheOption struct { 18 // Duration is the TTL for the cache. 19 // If the parameter `Duration` < 0, which means it clear the cache with given `Name`. 20 // If the parameter `Duration` = 0, which means it never expires. 21 // If the parameter `Duration` > 0, which means it expires after `Duration`. 22 Duration time.Duration 23 24 // Name is an optional unique name for the cache. 25 // The Name is used to bind a name to the cache, which means you can later control the cache 26 // like changing the `duration` or clearing the cache with specified Name. 27 Name string 28 29 // Force caches the query result whatever the result is nil or not. 30 // It is used to avoid Cache Penetration. 31 Force bool 32 } 33 34 // selectCacheItem is the cache item for SELECT statement result. 35 type selectCacheItem struct { 36 Result Result // Sql result of SELECT statement. 37 FirstResultColumn string // The first column name of result, for Value/Count functions. 38 } 39 40 // Cache sets the cache feature for the model. It caches the result of the sql, which means 41 // if there's another same sql request, it just reads and returns the result from cache, it 42 // but not committed and executed into the database. 43 // 44 // Note that, the cache feature is disabled if the model is performing select statement 45 // on a transaction. 46 func (m *Model) Cache(option CacheOption) *Model { 47 model := m.getModel() 48 model.cacheOption = option 49 model.cacheEnabled = true 50 return model 51 } 52 53 // checkAndRemoveSelectCache checks and removes the cache in insert/update/delete statement if 54 // cache feature is enabled. 55 func (m *Model) checkAndRemoveSelectCache(ctx context.Context) { 56 if m.cacheEnabled && m.cacheOption.Duration < 0 && len(m.cacheOption.Name) > 0 { 57 var cacheKey = m.makeSelectCacheKey("") 58 if _, err := m.db.GetCache().Remove(ctx, cacheKey); err != nil { 59 intlog.Errorf(ctx, `%+v`, err) 60 } 61 } 62 } 63 64 func (m *Model) getSelectResultFromCache(ctx context.Context, sql string, args ...interface{}) (result Result, err error) { 65 if !m.cacheEnabled || m.tx != nil { 66 return 67 } 68 var ( 69 cacheItem *selectCacheItem 70 cacheKey = m.makeSelectCacheKey(sql, args...) 71 cacheObj = m.db.GetCache() 72 core = m.db.GetCore() 73 ) 74 defer func() { 75 if cacheItem != nil { 76 if internalData := core.getInternalColumnFromCtx(ctx); internalData != nil { 77 if cacheItem.FirstResultColumn != "" { 78 internalData.FirstResultColumn = cacheItem.FirstResultColumn 79 } 80 } 81 } 82 }() 83 if v, _ := cacheObj.Get(ctx, cacheKey); !v.IsNil() { 84 if err = v.Scan(&cacheItem); err != nil { 85 return nil, err 86 } 87 return cacheItem.Result, nil 88 } 89 return 90 } 91 92 func (m *Model) saveSelectResultToCache( 93 ctx context.Context, queryType queryType, result Result, sql string, args ...interface{}, 94 ) (err error) { 95 if !m.cacheEnabled || m.tx != nil { 96 return 97 } 98 var ( 99 cacheKey = m.makeSelectCacheKey(sql, args...) 100 cacheObj = m.db.GetCache() 101 ) 102 if m.cacheOption.Duration < 0 { 103 if _, errCache := cacheObj.Remove(ctx, cacheKey); errCache != nil { 104 intlog.Errorf(ctx, `%+v`, errCache) 105 } 106 return 107 } 108 // Special handler for Value/Count operations result. 109 if len(result) > 0 { 110 var core = m.db.GetCore() 111 switch queryType { 112 case queryTypeValue, queryTypeCount: 113 if internalData := core.getInternalColumnFromCtx(ctx); internalData != nil { 114 if result[0][internalData.FirstResultColumn].IsEmpty() { 115 result = nil 116 } 117 } 118 } 119 } 120 121 // In case of Cache Penetration. 122 if result.IsEmpty() { 123 if m.cacheOption.Force { 124 result = Result{} 125 } else { 126 result = nil 127 } 128 } 129 var ( 130 core = m.db.GetCore() 131 cacheItem = &selectCacheItem{ 132 Result: result, 133 } 134 ) 135 if internalData := core.getInternalColumnFromCtx(ctx); internalData != nil { 136 cacheItem.FirstResultColumn = internalData.FirstResultColumn 137 } 138 if errCache := cacheObj.Set(ctx, cacheKey, cacheItem, m.cacheOption.Duration); errCache != nil { 139 intlog.Errorf(ctx, `%+v`, errCache) 140 } 141 return 142 } 143 144 func (m *Model) makeSelectCacheKey(sql string, args ...interface{}) string { 145 var ( 146 table = m.db.GetCore().guessPrimaryTableName(m.tables) 147 group = m.db.GetGroup() 148 schema = m.db.GetSchema() 149 customName = m.cacheOption.Name 150 ) 151 return genSelectCacheKey( 152 table, 153 group, 154 schema, 155 customName, 156 sql, 157 args..., 158 ) 159 }