本文主要是介绍快速学习gorm 框架,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
go-orm
介绍
godemo是一款go目前主流的orm框架
软件架构
官方文档 GORM - The fantastic ORM library for Golang, aims to be developer friendly.
使用说明
安装教程
1.设置代理
go env -w GOPROXY=https://goproxy.cn,direct
2.打算使用gin 搭配gorom 进行学习模拟web开发
## 安装gin
go get -u github.com/gin-gonic/gin
#数据库驱动和orm
go get gorm.io/driver/mysql
go get gorm.io/gorm
连接
数据库连
// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情dsn := "用户名:密码@tcp(127.0.0.1:3306)/数据库名?charset=utf8mb4&parseTime=True&loc=Local"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
添加配置 自定义驱动
import (_ "example.com/my_mysql_driver""gorm.io/driver/mysql""gorm.io/gorm"
)db, err := gorm.Open(mysql.New(mysql.Config{DriverName: "my_mysql_driver",DSN: "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local", // data source name, 详情参考:https://github.com/go-sql-driver/mysql#dsn-data-source-name
}), &gorm.Config{})
// 日志配置var mysqlLogger logger.Interface// 要显示的日志等级mysqlLogger = logger.Default.LogMode(logger.Info)db, _ := gorm.Open(mysql.New(mysql.Config{// leave blank for default driverDSN: "root:111111@tcp(127.0.0.1:3306)/ruiji?charset=utf8&parseTime=True&loc=Local", // data source name, 详情参考:https://github.com/go-sql-driver/mysql#dsn-data-source-name}), &gorm.Config{Logger: mysqlLogger, //注册SkipDefaultTransaction: true, // 禁用默认事务})sqlDB, _ := db.DB()// 设置空闲连接池中的最大连接数。ol.sqlDB.SetMaxIdleConns(10)// 设置与数据库的最大打开连接数sqlDB.SetMaxOpenConns(100)// 设置可以重复使用连接的最长时间。sqlDB.SetConnMaxLifetime(time.Hour)
CRUD
假如有model
type Student struct {ID uint // 默认使用ID作为主键//设置字段长度为2Name string `gorm:"size:10"`Sex string `gorm:"size:2"`Email *string // 使用指针是为了存空值Age int
}
gorm中的tag约束 除开上述 还有以下 可以做到创建表时候约束
创建表
如果存在也不会报错 如果结构体和数据库模型 不一样则执行的是alter 更改数据库的操作
使用 AutoMigrate 方法自动迁移你的 schema,这将会创建数据库中不存在的表。
db.AutoMigrate(&Product{})
插入数据
单挑数据插入
db.Create(s) s是该映射结构体的指针
func insertStudent(c *gin.Context) {s := new(Student)if err := c.ShouldBindQuery(s); err != nil {return} else {db.Create(s)c.JSON(200, gin.H{"data": s,"message": "创建用户成功",})}
}
批量插入 同样 参数传递结构体切片即可
func bentchInserter(c *gin.Context) {students := make([]Student, 10)for i := 1; i <= 20; i++ {students = append(students, Student{Name: "测试" + strconv.Itoa(i),Age: i,Sex: "男",//Email: nil,})}db.Create(&students)c.JSON(200, gin.H{"data": students,"message": "批量创建用户成功",})
}
插入后该指针结构体的id字段就会被填充
删除
api delete
//第一个参数 是映射结构体指针 第二个参数是id 只有空结构体指针时候代表删除全表
db.Delete(s, id)
等效
s.ID = uint(parsedID)
db.Delete(s)
Or where 拼接查询条件 可以使用?方式进行拼接sql
db.Or("age<?", 18).Where("name", "测试张三").Delete(s, id) // 根据主键删除
日志
[4.352ms] [rows:1] DELETE FROM students
WHERE name
= ‘测试张三’ AND students
.id
= ‘1’
注意
gorm 对于不携带条件的批量修改字段 比如UPDATE
students
SETsex
=‘妈妈’() ,DELETE FROMstudents
这俩个sql正常来说是会改变整个表的记录,但是gorom 并不会去执行sql1 go中的api
s := new(Student) db.Model(s).Update("sex", "妈妈")
sq2
db.Delete(s)
但是加一个简单的条件
db.Model(s).Where("1 = 1").Update("sex", "男")
[7.314ms] [rows:26] UPDATE
students
SETsex
=‘男’ WHERE 1 = 1 日志输出却可以成功改变全表记录
GORM 中无条件批量操作的行为总结
在 GORM 中,某些批量操作(如 Delete
和 Update
)具有防护机制,以防止意外更改整个数据库。以下是关键行为的总结:
- 无条件删除:
- 当你尝试在不指定条件的情况下删除记录时,GORM 可能会生成类似
DELETE FROM 'students'
的 SQL 语句,但实际上不会删除任何记录。输出会显示rows:0
,以防止意外的大规模删除操作。
- 当你尝试在不指定条件的情况下删除记录时,GORM 可能会生成类似
- 无条件更新:
- 使用 Struct 和
Updates
: 当你使用db.Model(&Student{}).Updates(Student{Sex: "未"})
时,GORM 会尝试更新表中所有记录的字段。但它会执行检查,避免在没有条件的情况下进行大规模更新。如果没有指定条件,这种操作通常不会执行。 - 使用
Update
更新单个字段: 如果你执行db.Model(&Student{}).Update("sex", "未")
,GORM 的日志会显示它计划更新所有记录的sex
字段,但如果没有明确的条件,它仍不会执行。
- 使用 Struct 和
- 使用虚拟条件的操作:
- 使用虚拟条件: 通过添加像
db.Model(s).Where("1 = 1").Update("sex", "妈妈")
这样的条件,更新操作会执行,因为存在条件(即使是一个简单的条件)。
- 使用虚拟条件: 通过添加像
关键提示
映射指针没有id就是全表操作 有id 还设置id查询条件就是and 拼接
修改
修改单个字段 update api
s := &Student{ID: uint(id)} // 创建 Student 结构体并设置 ID 无论批量还是单个更新值字段,都需要id 不然无法找到数据
//如果没有id 就是对全表操作 但是没有条件的全表操作不会执行db.Model(s).Update("sex", "未")
修改多个字段 updates api
db.Model(s).Where("id = ?", id).Where("age > ?", 10).Updates(Student{Name: "测试张三", Age: 18, Sex: "男"}) // 根据主
批量修改全表,只需要一个空结构体映射指针和简单的条件
db.Model(s).Where("1 = 1").Updates(Student{Name: "测试张三", Age: 18, Sex: "男"})
查询
单记录
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
批量 find
// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
虽然go中有select来简写查询语句 但是我还是喜欢where拼接的方式
DB.Select("name", "age").Find(&users)
在 GORM 中,Scan
方法用于将查询结果扫描到 Go 变量中。它允许你执行原始 SQL 查询,并将查询结果填充到指定的结构体或切片中。Scan
方法通常与 Raw
方法结合使用,用于处理更复杂的 SQL 查询。
可以根据这个方法来写原生sql
db.Raw("SELECT * FROM students WHERE id = ?", 1).Scan(&student)
分组查询
DB.Table("students").Select("count(id)").Group("gender").Scan(&ageList)
分页查询
// 一页两条,第1页
DB.Limit(2).Offset(0).Find(&users)
fmt.Println(users)
// 第2页
DB.Limit(2).Offset(2).Find(&users)
fmt.Println(users)
// 第3页 pagesize*(页号-1)
DB.Limit(2).Offset(4).Find(&users)
fmt.Println(users)
多表联查
下面是之前示例的 GORM 代码,并在每个示例的注释中添加了对应的 SQL 转换。
1. 多表联查 (JOIN Queries)
var users []User
db.Joins("JOIN orders ON orders.user_id = users.id").Where("orders.status = ?", "completed").Find(&users)// SQL: SELECT * FROM users
// JOIN orders ON orders.user_id = users.id
// WHERE orders.status = 'completed';
2. 范围查询 (Range Queries)
var orders []Order
db.Where("created_at BETWEEN ? AND ?", startDate, endDate).Find(&orders)// SQL: SELECT * FROM orders
// WHERE created_at BETWEEN 'startDate' AND 'endDate';
3. 子查询 (Subqueries)
subQuery := db.Table("orders").Select("AVG(amount)").Where("user_id = ?", userID).SubQuery()var orders []Order
db.Where("amount > ?", subQuery).Find(&orders)// SQL: SELECT * FROM orders
// WHERE amount > (SELECT AVG(amount) FROM orders WHERE user_id = userID);
4. 多条件复杂查询 (Complex Queries with Multiple Conditions)
var users []User
db.Where("age BETWEEN ? AND ?", minAge, maxAge).Where("region = ?", region).Where("status = ?", "active").Find(&users)// SQL: SELECT * FROM users
// WHERE age BETWEEN minAge AND maxAge
// AND region = 'region'
// AND status = 'active';
5. 联合查询 (UNION Queries)
var results []User
db.Raw("SELECT * FROM users WHERE region = ? UNION SELECT * FROM users WHERE region = ?", region1, region2).Scan(&results)// SQL: SELECT * FROM users WHERE region = 'region1'
// UNION
// SELECT * FROM users WHERE region = 'region2';
6. 预加载和条件加载 (Preload with Conditions)
var users []User
db.Preload("Orders", "status = ?", "completed").Find(&users)// SQL: SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (userIDs) AND status = 'completed';
7. 分组与聚合查询 (Group By and Aggregate Functions)
type Result struct {UserID uintCount int
}var results []Result
db.Model(&Order{}).Select("user_id, COUNT(*) as count").Group("user_id").Having("count > ?", 5).Find(&results)// SQL: SELECT user_id, COUNT(*) as count
// FROM orders
// GROUP BY user_id
// HAVING count > 5;
8. 关联表条件查询 (Conditions on Related Tables)
var users []User
db.Joins("JOIN orders ON orders.user_id = users.id").Joins("JOIN order_items ON order_items.order_id = orders.id").Joins("JOIN products ON products.id = order_items.product_id").Where("products.name = ?", "Product A").Find(&users)// SQL: SELECT users.* FROM users
// JOIN orders ON orders.user_id = users.id
// JOIN order_items ON order_items.order_id = orders.id
// JOIN products ON products.id = order_items.product_id
// WHERE products.name = 'Product A';
9. 使用 Raw SQL 实现复杂查询 (Using Raw SQL for Complex Queries)
var orders []Order
db.Raw("SELECT * FROM orders WHERE user_id = ? AND amount > ? ORDER BY created_at DESC", userID, minAmount).Scan(&orders)// SQL: SELECT * FROM orders
// WHERE user_id = userID
// AND amount > minAmount
// ORDER BY created_at DESC;
10. 分页查询 (Pagination Queries)
var orders []Order
db.Where("user_id = ?", userID).Limit(10).Offset(20).Order("created_at DESC").Find(&orders)// SQL: SELECT * FROM orders
// WHERE user_id = userID
// ORDER BY created_at DESC
// LIMIT 10 OFFSET 20;
gorm中有很多相似的api 但是大致都可以实现想要的sql效果下面总结一下
Find
-
功能:用于查询表中的所有记录或符合条件的记录,并将结果扫描到切片中。
-
用法:
var students []Student db.Find(&students)
你也可以结合条件使用
Find
:db.Where("age > ?", 18).Find(&students)
-
适用场景:用于检索表中的多条记录,结果会自动填充到结构体切片中。
2. First
-
功能:查询表中的第一条记录(按主键排序)。
-
用法:
var student Student db.First(&student)
你也可以结合条件使用
First
:db.Where("id = ?", 1).First(&student)
-
适用场景:用于检索表中的单条记录(通常是按主键或条件查询到的第一条记录)。
3. Last
-
功能:查询表中的最后一条记录(按主键排序)。
-
用法:
var student Student db.Last(&student)
你也可以结合条件使用
Last
:db.Where("age > ?", 18).Last(&student)
-
适用场景:用于检索表中的最后一条记录(通常是按主键或条件查询到的最后一条记录)。
4. Take
-
功能:查询表中的任意一条记录(不一定是第一条)。
-
用法:
var student Student db.Take(&student)
你也可以结合条件使用
Take
:db.Where("age > ?", 18).Take(&student)
-
适用场景:用于检索表中的任意一条记录,常用于测试或简化查询。
5. Raw
和 Scan
-
功能:
Raw
:执行原始 SQL 查询。Scan
:将原始 SQL 查询的结果扫描到 Go 变量中。
-
用法:
var students []Student db.Raw("SELECT * FROM students WHERE age > ?", 18).Scan(&students)
-
适用场景:用于执行复杂的原始 SQL 查询,特别是当 GORM 的链式调用不够灵活时。
Scan
用于将查询结果填充到自定义结构体或切片中。
6. Model
-
功能:用于指定模型类型,通常在查询链中用于指定操作的表。
-
用法:
var students []Student db.Model(&Student{}).Where("age > ?", 18).Find(&students)
-
适用场景:在需要在查询中指定模型时使用,比如在链式调用中切换模型。
7. Table
-
功能:用于指定操作的表名,通常用于原始 SQL 查询。
-
用法:
var students []Student db.Table("students").Where("age > ?", 18).Find(&students)
-
适用场景:用于在查询中指定表名,特别是当表名不是结构体名称或当执行原始 SQL 查询时。
感觉和mybatis-plus差不多其中的链式查询
最后写一个动态sql执行
func GetStudents(name string, age int, gender string) ([]Student, error) {var students []Studentdb := db.Model(&Student{})if name != "" {db = db.Where("name = ?", name)}if age > 0 {db = db.Where("age = ?", age)}if gender != "" {db = db.Where("gender = ?", gender)}err := db.Find(&students).Errorreturn students, err
}
模型定义
和mybatis-plus的思路一样
一对多
可以采用聚合集合的方式
type User struct {ID uint `gorm:"size:4"`Name string `gorm:"size:8"`Articles []Article // 用户拥有的文章列表
}type Article struct {ID uint `gorm:"size:4"`Title string `gorm:"size:16"`UserID uint // 属于 这里的类型要和引用的外键类型一致,包括大小User User // 属于
}
关联外键
type User struct {ID uint `gorm:"size:4"`Name string `gorm:"size:8"`Articles []Article `gorm:"foreignKey:UID"` // 用户拥有的文章列表
}type Article struct {ID uint `gorm:"size:4"`Title string `gorm:"size:16"`UID uint // 属于User User `gorm:"foreignKey:UID"` // 属于
}
事务
java中可以通过@Trantial注解完成这个效果 gorm中可以通过手动和自动方式实现
手动实现
func CreateUserWithOrders(db *gorm.DB) error {return db.Transaction(func(tx *gorm.DB) error {// 创建用户user := User{Name: "John"}if err := tx.Create(&user).Error; err != nil {return err // 回滚事务}// 创建订单order := Order{UserID: user.ID, Amount: 100}if err := tx.Create(&order).Error; err != nil {return err // 回滚事务}// 如果一切正常,事务会自动提交return nil})
}
多表操作时候才transaction中处理 如果参数异常就返回 数据库进行回滚 也可以db.Begin() api方式
func CreateUserWithOrders(db *gorm.DB) error {tx := db.Begin()// 创建用户user := User{Name: "John"}if err := tx.Create(&user).Error; err != nil {tx.Rollback() // 回滚事务return err}// 创建订单order := Order{UserID: user.ID, Amount: 100}if err := tx.Create(&order).Error; err != nil {tx.Rollback() // 回滚事务return err}return tx.Commit().Error // 提交事务
}
这篇关于快速学习gorm 框架的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!