Go-GORM操作mysql
前言
记录gorm 的一些基本用法,使用原生的话操作比较繁琐,使用gorm 的话可以让自己减少反复的写一些sql语句
gorm的一些特性
- 全功能的ORM
- 支持事务、嵌套事务
- 支持批量的删除
- 自定义 Logger
- 比较稳定
- SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
- ……
提示:以下是本篇文章正文内容,下面案例可供参考
一、数据库连接?
package main
import (
"fmt"
"sync"
"github.com/sirupsen/logrus"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var (
db *gorm.DB
linkOnce sync.Once
)
func linkDb() {
linkOnce.Do(func() {
dsn := "root:root@tcp(localhost:3306)/my_sql?charset=utf8mb4&parseTime=True&loc=Local"
var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
})
}
func init() {
linkDb()
}
- 连接mysql,并且用全局变量进行保存因为后面大量的操作需要用到
db sync.Once 保证全局唯一连接
二、为数据库操作添加日志记录
配置logrus ,并设定文件的创建的函数,这个与另一篇gin 日志中间件的几乎一样就不再详细讲解。
package main
import (
"fmt"
"os"
"path"
"github.com/sirupsen/logrus"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type MyWriter struct {
mlog *logrus.Logger
}
func NewMyWriter() *MyWriter {
logger := logrus.New()
logFilePath := ""
if dir, err := os.Getwd(); err == nil {
logFilePath = dir + "/logs/"
}
createFolder(logFilePath)
filenameFormat := time.Now().Format("2006-01-02")
src := createLogFile(logFilePath, filenameFormat)
logger.Out = src
logger.SetLevel(logrus.DebugLevel)
logger.SetFormatter(&logrus.TextFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
return &MyWriter{mlog: logger}
}
func (m *MyWriter) Printf(format string, v ...interface{}) {
logstr := fmt.Sprintf(format, v...)
m.mlog.Info(logstr)
}
func createFolder(logFilePath string) {
if err := os.MkdirAll(logFilePath, 0777); err != nil {
fmt.Println("文件夹创建失败")
panic(err)
}
}
func createLogFile(logFilePath, filenameFormat string) *os.File {
logFileName := filenameFormat + ".log"
fileName := path.Join(logFilePath, logFileName)
checkFile := func(filename string) {
if _, err := os.Stat(filename); err != nil {
if _, err := os.Create(filename); err != nil {
fmt.Println("打开文件失败")
panic(err)
}
}
}
checkFile(fileName)
src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
panic(err)
}
return src
}
修改上述数据库的链接,添加一个类似于中间件的东西
func linkDb() {
linkOnce.Do(func() {
newLogger := logger.New(
NewMyWriter(),
logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Info,
IgnoreRecordNotFoundError: true,
Colorful: false,
},
)
dsn := "root:ywh@tcp(121.41.27.5:3306)/my_sql?charset=utf8mb4&parseTime=True&loc=Local"
var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
panic(err)
}
})
}
**日志记录的结果
3. 数据库的基本操作
有了上述的数据库连接以及日志记录的基础,下面当然是一些增删改查以及事务的基本操作啦。
首先我们声明一个结构体Product , 后面的操作都是基于这个结构体的
type Product struct {
Code string `gorm:"primarykey"`
Price int `gorm:"price"`
}
func (t Product) TableName() string {
return "product"
}
gorm.Model 会额外增加id 、created_at 、updated_at 、deleted_at ,然后id 的话是默认作为一个主键的,这些去查看源代码都可以看到。
源代码截图
表的创建
func useGorm() {
if err := db.AutoMigrate(&Product{}); err != nil {
panic(err)
}
}
成功创建出的表
要注意的是创建的表名即为db.AutoMigrate(&Product{}) 中结构体Product 上的methodTableName 如果结构体没有这个method那么就是这个结构体对应的名字的的小写然后加上s。例如这里Product 上没有前面的TableName method那么创建的表名就是products
数据的插入
单条数据的插入
func insertData() {
rand.Seed(time.Now().UnixNano())
code := strconv.Itoa(rand.Intn(1000))
product := Product{
Code: "dasd" + code,
Price: rand.Intn(100),
}
if err := db.Model(&Product{}).Omit("Price").Create(&product).Error; err != nil {
fmt.Println("数据插入失败")
}
}
成功执行表对应的内容
- 因为在gorm中很好的支持了链式的操作,其实通过
db.Create(&product) 就可以把指定的数据插入到表中,而Model 的作用是可以指定输入插入数据对应的表,如果删除掉Model 而直接执行db..Create(&product) 的话插入表的信息以product 这个数据对应的结构体来指定,所以Model 可加可不加,加上去就以Model 优先。 - 字段的筛选,
Omit 可指定插入数据时要忽略的字段 - 字段的选择,
Select 可指定插入数据时要保留的字段
method | 作用 |
---|
Model | 指定后续要操作的数据表的基本信息(表名 、字段名 ),不使用该方法则以插入数据的结构体来指定 | Omit | 指定要忽略对的字段名 | Select | 指定要保留的字段名 | Create | 执行插入操作 |
多数据一次性插入
func insertDatas() {
rand.Seed(time.Now().UnixMilli())
var products []Product
for i := 0; i < 10; i++ {
products = append(products, Product{
Code: "dasd" + strconv.Itoa(rand.Intn(1000)),
Price: rand.Intn(100),
})
}
if err := db.Model(&Product{}).Create(&products).Error; err != nil {
fmt.Println("数据插入失败")
panic(err)
}
}
生成一次性执行的sql语句的日志
有时候可能要插入的数据太多,生成单句sql插入的方式可能性能较差,所以也支持数据的分批的插入
多数据的分批插入
func insertData() {
rand.Seed(time.Now().UnixMilli())
var products []Product
for i := 0; i < 10; i++ {
products = append(products, Product{
Code: "dasd" + strconv.Itoa(rand.Intn(1000)),
Price: rand.Intn(100),
})
}
db := db.Session(&gorm.Session{CreateBatchSize: 2})
if err := db.Model(&Product{}).Create(&products).Error; err != nil {
fmt.Println("数据插入失败")
panic(err)
}
}
分批插入的日志输出
数据的删除
单条数据的删除
func delData() {
var data Product
if err := db.Where("code = ?", "dasd104").Delete(&data).Error; err != nil {
panic(err)
}
db.Delete(&Product{}, "10")
db.Clauses(clause.Returning{}).Where("code = ?", "dasd945").Delete(&data)
}
与上面的数据插入不同的是这里通过Delete 方法来指定要对应操作的数据库
数据的多条删除
func delDatas() {
if err := db.Where("code LIKE ?", "%1%").Delete(&Product{}).Error; err != nil {
panic(err)
}
}
数据的更新
单列数据的更新
func updateData() {
product := Product{"dasd706", 10}
db.Model(&Product{}).Where("code = ?", "dasd706").Update("price", 10)
}
多列数据的更新
func updateDatas() {
if err := db.Model(&Product{}).Where("Price = ?", 16).Update("Price", 120).Error; err != nil {
panic(err)
}
}
上述没有用到Model 方法的话,操作的数据表由Update 方法中的结构体指示
数据的查询
单条数据的查询
func searchData() {
aimData := Product{}
if result := db.First(&aimData); result.Error != nil {
panic(result.Error.Error())
} else {
fmt.Println("找到的记录数", result.RowsAffected)
fmt.Println("查找到的数据", aimData)
}
}
多条数据的查询
func searchDatas() {
aimData := []Product{}
if err := db.Model(&Product{}).Where("Price = ?", 29).Find(&aimData).Error; err != nil {
fmt.Println(err.Error())
}
fmt.Println(aimData)
if err := db.Where("Price IN ?", []int{29, 50, 120}).Find(&aimData).Error; err != nil {
panic(err)
}
fmt.Println(aimData)
if err := db.Where(map[string]interface{}{"code": "dasd671", "price": 5}).Find(&aimData).Error; err != nil {
panic(err)
}
fmt.Println(aimData)
}
上述没有用到Model 方法的话,操作的数据表由Find 方法中的结构体指示
事务
func useAffairs() error {
rand.Seed(time.Now().UnixNano())
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
fmt.Println("回滚")
}
}()
if err := tx.Error; err != nil {
return err
}
if err := tx.Create(&Product{Price: rand.Intn(100), Code: "测试事务" + strconv.Itoa(rand.Intn(1000))}).Error; err != nil {
fmt.Println("发生回滚")
tx.Rollback()
return err
}
if err := tx.Create(&Product{Price: rand.Intn(100), Code: "测试事务" + strconv.Itoa(rand.Intn(1000))}).Error; err == nil {
fmt.Println("发生回滚")
tx.Rollback()
return err
}
return tx.Commit().Error
}
- 在开启事务之后,在上述的函数中若任意一条语句创建失败或者是运行时panic的话都会触发事务的回滚
- 需要注意的是
tx.Commit() 被执行的时候sql语句才真正被执行
参考
- 官方文档 里面有更多的示例和高级用法
https://gorm.io/zh_CN/docs/query.html - github地址
https://github.com/go-gorm/gorm
|