【问题标题】:Unnecessary queries executed by gorm to insert data in database tablesgorm 执行不必要的查询以在数据库表中插入数据
【发布时间】:2020-03-22 14:03:29
【问题描述】:

我正在尝试使用 golang 和 gorm 在我当前的项目中创建一个新功能。

我在 MySQL 中有以下数据库结构。

UserMaster 表位于层次结构的顶部,其主键列被许多其他表引用,它们自己将其用作主列。 UserLogin 表就是这样的表之一。这不是完美的数据库设计,但这是我将要处理的数据库结构,而且结构不可能改变。

以下是我在 go 中为 UserMaster 和 UserLogin 创建的模型类。

type UserMaster struct {
    UserId      string    `gorm:"column:UserId;PRIMARY_KEY";size:20`
    CreatedDate time.Time `gorm:"column:CreatedDate"`
    IsActive    bool      `gorm:"column:IsActive"`
    UserLogin   UserLogin `gorm:"foreignkey:Id"`
}

type UserLogin struct {
    Id            string    `gorm:"column:Id;PRIMARY_KEY";size:20"`
    UserName      string    `gorm:"column:UserName;unique"`
    EmailId       string    `gorm:"column:EmailId;unique"`
    PasswordHash  string    `gorm:"column:PasswordHash"`
    PasswordSalt  string    `gorm:"column:PasswordSalt"`
    LastLoginDate time.Time `gorm:"column:LastLoginDate"`
}

func (UserMaster) TableName() string {
    return "usermaster"
}

func (UserLogin) TableName() string {
    return "userlogin"
}

我希望通过 gorm 在 UserMaster 和 UserLogin 表中创建行,只需保存完全填充的 UserMaster 实例。

以下是我为此编写的代码。

func main() {
    db, err := gorm.Open("mysql", "user:password@tcp(localhost:3306)/ormsample")

    if err != nil {
        panic("Failed to connect the database.")
    }

    defer db.Close()

    createNewUser(db, "someuser009@yopmail.com", "j;dfasdasdf")
}

func createNewUser(db *gorm.DB, emailId string, password string) UserMaster {
    userId := createUserId()

    newUser := &UserMaster{}
    newUser.UserId = userId
    newUser.CreatedDate = time.Now()
    newUser.UserLogin = UserLogin{}
    newUser.UserLogin.UserName = emailId
    newUser.UserLogin.EmailId = emailId
    newUser.UserLogin.PasswordHash = password
    newUser.UserLogin.PasswordSalt = "SomeSalt"

    db.Debug().Model(&newUser).Create(&newUser)

    return *newUser
}

func createUserId() string {
    uuidWithHyphen := uuid.New()
    uuid := strings.Replace(uuidWithHyphen.String(), "-", "", -1)

    return strings.ToUpper(uuid[:12])
}

这整个事情按预期工作,这意味着它将记录插入到 UserMaster 和 UserLogin 表中。

问题在于 gorm 执行此数据库操作的方式。以下是终端记录的 SQL 查询(因为我在 gorm db 对象上使用Debug() 函数)。

INSERT INTO `usermaster` (`UserId`,`CreatedDate`,`IsActive`) 
    VALUES ('1601F7E41E29','2020-03-22 22:24:08',false) 

UPDATE `userlogin` SET `UserName` = 'someuser009@yopmail.com', 
  `EmailId` = 'someuser009@yopmail.com', `PasswordHash` = 'j;dfasdasdf', 
  `PasswordSalt` = 'SomeSalt', `LastLoginDate` = '0000-00-00 00:00:00'  
  WHERE `userlogin`.`Id` = '1601F7E41E29'

SELECT * FROM `userlogin`  WHERE `userlogin`.`Id` = '1601F7E41E29' 
 ORDER BY `userlogin`.`Id` ASC LIMIT 1

