Gorm 操作入门

什么是 ORM

ORM(Object-Relational-Mapping) 是在关系型数据库和对象之间做一个映射, 在操作数据库时
就不需要和复杂的 sql 语句打交道, 而是像操作对象一样操作sql就可以

GORMgo 语言的一个 orm 框架

# 使用 mysql 创建一个 users 表
create table users
(
    id       int(11),
    username varchar(255),
    age      int(3),
    sex      int
);
// 使用 gorm 对象创建一个 users 表
type User struct {
    ID int
    UserName string
    Age int
    Sex string
}

DB.CreateTable(&User{}) // 创建表

安装

# 安装 gorm
go get -u gorm.io/gorm 
# 安装 mysql 驱动
go get gorm.io/driver/mysql

连接数据库 mysql

var (
    DB  *gorm.DB
    err error
)

func init() {
    // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
    // 数据库用户名:密码@tcp(主机:端口)/gin?
    // charset=utf8mb4 设置字符集
    // parseTime=True 处理 time.Time 
    // loc=Local 时区设置,与本地时区保持一致
    dsn := "root:123456@tcp(127.0.0.1:3306)/gin?charset=utf8mb4&parseTime=True&loc=Local"
    // Open 第一个参数: 数据库
    DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
        SkipDefaultTransaction: true, // 事务配置
    })
    if err != nil {
        fmt.Println(err)
    }
}

orm 操作表

type User struct {
    ID int
    UserName string
    Age int
}

// 自定义表名
func (User) TableName() string{
    return "my_user"
}

// 创建表 表名为 结构体 小写复数 users
DB.CreateTable(&User{})
// 创建自定义名字的 表
DB.Table("user1").CteateTable(&User{})

// 删除表
DB.DropTable(&User{})
DB.DropTable("user1")

// 判断表是否存在
DB.HasTable(&USer{})

// 给表重命名
DB.RenameTable(oldName, newName)

对表里面的数据 CRUD

// 创建 users 表
DB.CreateTable(&User{})

// 新增 data
DB.Create(&User{UserName: "yym", Age: 18})

// 查询 data
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;
DB.First(&user, 10) // select * from users where id = 10;
DB.Find(&users, []int{1,2,3}) // select * from users where id in (1,2,3);
DB.First(&user, "id=?", "2121_aaa") // select * from users where id = "2121_aaa";
var user = User{ID: 10}
db.First(&user) // select * from users where id = 10
var result User
db.Model(User{ID: 10}).First(&result)  // SELECT * FROM users WHERE id = 10;

DB.Find(&users) // select * from users;

DB.Where("name=?", "yym").First(&user) // select * from users where name = 'yym' order by id limit 1 
DB.Where("name like ?", "%jin%").Find(&users) // select * from users where name like '%jin';
DB.Where("name=? and age>?", "yym", "10").Find(&user) // select * from users where name = 'yym' and age > 10;

// 更新 data, 先查询, 再更新
DB.First(&user)
user.Name = "yym2"
user.Age = 100
DB.Save(&user) // update users set name = 'yym2', age=100 where id=1;

// 更新单个字段
DB.Model(&user).Updata("age", 20) // update users set age=20 where id=1;
DB.Model(&user).Where("active=?", true).Update("name", "hello") // update users set name='hello' where id=1 and active=true;
// 更新多个字段, struct map[string]interface{}
DB.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false}) // update users set name='hello', age=18 where id =1;

// 删除 data
DB.Delete(&user) // delete from users where id = 1
DB.Where("name=?", "yym").Delete(&user)// delete from users where id =1 and name = 'yym';
DB.Delete(&Users{}, []int{1,2,3}) // delete from users where id in (1,2,3)

结构体和表名的映射

  1. 结构体没有驼峰命名, 表名就是: 结构体名小写+复数 => User 对应表 users
  2. 有驼峰命名, 表名: 大写变小写前面加下划线,最后复数 => UserInfo 对应表 user_infos
  3. 有连续大写字母, 表名: 连续大写字母变小写, 驼峰前加下划线小写复数 => DBUserInfo 对应 表 db_user_infos
// 对应表名 users
type User struct {}

// 对应表名 user_infos
type UserInfo struct {}

