如何使用gorm从数据库中返回新创建的记录?

huangapple go评论99阅读模式
英文:

How to return a newly created record back from the database using gorm

问题

我有一个创建新用户的函数,但是推荐的获取用户值的方式不包括数据库自动生成的值(id,created_at)。

你可以看到,尽管这些值是由我的数据库自动生成的,但我得到的id和created_at都是空字符串。

测试:

在某种程度上,我认为这归结于编写gorm所期望的“正确”类型。

  1. 如果我在结构体中添加gorm.Model,似乎Id将返回uuid,但它还会返回一堆我不想要的重复字段(ID,CreatedAt,UpdatedAt,DeletedAt)。如果删除gorm.Model,Id将不再返回由postgres生成的uuid。

数据库postgres:

-- CreateTable
CREATE TABLE "users" (
    "id" UUID NOT NULL DEFAULT gen_random_uuid(),
    "email" TEXT NOT NULL,
    "password" TEXT,
    "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updated_at" TIMESTAMP(3) NOT NULL,
    "deleted_at" TIMESTAMP(3),

    CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
英文:

I have a function that creates a new user, however the reccomended way to get the values of the user does not include the autogenerated values created by the database (id, created_at)

type User struct {
	Id         string `json:"id" gorm:"primaryKey"`
	Email      string `json:"email"`
	Password   string `json:"password"`
	Created_At string `json:"created_at"`
	Updated_At string `json:"updated_at"`
	Deleted_At string `json:"deleted_at"`
}

type UserRequest struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

func CreateUserRepo(newUser UserRequest) (*User, error) {

	user := User{Email: newUser.Email, Password: newUser.Password}
	fmt.Println(user)
	result := services.PostgresSQLDB.Select("Email", "Password").Create(&user)
	fmt.Println(result.RowsAffected)

	if result.Error != nil {
		modules.Logger.Error(result.Error)
		return nil, result.Error
	}

	return &user, nil

}

You can see i get an empty string for both id and created_at although those values have been auto generated by my database.

{
    "id": "",
    "email": "test@test.com",
    "password": "password",
    "created_at": "",
    "updated_at": "",
    "deleted_at": ""
}

如何使用gorm从数据库中返回新创建的记录?

testing

To some extend i think this comes down to writing the "proper" types that gorm expects.

  1. It seems like Id will return the uuid if i add gorm.Model to my struct, but it also returns back a bunch of fields that seem like duplicates that i dont want (ID, CreatedAt, UpdatedAt, DeletedAt). Removing gorm.Model, Id no longer gives me back the uuid generated by postgres
    type User struct {
    	gorm.Model
    	Id        uuid.UUID      `json:"id"`
    	Email     string         `json:"email"`
    	Password  string         `json:"password"`
    	CreatedAt time.Time      `json:"created_at"`
    	UpdatedAt time.Time      `json:"updated_at"`
    	DeletedAt gorm.DeletedAt `json:"deleted_at"`
    }
{
    "ID": 0,
    "CreatedAt": "0001-01-01T00:00:00Z",
    "UpdatedAt": "0001-01-01T00:00:00Z",
    "DeletedAt": null,
    "id": "b4dea226-3be2-4ee7-8548-67ccbbbcbcca",
    "email": "test@test.com",
    "password": "password",
    "created_at": "2022-06-25T20:59:27.872198797+01:00",
    "updated_at": "2022-06-25T20:59:27.872198797+01:00",
    "deleted_at": null
}

如何使用gorm从数据库中返回新创建的记录?

database postgres

-- CreateTable
CREATE TABLE "users" (
    "id" UUID NOT NULL DEFAULT gen_random_uuid(),
    "email" TEXT NOT NULL,
    "password" TEXT,
    "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updated_at" TIMESTAMP(3) NOT NULL,
    "deleted_at" TIMESTAMP(3),

    CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);

答案1

得分: 2

我终于找到了不需要妥协的答案。

添加默认的返回子句,返回所有新创建的值。
https://gorm.io/docs/update.html#Returning-Data-From-Modified-Rows

Clauses.(clause.Returning{})

model.go

type User struct {
	ID        uuid.UUID      `json:"id" gorm:"primaryKey:type:uuid"`
	Email     string         `json:"email"`
	Password  string         `json:"password"`
	CreatedAt time.Time      `json:"created_at"`
	UpdatedAt time.Time      `json:"updated_at"`
	DeletedAt gorm.DeletedAt `json:"deleted_at"`
}

repository.go

func CreateUserRepo(newUser UserRequest) (*User, error) {

	hash, errHash := modules.HashPassword(newUser.Password)

	if errHash != nil {
		return nil, errHash
	}

	user := User{Email: newUser.Email, Password: *hash}
	result := services.PostgresSQLDB.Clauses(clause.Returning{}).Select("Email", "Password").Create(&user)
	fmt.Println(result.RowsAffected)
	fmt.Println(&user)

	if result.Error != nil {
		modules.Logger.Error(result.Error)
		return nil, result.Error
	}

	return &user, nil

}

感谢@TheFool的评论找到这个。

英文:

I finally found the answer without comprising.

Adding the default return clause, returns all the newly created values.
https://gorm.io/docs/update.html#Returning-Data-From-Modified-Rows

Clauses.(clause.Returning{})

model.go

type User struct {
	ID        uuid.UUID      `json:"id" gorm:"primaryKey:type:uuid"`
	Email     string         `json:"email"`
	Password  string         `json:"password"`
	CreatedAt time.Time      `json:"created_at"`
	UpdatedAt time.Time      `json:"updated_at"`
	DeletedAt gorm.DeletedAt `json:"deleted_at"`
}

repository.go

func CreateUserRepo(newUser UserRequest) (*User, error) {

	hash, errHash := modules.HashPassword(newUser.Password)

	if errHash != nil {
		return nil, errHash
	}

	user := User{Email: newUser.Email, Password: *hash}
	result := services.PostgresSQLDB.Clauses(clause.Returning{}).Select("Email", "Password").Create(&user)
	fmt.Println(result.RowsAffected)
	fmt.Println(&user)

	if result.Error != nil {
		modules.Logger.Error(result.Error)
		return nil, result.Error
	}

	return &user, nil

}

thanks to @TheFool comment for this find.

答案2

得分: -1

据我所知,GORM不支持除了整数自增之外的其他主键类型。所以要么你保留在PostgreSQL端处理(就像你已经做的那样),要么在创建对象之前在Go中生成自己的UUID(使用BeforeCreate钩子)。

另外,gorm.Model的定义如下:

type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

你不一定非要使用它,但如果不使用的话,你需要在主键字段上添加gorm:"primaryKey"。默认情况下,主键字段是自增的,对于你的情况,我建议使用autoIncrement:false来禁用它。

type User struct {
    Id        uuid.UUID      `gorm:"primaryKey;autoIncrement:false" json:"id"`
    Email     string         `json:"email"`
    Password  string         `json:"password"`
}

由于你让PostgreSQL处理默认值(而不是GORM),你需要再次查询对象才能访问你的id UUID和created_at TIMESTAMP。

还要注意,你可以使用GormValuerInterface在创建时使用SQL表达式。但你仍然需要再次查询记录。(https://gorm.io/docs/data_types.html#GormValuerInterface)

如果你有兴趣在Go端处理所有内容,下面是一个使用和不使用gorm.Model的示例:

package main

import (
	"fmt"
	"time"

	"github.com/google/uuid"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

var DB *gorm.DB

type User struct {
	UUID      uuid.UUID `gorm:"primaryKey;autoIncrement:false"`
	Name      string
	CreatedAt time.Time
}

type UserWithGormModel struct {
	gorm.Model
	UUID uuid.UUID `gorm:"primaryKey;autoIncrement:false"`
	Name string
}

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {

	// 这只是一个示例,请参考https://pkg.go.dev/github.com/google/UUID以获取良好的用法
	u.UUID = uuid.New()

	u.CreatedAt = time.Now()

	return
}

func (u *UserWithGormModel) BeforeCreate(tx *gorm.DB) (err error) {

	// 这只是一个示例,请参考https://pkg.go.dev/github.com/google/UUID以获取良好的用法
	u.UUID = uuid.New()

	return
}

func ConnectDatabase() {

	database, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}

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

	database.AutoMigrate(&User{}, &UserWithGormModel{})

	DB = database
}

func main() {
	ConnectDatabase()

	user := User{Name: "Bob"}
	DB.Create(&user)
	fmt.Printf("User{UUID: %s, User.Name: %s, CreatedAt: %s}\n", user.UUID, user.Name, user.CreatedAt)

	user2 := UserWithGormModel{Name: "John"}
	DB.Create(&user2)
	fmt.Printf("UserWithGormModel{UUID: %s, Name: %s, CreatedAt: %s}\n", user2.UUID, user2.Name, user2.CreatedAt)
}

输出:

User{UUID: 8551636a-540f-4733-8179-3f0cc45daf4f, User.Name: Bob, CreatedAt: 2022-06-28 11:40:10.9225724 +0200 CEST}
UserWithGormModel{UUID: 2a6f94bd-a42b-4316-90be-0e93ea5091a6, Name: John, CreatedAt: 2022-06-28 11:40:10.9318004 +0200 CEST}
英文:

As far as I know, GORM doesn't support anything else that integer auto increment for the primary key. So either you keep that on PostgreSQL side (like you did) or you generate your own UUID in Go before creating the object (with BeforeCreate hook).

Also, the gorm.Model definition is the following:

type Model struct {
ID        uint           `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}

You don't have to use it, but if you don't, add gorm:"primaryKey" to your primary key field. By default primary field have auto increment, for your case, I would recommend to disable it with autoIncrement:false.

type User struct {
Id        uuid.UUID      `gorm:"primaryKey;autoIncrement:false" json:"id"`
Email     string         `json:"email"`
Password  string         `json:"password"`
}

As you let PostgreSQL handle default value (and not GORM), you have to query again the object in order to access your id UUID and created_at TIMESTAMP.

Also note that you can use GormValuerInterface to use an SQL Expr on creation. But you will still have to query again your record. (https://gorm.io/docs/data_types.html#GormValuerInterface)

In case you are interested to handle all on Go side, here an example with and without gorm.Model.

package main
import (
"fmt"
"time"
"github.com/google/uuid"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var DB *gorm.DB
type User struct {
UUID      uuid.UUID `gorm:"primaryKey;autoIncrement:false"`
Name      string
CreatedAt time.Time
}
type UserWithGormModel struct {
gorm.Model
UUID uuid.UUID `gorm:"primaryKey;autoIncrement:false"`
Name string
}
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
// This is an example, refer to https://pkg.go.dev/github.com/google/UUID for good usage
u.UUID = uuid.New()
u.CreatedAt = time.Now()
return
}
func (u *UserWithGormModel) BeforeCreate(tx *gorm.DB) (err error) {
// This is an example, refer to https://pkg.go.dev/github.com/google/UUID for good usage
u.UUID = uuid.New()
return
}
func ConnectDatabase() {
database, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
if err != nil {
panic("Failed to connect to database!")
}
database.AutoMigrate(&User{}, &UserWithGormModel{})
DB = database
}
func main() {
ConnectDatabase()
user := User{Name: "Bob"}
DB.Create(&user)
fmt.Printf("User{UUID: %s, User.Name: %s, CreatedAt: %s}\n", user.UUID, user.Name, user.CreatedAt)
user2 := UserWithGormModel{Name: "John"}
DB.Create(&user2)
fmt.Printf("UserWithGormModel{UUID: %s, Name: %s, CreatedAt: %s}\n", user2.UUID, user2.Name, user2.CreatedAt)
}

Output:

User{UUID: 8551636a-540f-4733-8179-3f0cc45daf4f, User.Name: Bob, CreatedAt: 2022-06-28 11:40:10.9225724 +0200 CEST}
UserWithGormModel{UUID: 2a6f94bd-a42b-4316-90be-0e93ea5091a6, Name: John, CreatedAt: 2022-06-28 11:40:10.9318004 +0200 CEST}

huangapple
  • 本文由 发表于 2022年6月26日 02:18:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/72756319.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定