INSERT INTO `userlogin` (`Id`,`UserName`,`EmailId`,`PasswordHash`,`PasswordSalt`,`LastLoginDate`)
VALUES ('1601F7E41E29','someuser009@yopmail.com','someuser009@yopmail.com',
 'j;dfasdasdf','SomeSalt','0000-00-00 00:00:00')

正如您在上面看到的那样,gorm 执行 2 个 INSERT、1 个 UPDATE 和 1 个 SELECT 查询以插入到两个表中,而应该只执行 2 个 INSERT 查询。

我在各种 golang 和 gorm 论坛上对此进行了很多搜索,但没有找到任何可以解释这种行为的东西。

任何解决此问题的帮助/指导/方向将不胜感激。

【问题讨论】:

  • @All 随时询问您是否需要 sql 脚本来创建数据库表。我没有将它们包括在问题中以保持问题的长度适中。
  • 如果将newUser.UserLogin.Id 设置为userId 而不是将其留空,会发生什么情况?
  • @mkopriva 是的..我也试过了......但行为没有改变。它仍然执行 4 个查询。

标签: go go-gorm


【解决方案1】:

Gorm 在创建之前调用这个函数

func beforeCreateCallback(scope *Scope) {
    if !scope.HasError() {
        scope.CallMethod("BeforeSave")
    }
    if !scope.HasError() {
        scope.CallMethod("BeforeCreate")
    }
}

如果已经设置了主键,那么 gorm 会尝试使用给定的主键更新现有数据。这就是执行额外查询的原因。 要解决此问题,请将UserId 用作UserLoginforeignkey,这是UserLogin 模型的主键,这样gorm 会尝试先保存孩子然后再保存父母。不设置主键,使用BeforeCreate钩子设置

func (user *UserLogin) BeforeCreate(scope *gorm.Scope) error {
  scope.SetColumn("Id", ), createUserId())
  return nil
}

型号如下:

type UserMaster struct {
    UserId      uint      `gorm:"column:UserId;PRIMARY_KEY;size:20"`
    CreatedDate time.Time `gorm:"column:CreatedDate"`
    IsActive    bool      `gorm:"column:IsActive"`
    UserLogin   UserLogin `gorm:"foreignkey:UserId"`
}

type UserLogin struct {
    Id            uint      `gorm:"column:Id;PRIMARY_KEY;size:20"`
    UserName      string    `gorm:"column:UserName;"`
    EmailId       string    `gorm:"column:EmailId;"`
    PasswordHash  string    `gorm:"column:PasswordHash"`
    PasswordSalt  string    `gorm:"column:PasswordSalt"`
    LastLoginDate time.Time `gorm:"column:LastLoginDate"`
}

不需要同时使用Model(&user)Create(&user)Model() 主要在更新现有数据之前使用,这可能会导致额外的查询。只有创建你可以尝试

db.Debug().Create(&newUser)

Gorm 从newUser 中自动选择型号UserMaster

【讨论】:

  • 感谢您的回答。我无法更改 PrimaryKey 列的数据类型,因为这是一个在生产中运行的现有数据库,我正在其上构建我的应用程序。如果 Gorm 不能解决这个问题,你有什么其他的 ORM 建议我可以使用吗?
  • 我也尝试过使用db.Debug().Create(&newUser),但这并没有帮助。它仍然运行 4 个查询。
  • 您可以尝试分别创建UserLoginUserMaster
  • 是的......我试过了......它按预期工作并且只运行两个 INSERT 查询。我想避免因为在应用程序中创建新用户需要在 2-4 个表中插入行。我希望通过 Gorm 触发一个 Create 操作来完成这项工作。如果没有解决问题,我将不得不采取多条 Create 操作路线。感谢您对 Abinash 的帮助。
  • 我用不同的方式解决了这个问题。如果从Id 结构的Id 属性中删除PRIMARY_KEY 声明。意思是不让 Gorm 知道它是一个主键列。现在有了这个更改,我看到只有两个 INSERT 查询正在执行。
猜你喜欢
  • 2013-04-15
  • 2013-05-14
  • 2017-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多