// 对应表名 db_user_infos
type DBUserInfo struct {}

gorm.Model

GORM 定义一个 gorm.Model 结构体,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt

// gorm.Model 的定义
type Model struct {
    ID        uint           `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

type User struct {
    gorm.Model
    Name string
}
// 等效于
type User struct {
    ID        uint           `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
    Name string
}

字段标签

声明 model 时,tag 是可选的,GORM 支持以下 tag: tag 名大小写不敏感,但建议使用 camelCase 风格

  • "primaryKey" 将列定义为主键
  • "autoIncrement" 自增
  • "-" 忽略该字段
  • "not null" 指定列为 NOT NULL
  • "index" 根据参数创建索引
  • "unique" 唯一索引, 不能重复
  • "column" 指定列名
  • "size" 设置默认长度
type Student struct {
    ID int `gorm:"primaryKey;autoIncrement"` // 主键 自动增长
    Name string `gorm:"index:idx_name;column:name1"` // 自定义索引名称
    Desc string `gorm:"-;size:10"` // 忽略不映射这个字段
}

表关系

一对一

// 一对一: 一个用户有一个扩展信息, 一个扩展信息对应一个用户, 外键可以加在任意表中
// belongs_to : 关系和外键的指定在同一方

// 用户表
type User struct {
    UserId string `gorm:"primaryKey;AutoIncrement"`
    Name string // 姓名
    Age int // 年龄
}

// 用户信息表
// `gorm:"foreignKey:MyUserId;AssociationForeignKey:UserId"` 指定外键
type UserInfo struct {
    InfoId int `gorm: primaryKey;AutoIncrement"`
    Pic string // 图片
    Address string
    User User `gorm:"foreignKey:MyUserId;AssociationForeignKey:UserId"` // 关联关系
    MyUserId int  // 指定外键
}


// has_one: 关系和外键的指不在同一方
type User struct {
    UserId string `gorm:"primaryKey;AutoIncrement"`
    Name string // 姓名 
    Age int // 年龄
    UserInfo UserInfo `gorm:"foreignKey:MyUserId"`
}

// 
type UserInfo struct {
    InfoId int `gorm: primaryKey;AutoIncrement"`
    Pic string // 图片 
    Address string 
    MyUserId int  // 指定外键
}

一对多

// 作者
type Author struct {
    AID int `gorm:"primaryKey"`
    Name string
    Age int
    Article []Article // 关联关系
}
// 文章
type Article struct {
    ArID int `gorm:"primaryKey"`
    Title string
    Content string
    AID uint // 外键
    
}

// 一对多, 一个作者可以有多个文章, 一片文章属于一个作者
// 在文章表中加一个 所属作者 的 外键

多对多

// 学生
type Student struct {
    SId int `gorm:"primaryKey"`
    Name string
    // 关联表
    Course []Course `gorm:"many2many:student_courses;"`
}

// 课程
type Course struct {
    CId int `gorm:"primaryKey"`
    teacher string
}

// 多对多, 一个学生有有个课程, 每个课程有很多学生上
// 新建一张关联表 student_schedules: id student_id schedule_id

表关联操作

一对一关联操作

// add 关联添加操作
info1 := UserInfo{
    Pic: "/xxx",
    Address: "上海",
    User: User{
        Name: "yym",
        Age: 18
    }
}

DB.create(&info1)

// find 关联查询操作 关联关系在 UserInfo 中, 所以从 info 入手
var userinfo UserInfo // userinfo 是源模型, 主键不能为空
// 1. Association
DB.First(&userinfo, "info_id=?", 1) // 查不到关联关系
// Model参数: 要查询的表数据
// Association参数, 关联到具体的模型名 User
// Find参数: 查询的数据要放在什么字段中
DB.Model(&userinfo).Association("User").Find(&userinfo.User)
// 带的条件的查询
codes := []string{"zh-CN", "en-US", "ja-JP"}
DB.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages)

// 2. Preload 方式 预加载
DB.Preload("User").Find(&userinfo, "info_id=?", 1)

// 3. Related 方式
DB.First(&userinfo, "info_id=?", 1)
var user User
// 通过 userinfo 查出来的 User 字段信息放入新的容器 User 中
DB.Model(&userinfo).Related(&user, "User")

// update 关联更新 通过 info 对 user 表数据更新
DB.Preload("User").Find(&userinfo "info_id", 1) // 查询
DB.Model(&userinfo.user).Update("age", 20) // 更新

// delete 关联删除
DB.Preload("User").Find(&userinfo "info_id", 1) // 查询
DB.Delete(&userinfo.User) // 删除userinfo 的 User 表中的记录

一对多关联操作

// author article 两个表, 一个 author -> 多个 article, 操作 author 表

// add 关联添加
author := Author{
    Name: "yym",
    Age: 18,
    Article: []Atricle{
        {
            Title: "HTML",
            Content: "122"
        },
        {
            Content: "CSS"
            Content: "122"
        }
    },
}

DB.Create(&author)

// find 查询
var author Author
// 条件是 文章的条件
DB.Model(&author).Where("ar_id=?", 1).Association("Article").Find(&author.Article)

// update 更新
// 先查询, 再更新
DB.Preload("Article").Find(&author, "a_id=?", 1)
DB.Model(&author.Article).Where("ar_id=?", 1).Update("title", "JS入门")

// 删除
DB.Where("ar_id=?", 2).Delete(&author.Article)

多对多关联操作

// student course两个表 关联关系在 student 中, 操作 student

// add
stu := Student{
    Name: "yym",
    Course: []Course{
        {
            teacher: "张三",
        },{
            teacher: "李四"
                }
    }
}

DB.Create(&stu)

常用方法

DB.First() // 按条件查询, 升序排列 查询出一条记录

// 有对应数据, 就查出来, 没有, 就创建
DB.Where(User{Name: "non_existing"}).Attrs(User{Email: "fake@fake.org"}).FirstOrCreate(&user)

DB.Last() // 按条件查询, 降序排列 查询出一条记录

DB.Take() // 按条件查询, 和 First 类似, 未排序

DB.Find(&user, 1) // select * from where id = 1

DB.Where() // 查询 加入指定条件 

DB.Select("name, age") // 对字段过滤

DB.Create() // 添加单条数据

DB.Save() // 保存更新数据

DB.Update() // 更新一列
DB.Updates() // 更新多列

DB.Delete() // 删除数据

DB.Not("user_id = ?", 1).Find(&users) // 排除 id = 1 的值
DB.Or("user_id=?", 2) // 多个条件的查询
DB.Order("age desc") // 升序或降序排列

DB.Limit(10) // 获取记录的最大数量
DB.Offset(1).Limit(3) // 跳过几条,偏移, 和 Limit 结合使用
// 结果扫描到另一个结构体, 可以对 JSON 进行处理
DB.Scan(&user2) // 把获取的数据扫描到 &user2 中, 结构体字段一致

DB.Count(&count) // 计数
// select age count(*) from users group by age
DB.Group("age") // 根据年龄进行分组
// select age, count(*) from users group by age having (age > 18)
DB.Having("age >18") // 分组以后进行过滤


// 左连接 右连接
// 左连接 users 表是全的, user_infos 表数据不全
//select * from users left join user_infos on users.id = user_infos.info_id

// 右连接 users 表不全, user_infos 表是全的
//select * from users right join user_infos on users.id = user_infos.info_id
type NewUserInfo struct {} // 新的结构体, 扫描数据进来
DB.Joins("left join user_infos on users.id = user_infos.info_id").Find(&users).Scan(&NewUserInfo)

DB.DeBug() // 打印当前行的 sql 语句

// 日志级别
logger.Default.LogMode(logger.Silent)

// 操作原生 sql 查询 Raw
DB.Raw("select * from users").Find(&users)
DB.Raw("select * from users where age = ?", 14).Find(&users)
// 操作原生sql Exec 增加 删除 修改
DB.Exec("insert into users (age, name) values (?, ?)", 33, "张五")
DB.Exec("delete from users where user_id = ?", 1)
DB.Exec("update users set name = ? where user_id = ?", "张三", 3)

日志

Golang标准库的日志框架比较简单, 使用第三方日志 logrus

go get -u github.com/sirupsen/logrus
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容