聊聊gorm的OnConflict

本文深入解析了gorm库中OnConflict功能的实现原理及其使用方法。详细介绍了OnConflict结构体的各项属性,包括Columns、Where、OnConstraint等,并通过具体示例展示了如何在实际应用中运用这些属性来构建SQL语句。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文主要研究一下gorm的OnConflict

OnConflict

gorm.io/gorm@v1.20.11/clause/on_conflict.go

type OnConflict struct {
	Columns      []Column
	Where        Where
	OnConstraint string
	DoNothing    bool
	DoUpdates    Set
	UpdateAll    bool
}

func (OnConflict) Name() string {
	return "ON CONFLICT"
}

// Build build onConflict clause
func (onConflict OnConflict) Build(builder Builder) {
	if len(onConflict.Columns) > 0 {
		builder.WriteByte('(')
		for idx, column := range onConflict.Columns {
			if idx > 0 {
				builder.WriteByte(',')
			}
			builder.WriteQuoted(column)
		}
		builder.WriteString(`) `)
	}

	if len(onConflict.Where.Exprs) > 0 {
		builder.WriteString("WHERE ")
		onConflict.Where.Build(builder)
		builder.WriteByte(' ')
	}

	if onConflict.OnConstraint != "" {
		builder.WriteString("ON CONSTRAINT ")
		builder.WriteString(onConflict.OnConstraint)
		builder.WriteByte(' ')
	}

	if onConflict.DoNothing {
		builder.WriteString("DO NOTHING")
	} else {
		builder.WriteString("DO UPDATE SET ")
		onConflict.DoUpdates.Build(builder)
	}
}

// MergeClause merge onConflict clauses
func (onConflict OnConflict) MergeClause(clause *Clause) {
	clause.Expression = onConflict
}

OnConflict定义了Columns、Where、OnConstraint、DoNothing、DoUpdates、UpdateAll属性;Build方法会根据这些属性拼装sql,如果是DoNothing则追加DO NOTHING,否则追加DO UPDATE SET

Expression

gorm.io/gorm@v1.20.11/clause/set.go

type Set []Assignment

type Assignment struct {
	Column Column
	Value  interface{}
}

func (set Set) Name() string {
	return "SET"
}

func (set Set) Build(builder Builder) {
	if len(set) > 0 {
		for idx, assignment := range set {
			if idx > 0 {
				builder.WriteByte(',')
			}
			builder.WriteQuoted(assignment.Column)
			builder.WriteByte('=')
			builder.AddVar(builder, assignment.Value)
		}
	} else {
		builder.WriteQuoted(PrimaryColumn)
		builder.WriteByte('=')
		builder.WriteQuoted(PrimaryColumn)
	}
}

// MergeClause merge assignments clauses
func (set Set) MergeClause(clause *Clause) {
	copiedAssignments := make([]Assignment, len(set))
	copy(copiedAssignments, set)
	clause.Expression = Set(copiedAssignments)
}

