Thanks to visit codestin.com
Credit goes to github.com

Skip to content

BeforeSave is executed twice in transaction #6285

@ras0q

Description

@ras0q

GORM Playground Link

go-gorm/playground#593

Description

When you save a model which BeforeSave is implemented in a transaction, BeforeSave will be executed twice.

package main_test

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func Test_main(t *testing.T) {
	dsn := "root:pass@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn))
	assert.NoError(t, err)
	assert.NoError(t, db.AutoMigrate(&User{}, &Token{}))

	u := User{
		Name: "user",
		Token: Token{
			Content: "token",
		},
	}
	u1, err := saveUser(db, &u)
	assert.NoError(t, err)
	assert.Equal(t, "token_encrypted", u1.Token.Content)

	u = User{
		ID:   u.ID,
		Name: "user",
		Token: Token{
			Content: "token2",
		},
	}
	u2, err := saveUser(db, &u)
	assert.NoError(t, err)
	assert.Equal(t, "token2_encrypted", u2.Token.Content) // FAIL: actual is token2_encrypted_encrypted
}

func saveUser(db *gorm.DB, u *User) (*User, error) {
	var newUser User
	if err := db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Debug().Session(&gorm.Session{FullSaveAssociations: true}).Save(u).Error; err != nil {
			return err
		}

		if err := tx.Preload("Token").First(&newUser, u.ID).Error; err != nil {
			return err
		}

		return nil
	}); err != nil {
		return nil, err
	}

	return &newUser, nil
}

// - Models

type User struct {
	ID    int    `gorm:"primary_key"`
	Name  string `gorm:"type:varchar(100)"`
	Token Token  `gorm:"foreignKey:UserID"`
}

type Token struct {
	UserID  int    `gorm:"primary_key"`
	Content string `gorm:"type:varchar(100)"`
}

// This method should be called only once
func (t *Token) BeforeSave(tx *gorm.DB) error {
	t.Content += "_encrypted"
	return nil
}
$ go test main_test.go

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[2.085ms] [rows:1] INSERT INTO `tokens` (`content`,`user_id`) VALUES ('token_encrypted',7) ON DUPLICATE KEY UPDATE `content`=VALUES(`content`)

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[5.084ms] [rows:1] INSERT INTO `users` (`name`) VALUES ('user')

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[2.312ms] [rows:2] INSERT INTO `tokens` (`content`,`user_id`) VALUES ('token2_encrypted',7) ON DUPLICATE KEY UPDATE `content`=VALUES(`content`)

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[4.827ms] [rows:0] UPDATE `users` SET `name`='user' WHERE `id` = 7

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[2.382ms] [rows:2] INSERT INTO `tokens` (`content`,`user_id`) VALUES ('token2_encrypted_encrypted',7) ON DUPLICATE KEY UPDATE `content`=VALUES(`content`)

2023/05/01 11:00:35 /home/ras/misc/go/gorm/main_test.go:42
[5.107ms] [rows:0] INSERT INTO `users` (`name`,`id`) VALUES ('user',7) ON DUPLICATE KEY UPDATE `name`=VALUES(`name`)
--- FAIL: Test_main (0.12s)
    main_test.go:36: 
                Error Trace:    /home/ras/misc/go/gorm/main_test.go:36
                Error:          Not equal: 
                                expected: "token2_encrypted"
                                actual  : "token2_encrypted_encrypted"
                            
                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -token2_encrypted
                                +token2_encrypted_encrypted
                Test:           Test_main
FAIL
FAIL    command-line-arguments  0.125s
FAIL

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions