GORM 是 Golang 的一个 orm 框架。简单说,ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是"对象-关系映射"(Object/Relational Mapping) 的缩写。使用 ORM框架可以让我们更方便的操作数据库。 GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server
安装
go get -u gorm.io/gorm
# GORM 官方支持 sqlite、mysql、postgres、sqlserver
go get -u gorm.io/driver/mysql
config.yaml 配置项
配置连接数据库
mysql:
host: 127.0.0.1
port: 3306
user: root
password: 123456
database: demo
charset: utf8mb4
type MySQLInfo struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
Database string `mapstructure:"database"`
Charset string `mapstructure:"charset"`
}
初始化数据库 db.go
// main.go
// 初始化数据库
config.InitializeDB()
config.TableAutoMigrate()
package config
import (
"leo-gin/models"
"strconv"
"go.uber.org/zap"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
func InitializeDB() *gorm.DB {
dbConfig := config.Conf.MySQL
if dbConfig.Database == "" {
return nil
}
dsn := dbConfig.User + ":" + dbConfig.Password + "@tcp(" + dbConfig.Host + ":" + strconv.Itoa(dbConfig.Port) + ")/" + dbConfig.Database + "?charset=" + dbConfig.Charset + "&parseTime=True&loc=Local"
mysqlConfig := mysql.Config{
DSN: dsn, // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据版本自动配置
}
if db, err := gorm.Open(mysql.New(mysqlConfig), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true, // 禁用自动创建外键约束
}); err != nil {
logger.Log.Error("mysql connect failed, err:", zap.Any("err", err))
return nil
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10) // 空闲连接池中连接的最大数量
sqlDB.SetMaxOpenConns(100) // 打开数据库连接的最大数量
DB = db
logger.Log.Info("mysql connect success")
return db
}
}
func TableAutoMigrate() {
if DB == nil {
return
}
err := DB.AutoMigrate(&models.User{})
if err != nil {
logger.Log.Error("AutoMigrate failed, err:", zap.Any("err", err))
} else {
logger.Log.Info("AutoMigrate success")
}
}
定义数据库模型 创建user model
- 结构体的名称必须首字母大写 ,并和数据库表名称对应。例如:表名称为 user 结构体名称定义成 User,表名称为 article_cate 结构体名称定义成 ArticleCate
- 结构体中的字段名称首字母必须大写,并和数据库表中的字段一一对应。例如:结构体中的 Id 和数据库中的 id 对应,Username 和数据库中的 username 对应,AddTime 和数据库中的 add_time 字段对应
- 默认情况表名是结构体名称的复数形式。如果我们的结构体名称定义成 User,表示这个模型默认操作的是 users 表
- 使用结构体中的自定义方法 TableName 改变结构体的默认表名称
// 自增ID主键
type ID struct {
ID uint `json:"id" gorm:"primaryKey"`
}
// 创建、更新时间
type Timestamps struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// 软删除
type SoftDeletes struct {
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
}
type User struct {
ID
Name string `json:"name" gorm:"not null;comment:用户名称"`
Mobile string `json:"mobile" gorm:"not null;index;comment:用户手机号"`
Password string `json:"password" gorm:"not null;default:'';comment:用户密码"`
Timestamps
SoftDeletes
}
写入数据
func Register(c *gin.Context) {
var req RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user := models.User{
Name: req.Name,
Mobile: req.Mobile,
Password: req.Password,
}
if err := database.DB.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "用户保存失败"})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "注册成功",
"user_id": user.ID,
})
}
删除数据
r.DELETE("/usr/delete/:id", func(c *gin.Context) {
var data []models.User
id := c.Param("id")
database.DB.Where("id=?", id).Find(&data)
if len(data) == 0 { // if not found id
c.JSON(200, gin.H{
"message": "delete failed,not found id",
"code": "400",
})
} else {
database.DB.Where("id=?", id).Delete(&data)
c.JSON(200, gin.H{
"message": "delete succeeded",
"code": "200",
})
}
})
修改数据
r.PUT("/usr/update/:id", func(c *gin.Context) {
var data models.User
id := c.Param("id")
database.DB.Select("id").Where("id=?", id).Find(&data)
if data.ID == 0 { // if not found id
c.JSON(200, gin.H{
"message": "Put failed,not found id",
"code": "400",
})
} else { // ID is already
err := c.ShouldBindJSON(&data)
if err != nil {
// put failed
c.JSON(200, gin.H{
"message": "put failed",
"code": "400",
})
} else {
database.DB.Where("id = ?", "id").Updates(&data)
c.JSON(http.StatusOK, gin.H{
"message": "put succeeded",
"code": "200",
})
}
}
})
查询数据
r.GET("/usr/list/:name", func(c *gin.Context) {
name := c.Param("name")
var data []models.User
database.DB.Where("name=?", name).Find(&data)
if len(data) == 0 {
c.JSON(200, gin.H{
"message": "not found",
"code": "400",
"data": gin.H{},
})
} else {
c.JSON(200, gin.H{
"message": "get successfully",
"code": "200",
"data": data,
})
}
})
分页数据
r.GET("/usr/list", func(c *gin.Context) {
var datalist []models.User
pageSize, _ := strconv.Atoi(c.Query("pageSize"))
pageNum, _ := strconv.Atoi(c.Query("pageNum"))
if pageSize == 0 {
pageSize = -1
}
if pageNum == 0 {
pageNum = -1
}
// get all data
var total int64
database.DB.Model(datalist).Count(&total).Limit(pageSize).Offset(pageNum).Find(&datalist)
if len(datalist) == 0 {
c.JSON(200, gin.H{
"message": "get failed,no data",
"code": "400",
"data": gin.H{},
})
} else {
c.JSON(200, gin.H{ //return datalist json response
"message": "get succeed",
"code": "200",
"data": gin.H{
"list": datalist,
"total": total,
"pageNum": pageNum,
"pageSize": pageSize,
},
})
}
})
gorm表关联查询
一对多
type Comment struct {
gorm.Model
Content string
ParentId uint // 父评论ID
UserID uint // 用户ID
ReplyName string // 回复者名字
UserName string
UserAvatar string
SocialId uint `gorm:"foreignkey:Social;association_jointable_foreignkey:social_id"` //社区帖子ID
Children []Comment `gorm:"foreignkey:ParentId"`
}
多对多
type User struct {
gorm.Model
Relations []User `gorm:"many2many:relation; association_jointable_foreignkey:relation_id"`
UserName string
Email string //`gorm:"unique"`
Avatar string `gorm:"size:1000"`
Phone string
BanTime int
OpenID string `gorm:"unique"`
}