func Assignments(values map[string]interface{}) Set {
	keys := make([]string, 0, len(values))
	for key := range values {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	assignments := make([]Assignment, len(keys))
	for idx, key := range keys {
		assignments[idx] = Assignment{Column: Column{Name: key}, Value: values[key]}
	}
	return assignments
}

func AssignmentColumns(values []string) Set {
	assignments := make([]Assignment, len(values))
	for idx, value := range values {
		assignments[idx] = Assignment{Column: Column{Name: value}, Value: Column{Table: "excluded", Name: value}}
	}
	return assignments
}

的DoUpdates属性是Set类型,Set类型实际是Assignment数组;其Build方法会组装assignment的sql

实例

func onConflictDemo(db *gorm.DB) {
	entities := []DemoEntity{
		{
			Model: gorm.Model{ID: 1},
			Name:  "coco",
		},
		{
			Model: gorm.Model{ID: 2},
			Name:  "bear",
		},
	}
	result := db.Debug().Create(&entities)
	b, _ := json.Marshal(entities)
	log.Println("data:", string(b))
	log.Println("result.RowsAffected:", result.RowsAffected, "result.Error:", result.Error)

	if err := db.Debug().Clauses(clause.OnConflict{DoNothing: true}).Create(&entities).Error; err != nil {
		panic(err)
	}
}

输出

2021/01/17 20:03:31 /demo.go:53 UNIQUE constraint failed: demo_entities.id
[0.487ms] [rows:0] INSERT INTO `demo_entities` (`created_at`,`updated_at`,`deleted_at`,`name`,`id`) VALUES ("2021-01-17 20:03:31.711","2021-01-17 20:03:31.711",NULL,"coco",1),("2021-01-17 20:03:31.711","2021-01-17 20:03:31.711",NULL,"bear",2)
2021/01/17 20:03:31 data: [{"ID":1,"CreatedAt":"2021-01-17T20:03:31.71143+08:00","UpdatedAt":"2021-01-17T20:03:31.71143+08:00","DeletedAt":null,"Name":"coco"},{"ID":2,"CreatedAt":"2021-01-17T20:03:31.71143+08:00","UpdatedAt":"2021-01-17T20:03:31.71143+08:00","DeletedAt":null,"Name":"bear"}]
2021/01/17 20:03:31 result.RowsAffected: 0 result.Error: UNIQUE constraint failed: demo_entities.id

2021/01/17 20:03:31 /demo.go:58
[0.123ms] [rows:0] INSERT INTO `demo_entities` (`created_at`,`updated_at`,`deleted_at`,`name`,`id`) VALUES ("2021-01-17 20:03:31.711","2021-01-17 20:03:31.711",NULL,"coco",1),("2021-01-17 20:03:31.711","2021-01-17 20:03:31.711",NULL,"bear",2) ON CONFLICT DO NOTHING

小结

gorm的OnConflict定义了Columns、Where、OnConstraint、DoNothing、DoUpdates、UpdateAll属性;Build方法会根据这些属性拼装sql,如果是DoNothing则追加DO NOTHING,否则追加DO UPDATE SET

doc

### GORM 基本概念与功能 GORM 是一种用于 Go 的对象关系映射 (ORM) 工具,它提供了简单而强大的接口来操作数据库[^1]。通过 GORM,开发者可以轻松实现数据模型定义、迁移以及 CRUD 操作等功能。 #### 安装 GORM 和驱动程序 要使用 GORM,首先需要安装其依赖项和对应的数据库驱动程序。以下是针对不同数据库的安装命令: ```bash go get -u gorm.io/gorm go get -u gorm.io/driver/mysql # 对于 MySQL 数据库 go get -u gorm.io/driver/postgres # 对于 PostgreSQL 数据库 go get -u gorm.io/driver/sqlite # 对于 SQLite 数据库 ``` #### 创建连接到数据库实例 下面是一个简单的例子,展示如何创建并初始化一个 GORM 实例并与数据库建立连接: ```go package main import ( "fmt" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func main() { db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) if err != nil { fmt.Println(err) return } fmt.Println("Connected to the database successfully!") } ``` 此代码片段展示了如何打开一个新的 SQLite 连接,并打印成功消息如果一切正常运行的话[^1]。 #### 自动迁移支持 GORM 提供了一个自动迁移的功能,允许基于结构体字段自动生成表结构变化脚本。这使得管理数据库模式变得更加容易。 ```go type User struct { ID uint Name string Age int } db.AutoMigrate(&User{}) // 将会根据 `User` 结构体创建对应的数据表 ``` 上面的例子说明了当调用 AutoMigrate 方法时,GORM 如何依据给定的结构体去更新或者新建相应的表格。 #### 执行基本查询操作 执行基础增删改查的操作也非常直观,在这里提供几个常见的示例: 增加记录: ```go user := User{Name: "John Doe", Age: 30} result := db.Create(&user) fmt.Println(result.RowsAffected) // 输出受影响行数 ``` 查找单条记录: ```go var user User db.First(&user, 1) // 查找 ID=1 的第一条记录 fmt.Printf("%v\n", user.Name) ``` 删除记录: ```go db.Delete(&user, 1) // 删除 ID=1 的记录 ``` 这些只是 GORM 功能的一小部分演示;更多高级特性如事务处理、嵌套预取等都可以在其官方文档找到详细描述[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值