范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

使用gorm。DefaultTableNameHandler可能存在的问题

  业务背景
  有这样的业务场景, 线上一个表  tablea , 线上环境还有一个镜像表 tablea_mirror , 你需要当 请求中有一些 tag  标识的时候,访问 tablea_mirror  表先安装 sqlite
  https://wangxiaoming.blog.csdn.net/article/details/121884736 代码
  可以使用  DefaultTableNameHandler  来实现加前缀或者后缀功能。 import (  "code.byted.org/gopkg/gorm"  "context" )  type dbStagingPostfixKeyType struct{}  var dbStagingPostfixKey = dbStagingPostfixKeyType{}  func WithDbStagingPostfix(ctx context.Context, postfix string) context.Context {  return context.WithValue(ctx, dbStagingPostfixKey, postfix) }  func ReWriteTableName() {  gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {   if v := db.Ctx.Value(dbStagingPostfixKey); v != nil {    return defaultTableName + v.(string)   }   return defaultTableName  } } 测试代码package mysql  import (  "fmt"  "testing"   "github.com/jinzhu/gorm"  //_ "github.com/jinzhu/gorm/dialects/sqlite"  _ "github.com/mattn/go-sqlite3" )  type Product struct {  gorm.Model  Code  string  Price uint }  func (Product) TableName() string {  return "hax_products" } func Test(t *testing.T) {  db, err := gorm.Open("sqlite3", "test.db")  if err != nil {   panic("failed to connect database")  }  defer db.Close()  gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {   return "hax_" + defaultTableName  }  db.LogMode(true)  // Migrate the schema  db.AutoMigrate(&Product{})  db.Create(&Product{Code: "L1212", Price: 1000})  var product Product  db.First(&product, 1)  var products []Product  db.Find(&products)  fmt.Printf("Total count %d", len(products)) }
  执行结果: (/Users/xxx/go/src/xxx/xxx.xx/GoProject/mysql/sqllite_test.go:33)  [2021-12-14 21:43:33]  [1.38ms]  INSERT INTO "hax_products" ("created_at","updated_at","deleted_at","code","price") VALUES ("2021-12-14 21:43:33","2021-12-14 21:43:33",NULL,"L1212",1000)   [1 rows affected or returned ]   (/Users/xxx/go/src/xxx/xxx.xx/GoProject/mysql/sqllite_test.go:35)  [2021-12-14 21:43:33]  [0.23ms]  SELECT * FROM "hax_products"  WHERE "hax_products"."deleted_at" IS NULL AND (("hax_products"."id" = 1)) ORDER BY "hax_products"."id" ASC LIMIT 1   [1 rows affected or returned ]   ((/Users/xxx/go/src/xxx/xxx.xx/GoProject/mysql/sqllite_test.go:37)  [2021-12-14 21:43:33]  no such table: hax_hax_products   ((/Users/xxx/go/src/xxx/xxx.xx/GoProject/mysql/sqllite_test.go:37)  [2021-12-14 21:43:33]  [0.10ms]  SELECT * FROM "hax_hax_products"  WHERE "hax_hax_products"."deleted_at" IS NULL   [0 rows affected or returned ]  Total count 0--- PASS: Test (0.00s) PASS
  根据执行结果,可以看到,创建语言与查询单条记录时表名为  hax_products  但是查询 多条记录时,却使用了表名hax_hax_products .这个就是坑1
  查询单个记录时使用了TableName()返回的表名,而在查询结果为Array时,表名在TableName()的基础上又添加了前缀。
  Gorm 结构体 一般分析如下  struct type DB struct (gorm/main.go) 代表数据库连接,每次操作数据库会创建出clone 对象。 方法gorm.Open() 返回的值类型就是这个结构体指针。type Scope struct (gorm/scope.go)  当前数据库操作的信息,每次添加条件时也会创建clone 对象。type Callback struct (gorm/callback.go)  数据库各种操作的回调函数, SQL生成也是靠这些回调函数。 每种类型的回调函数放在单独的文件里,比如查询回调函数在gorm/callback_query.go , 创建的在gorm/callback_create.go db.First() 代码分析
  First() 方法位于gorm/main.go文件中, .callCallbacks(s.parent.callbacks.queries)调用了query回调函数。// file: gorm/main.go // First find first record that match given conditions, order by primary key func (s *DB) First(out interface{}, where ...interface{}) *DB {  newScope := s.NewScope(out)  newScope.Search.Limit(1)  return newScope.Set("gorm:order_by_primary_key", "ASC").   inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db }
  Callback 结构体中定义queries为函数指针数组, 而默认值的初始化在gorm/callback_query.go 的init() 方法中, 查询方法为queryCallback , 而queryCallback() 方法又调用到scope.prepareQuerySQL() , scope 中的方法真正生成SQL的地方。// file: gorm/callback.go type Callback struct {  logger     logger  creates    []*func(scope *Scope)  updates    []*func(scope *Scope)  deletes    []*func(scope *Scope)  queries    []*func(scope *Scope)  rowQueries []*func(scope *Scope)  processors []*CallbackProcessor } // file: gorm/callback_query.go // Define callbacks for querying func init() {  DefaultCallback.Query().Register("gorm:query", queryCallback)  DefaultCallback.Query().Register("gorm:preload", preloadCallback)  DefaultCallback.Query().Register("gorm:after_query", afterQueryCallback) } // queryCallback used to query data from database func queryCallback(scope *Scope) { ...     scope.prepareQuerySQL() ... }
  跟踪代码到 scope.go 文件, 函数TableName() 是获取数据库表名的地方。 它按如下顺序来确定表名:scope.Search.tableName  查询条件中设置了表名, 则直接使用scope.Value.(tabler)  值对象实现了tabler接口(方法TableName() string ), 则从调用方法获取scope.Value.(dbTabler)  值对象实现了dbTabler接口(方法TableName(*DB) string), 则从调用方法获取若以上条件都不成立,则从scope.GetModelStruct()中获取对象的结构体信息,从结构体名生成表名
  具体可见  scope.go  源码// file: gorm/scope.go func (scope *Scope) prepareQuerySQL() {  if scope.Search.raw {   scope.Raw(scope.CombinedConditionSql())  } else {   scope.Raw(fmt.Sprintf("SELECT %v FROM %v %v", scope.selectSQL(), scope.QuotedTableName(), scope.CombinedConditionSql()))  }  return } // QuotedTableName return quoted table name func (scope *Scope) QuotedTableName() (name string) {  if scope.Search != nil && len(scope.Search.tableName) > 0 {   if strings.Contains(scope.Search.tableName, " ") {    return scope.Search.tableName   }   return scope.Quote(scope.Search.tableName)  }  return scope.Quote(scope.TableName()) } // TableName return table name func (scope *Scope) TableName() string {  if scope.Search != nil && len(scope.Search.tableName) > 0 {   return scope.Search.tableName  }  if tabler, ok := scope.Value.(tabler); ok {   return tabler.TableName()  }  if tabler, ok := scope.Value.(dbTabler); ok {   return tabler.TableName(scope.db)  }  return scope.GetModelStruct().TableName(scope.db.Model(scope.Value)) }
  对比以上条件, 示例中的Product结构体定义了方法 TableName() string ,符合条件2,那么db.First(&product, 1) 使用的表名就是hax_products 。db.Find() 代码分析
  Find() 代码如下,与First()同样是使用了callbacks.queries 回调方法,不同点在于设置了newScope.Search.Limit(1) 只返回一个结果、增加了按id排序。// Find find records that match given conditions func (s *DB) Find(out interface{}, where ...interface{}) *DB {  return s.NewScope(out).inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db }
  在debug模式下跟踪代码到scope.TableName()中时,两次查询的区别显示出来了: 它们的结果值类型不同。  db.First(&product, 1) 的值类型为结构体的指针*Product ,而db.Find(&products) 的值类型是数组的指针*[]Product, 从而导致db.Find(&products) 进入条件 scope.GetModelStruct().TableName(scope.db.Model(scope.Value)) } ,需要靠分析struct结构体来生成表名。// file: gorm/model_struct.go // TableName returns model"s table name func (s *ModelStruct) TableName(db *DB) string {  s.l.Lock()  defer s.l.Unlock()  if s.defaultTableName == "" && db != nil && s.ModelType != nil {   // Set default table name   if tabler, ok := reflect.New(s.ModelType).Interface().(tabler); ok {    s.defaultTableName = tabler.TableName()   } else {    tableName := ToTableName(s.ModelType.Name())    db.parent.RLock()    if db == nil || (db.parent != nil && !db.parent.singularTable) {     tableName = inflection.Plural(tableName)    }    db.parent.RUnlock()    s.defaultTableName = tableName   }  }  return DefaultTableNameHandler(db, s.defaultTableName) }
  默认表名 s.defaultTableName 为空值时先进行求值,reflect.New(s.ModelType).Interface().(tabler) 先判断是否实现了tabler接口,有则调用其TableName()取值; 否则的话从结构体的名字来生成表名。 结果返回之前再调用 DefaultTableNameHandler(db, s.defaultTableName) 方法。
  这个 ModelStruct 的TableName 方法与scope.TableName()  中的逻辑两个不一致的地方:scope.TableName() 会判断是否实现tabler与dbTabler两个接口,而这里只判断了tablerscope.TableName() 是将tableName结果直接返回的, 而这里多调用了DefaultTableNameHandler()。
  因为逻辑  scope.TableName() 的存在, 当重写DefaultTableNameHandler() 方法时, 就会出现表前缀再次被添加了表名前。问题2
  DefaultTableNameHandler()在多数据库时出现混乱
  通过以上代码的分析,于是发现了另一个坑: 当一个程序中使用两个不同的数据库时, 重写方法 DefaultTableNameHandler() 会影响到两个数据库中的表名。 其中一个数据库需要设置表前缀时,访问另一个数据库的表也可能会被加上前缀。 因为是包级别的方法,整个代码里只能设置一次值。// file: gorm/model_struct.go // DefaultTableNameHandler default table name handler var DefaultTableNameHandler = func(db *DB, defaultTableName string) string {  return defaultTableName } 总结当给结构体实现了TableName()方法时,就不要设置DefaultTableNameHandler了。 保持所有Model的表名生成方式一致,要么全部使用自动生成的表名,要么全部实现tabler接口(实现- TableName()方法) 当需要使用多个数据库时,要避免设置 DefaultTableNameHandler 强烈建议: 所有Model结构体全部实现tabler接口 欢迎关注:程序员财富自由之路
  在这里插入图片描述 参考资料http://blog.vikazhou.com/2020/02/09/GORM-Problem-Analyze/

400电话是怎么收费的?谢谢邀请。什么是400电话?400业务又称全国统一接入码业务,是一个由10位数字组成的全国唯一接入号码,是电信运营商专为企事业单位设计的全国唯一的强大的虚拟电话总机,所有拨打400裁员还是优化?在线教育行业的洗牌期真的要来了么?裁员优化,其实没啥区别,我见到最狠的是所谓未来每年将会向社会输出1000名在阿里工作10年以上的人才,他们应该参与到社会的建设,他们到各个公司去。为什么会看到在线教育机构裁员1。培618黑马现身,realme超越华为,直冲前三,是格局变了吗?六月,不仅仅是儿童节了,伴随年龄的增长,我开始关注手机,关注科技,慢慢地,我的潜意识里,6月不再是儿童节了,更多的是618的狂欢的前奏。各大厂商都拿出了自己的杀手锏,然而,Real华为鸿蒙系统开源,小米等品牌集体失声,甚至遭OPPO公关讽刺华为鸿蒙开源华为发布了自己的手机系统,鸿蒙。向所有手机厂商开源。可惜,小米等品牌集体失声。其中,OPPO公关极力讽刺。请问OPPO能研发系统出来吗?算哪一根葱?这些人,可能忘了。国vivoX80假日评测打破天玑9000的相机魔咒,自研V1寻找黑夜亮光联发科天玑9000芯片在2022年这个时间段可以说是真香首选,其均衡性能与实力表现深受手机玩家的喜爱。但在众多优点当中唯一会让用户担忧的恐怕就是它的拍照表现了。那么vivoX80作百度百科浏览器编录(20220511104304)原来你是这样的大学浏览器网页浏览器科普中国本词条由科普中国科学百科词条编写与应用工作项目审核审阅专家闫晓东浏览器是用来检索展示以及传递Web信息资源的应用程序。Web信息资源由统一Flutter中InheritedWidget的使用定义在Tree中从上往下高效传递数据的基类widget,定义为abstractclassInheritedWidgetextendsProxyWidgetInheritedWidg80后博士,升任正厅级5月10日,陕西省人民政府官网发布消息,省政府2022年4月29日决定,任命赵孝为陕西省政务大数据服务中心主任(试用期一年)。陕西省政务大数据服务中心成立于2021年7月,为省政府仅1799元,骁龙87050倍变焦柔性屏,还有12256G内存手机市场竞争激烈,但我从未想到如此残酷,在来到五月份的时候,各大厂商都推出了自己的新机,天玑8100成为上半年的香饽饽,去年的那些旧机型,也都开始了自己的清仓之旅,骁龙888,骁龙分析师爆猛料iPhone15或采用USBC接口让苹果回归主流,放弃Lightning口,可能吗?5月11日,分析师郭明錤表示,iPhone15可能是第一款采用USBC端口代替Lightning的iPhone,在硬件上提高iPh乱套了,骁龙888旗舰清库存,8256G直接降了1900元为了给自家新机铺垫好市场,手机品牌内部之间一般不会有内卷现象产生,因为自己和自己竞争,受益的当然是别人。所以在新机更新换代的时候,我们就会看到旧机为了新机让路大力调低售价清仓。对于
干货!智能工厂相关的六大解决方案,你知道几个?在智能制造的行业大背景下,实现智慧工厂的最终目标已经成为了众多制造企业发展的共同方向。通过引进更多智能制造装备,来实现生产自动化数字化智能化,也成为了许多大型制造企业提高经济效益节将65W闪充和素皮打到千元,realmeQ2Pro评测当有人问你,他想换一台价格1K,性能够用,颜值要高,各方面还要表现超均衡的手机,你会让他选择什么?至少过去我会说加钱。放眼目前1K售价的手机,似乎真的没有特别值得选择的产品,但就在与官网配色不同,蓝色款iPhone12被吐槽,现已多次反转iPhone12系列中共有五款配色,分别是白色黑色红色蓝色以及淡绿色,其中新增的配色就是蓝色。而iPhone12的蓝色与iPhone12Pro的海军蓝还不太一样,从现在的真机图来看还会为信仰买单?索尼Xperia1II5II发布年初发布,年末上市索尼移动今天终于带来了2020年旗舰机型索尼Xperia1II与Xperia5II。两款新品秉承为速度而生理念,融合了索尼旗下影像显示和音频等诸多黑科技和工业设计小米高端净水器小米净水器H600G,双出水新型复合滤芯2015年7月16日,小米发布第一款净水器产品,5年之后,小米已成为净水器行业线上销量第一名。10月21日,小米又将推出一款全新升级版净水产品小米净水器H600G。这款产品同时具备12。5万转电机的全面清洁实力派,米家无线吸尘器K10体验大部分人家中的清扫组合应该是扫地机器人无线吸尘器湿拖器的三巨头组合,我现在家中主要就是靠无线吸尘器和湿拖器来清洁地面,问题是每次都要先吸后拖,效率太低,而我前几年买的无线吸尘器性能12。5万转的实力派,米家无线吸尘器K10正式发布吸尘器作为智能清洁工具已经从昔日的冷门家电逐渐成为人们品质生活的标配,在吸尘器市场迅猛发展而同时进口品牌一枝独秀的局面也被众多国产品牌的崛起而打破,与此同时,消费者在选购吸尘器产品小米发布三款净水器新品,全面覆盖高中入门三个档位作为智能生活的引领者,小米在智能家电领域的优势仍在持续扩大,成为了千家万户首选的智能品牌。躺着就能控制家电,让家居环境达到更舒服的状态,让家务变得更简单轻松,已经逐渐成为用户更懒的和国产手机对比夜景拍照,iPhone12还需努力今年iPhone12在摄像头的表现提升方面,我个人觉得没有很明显的突破,硬件上依旧是熟悉的标准真超广角的搭配,虽然相比前代后置摄像头的光圈稍微进行了提升,镜片组提升到7枚镜片,另外2020工博会落幕,智能制造的最新技术到什么程度了?2020年9月19日,为期5天的第22届中国国际工业博览会19日在国家会展中心(上海)圆满落幕!本届工博会以智能互联赋能产业新发展为主题,共设9大专业展,吸引了来自全球22个国家和前沿技术,产业方向,华南工博会与你共享华南工业博览会,将于10月1215日在深圳会展中心(新馆)举办,此次展会由汉诺威工博会和中国工博会联合打造,包含工业机器人数字工厂机器视觉信息技术激光技术金属加工等专题展会,信息同