go-gorm / optimisticlock Goto Github PK
View Code? Open in Web Editor NEWoptimistic lock plugin for gorm
License: MIT License
optimistic lock plugin for gorm
License: MIT License
在使用gorm-gen时,出现panic
生成语句:
g.GenerateModel("user", optVersionType := gen.FieldType("version", "optimisticlock.Version"))
生成的模型:
type User struct {
ID int64 `gorm:"column:id;primaryKey" json:"id"` // 主键
// 忽略其它字段
Version optimisticlock.Version `gorm:"column:version;not null" json:"version"` // 乐观锁
}
执行操作:
q.User.WithContext(ctx).Create(&model.User{ID: 123})
出现错误:
panic: reflect: call of reflect.Value.Field on slice Value [recovered]
panic: reflect: call of reflect.Value.Field on slice Value
panic({0x13f5d40, 0xc000438060})
C:/Users/NorthLan/.g/go/src/runtime/panic.go:838 +0x207
reflect.Value.Field({0x13d3160?, 0xc000438048?, 0x13b7da5?}, 0x0?)
C:/Users/NorthLan/.g/go/src/reflect/value.go:1220 +0xef
gorm.io/gorm/schema.(*Field).setupValuerAndSetter.func1({0xc000132c00?, 0x13?}, {0x13d3160?, 0xc000438048?, 0xf1be5d?})
C:/Users/NorthLan/go/pkg/mod/gorm.io/gorm@v1.23.6/schema/field.go:446 +0x65
gorm.io/plugin/optimisticlock.VersionCreateClause.ModifyStatement({0x20?}, 0xc00022d340)
C:/Users/NorthLan/go/pkg/mod/gorm.io/plugin/optimisticlock@v1.0.7/version.go:68 +0x4f
gorm.io/gorm.(*Statement).AddClause(0xc00022d340, {0x155ab08, 0xc000413680})
C:/Users/NorthLan/go/pkg/mod/gorm.io/gorm@v1.23.6/statement.go:257 +0x46
gorm.io/gorm/callbacks.Create.func1(0xc000432330)
C:/Users/NorthLan/go/pkg/mod/gorm.io/gorm@v1.23.6/callbacks/create.go:47 +0xf88
gorm.io/gorm.(*processor).Execute(0xc0001ffae0, 0xc00040fb28?)
C:/Users/NorthLan/go/pkg/mod/gorm.io/gorm@v1.23.6/callbacks.go:130 +0x433
gorm.io/gorm.(*DB).Create(0x144e1c0?, {0x13d3160?, 0xc000438048})
C:/Users/NorthLan/go/pkg/mod/gorm.io/gorm@v1.23.6/finisher_api.go:24 +0xa5
gorm.io/gen.(*DO).Save(0xc00040fc88, {0x13d3160, 0xc000438048})
C:/Users/NorthLan/go/pkg/mod/gorm.io/gen@v0.3.8/do.go:553 +0xba
看情况应该是 version.go
中这个方法的异常,大概是通过反射寻找非零值的 Version
func (v VersionCreateClause) ModifyStatement(stmt *gorm.Statement) {
var value int64 = 1
if val, zero := v.Field.ValueOf(stmt.Context, stmt.ReflectValue); !zero {
if version, ok := val.(Version); ok {
value = version.Int64
}
}
stmt.SetColumn(v.Field.DBName, value)
}
摸一摸具体原因可能是:
因为gorm-gen的 Create 方法是传递的可变参数,stmt.ReflectValue
的 Kind是23(slice)而非25(struct)出现的错误。
// gorm-gen生成的Create方法
// u.DO.Create底层调用的还是 gorm.DB 的 Create 方法
func (u userDo) Create(values ...*model.User) error {
if len(values) == 0 {
return nil
}
return u.DO.Create(values)
}
特别地,如果使用 gorm.DB 的
Create
就不会存在问题,例:db.Create(&user)
特别地2,如果使用 gorm.DB 的
Create
,但是传入slice,一样也会出现问题。例:db.Create(&[]model.User{user})
由此可得,修改修改 ModifyStatement
应该就好了。
有些字段是不存在并发冲突风险的, 开发者会直接使用 map 来更新.
result = db.Model(&account).Updates(map[string]interface{}{"video_profit": account.VideoProfit})
但是目前的机制, 会自动增加 version 到 where 中导致更新失败
希望提供额外的选项, 例如说 IgnoreLockVersion, 可以在使用 map 或者 struct 更新时, 忽略乐观锁的限制, 例如 where 中不再与偶 version 的判断
func TestUpdateWithLock(t *testing.T) {
db := initializers.DB.WithContext(context.Background())
err := db.Transaction(func(tx *gorm.DB) error {
account := models.Account{}
tx.Model(&account).Where("id = ?", 1).First(&account)
account.CostDiamond(decimal.NewFromInt(1))
if result := tx.Updates(&account) ; result.RowsAffected == 0 {
return errors.New("更新用户账户失败")
}
account.CostGold(decimal.NewFromInt(1))
if result := tx.Updates(&account) ; result.RowsAffected == 0 {
return errors.New("更新用户账户失败")
}
return nil
})
if err != nil {
fmt.Println("error", err.Error())
return
}
}
生成两条 SQL
UPDATE `accounts` SET `version`=`version`+1 WHERE `accounts`.`deleted_at` IS NULL AND `accounts`.`version` = 2 AND `id` = 1 {rows": 1}
UPDATE `accounts` SET `version`=`version`+1 WHERE `accounts`.`deleted_at` IS NULL AND `accounts`.`version` = 2 AND `id` = 1 {"rows": 0}
DB.Model(&user).Updates(&User{Age: 18, Version: optimisticlock.Version{Int64: 1}})
// UPDATE users
SET age
=18,version
=version
+1 WHERE users
.version
= 3 AND id
= 1
上述代码的执行结果是:
UPDATE users
SET age
=18,version
=version
+1
PR: #22 This reproduces the problem TestPostgres
passes and TestPostgres_FailedCase
fails.
I took your unit test and just added my own primary key. When I try to user.ID = "test1"
add this line to set a primary key, the test values because the version is set to 0. When i remove that line, the test pass with the version set to 1.
There is probably something that i'm not understanding with this...
type User struct {
ID string `gorm:"primarykey"`
Name string
Age uint
Version optimisticlock.Version
}
type User struct {
ID string `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
Name string
Age uint
Version optimisticlock.Version
}
func TestPostgres(t *testing.T) {
DB, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
require.Nil(t, err)
_ = DB.Migrator().DropTable(&User{})
_ = DB.AutoMigrate(&User{})
user := User{Name: "bob", Age: 20}
DB.Save(&user)
require.Equal(t, "bob", user.Name)
require.Equal(t, uint(20), user.Age)
require.Equal(t, int64(1), user.Version.Int64)
rows := DB.Model(&user).Update("age", 18).RowsAffected
require.Equal(t, int64(1), rows)
require.Nil(t, DB.First(&user).Error)
require.Equal(t, int64(2), user.Version.Int64)
require.Equal(t, uint(18), user.Age)
}
func TestPostgres_FailedCase(t *testing.T) {
DB, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
require.Nil(t, err)
_ = DB.Migrator().DropTable(&User{})
_ = DB.AutoMigrate(&User{})
user := User{Name: "bob", Age: 20, ID: "test"}
DB.Save(&user)
require.Equal(t, "bob", user.Name)
require.Equal(t, uint(20), user.Age)
require.Equal(t, int64(1), user.Version.Int64)
rows := DB.Model(&user).Update("age", 18).RowsAffected
require.Equal(t, int64(1), rows)
require.Nil(t, DB.First(&user).Error)
require.Equal(t, int64(2), user.Version.Int64)
require.Equal(t, uint(18), user.Age)
}
In the debugger, it looks like the save went through but version is 0.
When primary key is not set, you can see it set to version 1
Calling Unscoped does not add version=n
in where, which is the same as soft delete
DB.Unscoped().Model(&user).Update("age", 18)
// UPDATE `users` SET `age`=18,`version`=`version`+1 WHERE `id` = 1
// Account 用户钱包
type Account struct {
gorm.Model
UserID *uint
User *User
GoldAmount decimal.Decimal `gorm:"type:decimal(20,2)"` //金币余额
Version optimisticlock.Version `gorm:"default:0"`
}
var account Account
db.First(&account)
account.CostGold(decimal.NewFromFloat(100))
db.Updates(&account)
未引入 Version
字段时, db.Updates(&account)
工作起来没问题, 也符合 DDD 的常见写法; 引入后报错:
sql: UPDATE `accounts` SET `gold_amount`='3400',`Model`='{47 2022-03-05 12:40:14.574 +0800 CST 2022-03-09 01:20:14.138 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}}',`version`=`version`+1,`updated_at`='2022-03-09 01:20:14.182' WHERE `accounts`.`deleted_at` IS NULL AND `id` = 47
"error": "sql: converting argument $7 type: unsupported type gorm.Model, a struct"
加上本插件字段后,updateat字段的值不会自动变化
UPDATE s
SET created_at
="2022-09-02 15:42:12.845",id
=1,name
="name",updated_at
="2022-09-02 15:42:12.845",version
=version
+1 WHERE s
.version
= 1 AND id
= 1
去掉后正常
UPDATE s
SET name
="name",created_at
="2022-09-02 15:44:39.756",updated_at
="2022-09-02 15:44:40.763" WHERE id
= 1
期待多租户插件
When FullSaveAssociations
is enabled and an association is updated, the generated query is not modified by this plugin and therefore locking is not working even though version field is available for that association.
Is there any possible solution for this? (except disabling FullSaveAssociations
)
The version column is not incrementing when using the optimistic lock plugin with gorm v1.22.5. Below is the generated query.
UPDATE "" SET "name"='name-1',"created_timestamp"='2022-11-30 12:56:05.62',"updated_timestamp"='2022-11-30 14:49:04.531',"sync_timestamp"='0000-00-00 00:00:00',"created_by"='',"updated_by"='',"o_version"=1, "status"='Available' WHERE "uuid" = '327250f2-c77f-4996-b8a1-7ec7c76c1e0a' AND ""."o_version" = 1
"o_version" is name of the column used for optimistic locking.
type Common struct {
UUID uuid.UUID gorm:"primaryKey" json:"uuid,omitempty"
Name string gorm:"not null;unique" json:"name,omitempty"
OVersion optimisticlock.Version json:"oVersion,omitempty"
}
As you can see in the generated query the updated clause contains "o_version"=1, but according to implementation it should contain an SQL query of form "o_version"="o_version"+1 for optimistic locking to work properly. Your help will be greatly appreciated.
目前我是在用AfterUpdate钩子实现的,不过这样得给所有用到的类型都加上AfterUpdate,想知道有没有办法在插件里实现
The following error is reported when using postgres
ERROR: syntax error at or near \"=`\" (SQLSTATE 42601)
The sql as follows:
UPDATE
instances
SET
stage = 'Pending',
version = `version` + 1,
updated_at = '2022-03-28 18:15:24.572'
WHERE
AND deleted_at IS NULL
AND id = '123';
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.