golang 数据库操作工具gorm

Gorm当前支持MySql, PostgreSql, Sqlite等主流数据库

1.安装驱动

go get github.com/go-sql-driver/mysql

2.小示例

package main

import (
    "fmt"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql" // 包装
)

type User struct {
   Id int64
   UserId int64
   AddId int64
   Name string
   Address string
}

type Address struct {
    Id int64
    UserId int64
    AddId int64
    AddName string
    AddLocation string
}


func main() {
    db, err := gorm.Open("mysql", "root:123456@/guolianlc?charset=utf8&parseTime=True&loc=Local")
    if err != nil {
         fmt.Println("connect db error: ", err)
    }
    defer db.Close()
    if db.HasTable(&User{}) {
        db.AutoMigrate(&User{})
    } else {
        db.CreateTable(&User{})
    }
    //db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&Address{})
    db.AutoMigrate(&Address{})
    db.Model(&User{}).AddForeignKey("add_id", "addresses(id)", "RESTRICT", "RESTRICT")
    db.Model(&User{}).AddForeignKey("add_id", "addresses(id)", "RESTRICT", "RESTRICT")
    db.Model(&User{}).AddIndex("idx_user_add_id", "add_id")
    db.Model(&User{}).AddUniqueIndex("idx_user_id", "user_id")
}

3.表级别操作

  • AutoMigrate()
    db.AutoMigrate(&Address{})
    AutoMigrate()运行后,会自动migrate对应的model.仅仅新增新增的字段,不会进行修改已有的字段类型,删除字段的操作
  • HasTable()
    检查表是否存在
  • CreateTable()
    db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&Address{})
    创建表
    默认情况下,表名为结构体名的复数形式,当然也可以禁用;
    db.SingularTable(true)
  • DropTable()/ DropTableIfExists()
    删除表
  • ModifyColumn()
    修改列
  • DropColumn()
    删除列
  • AddForeignKey()
    参数 : 1th:外键字段,2th:外键表(字段),3th:ONDELETE,4th:ONUPDATE
    db.Model(&User{}).AddForeignKey("add_id", "addresses(id)", "RESTRICT", "RESTRICT")
    两个表中的字段都必须存在,就像Users表中的add_id字段,如果不存在,无法自动新增字段,并自动创建外键
  • AddIndex() / AddUniqueIndex
    添加索引,添加唯一值索引
    db.Model(&User{}).AddForeignKey("add_id", "addresses(id)", "RESTRICT", "RESTRICT")
    db.Model(&User{}).AddIndex("idx_user_add_id", "add_id")
    db.Model(&User{}).AddUniqueIndex("idx_user_id", "user_id")
  • RemoveIndex()
    删除索引

示例中最终创建出的表结构:

CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) DEFAULT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `address` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `add_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_id` (`user_id`),
  KEY `idx_user_add_id` (`add_id`),
  CONSTRAINT `users_add_id_addresses_id_foreign` FOREIGN KEY (`add_id`) REFERENCES `addresses` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

4.表结构设计以及gorm标签的使用

go中使用结构体来作为表结构设计的载体,实例:

package main

import (
    "database/sql"
    "fmt"
    "time"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql" // 包装
)

type User struct {
    gorm.Model
    UserId    int64 `gorm:"index"`
    Birthday  time.Time
    Age       int           `gorm:"column:age"`                     //可定制列表名
    Name      string        `gorm:"size:255;index:idx_name_add_id"` // string默认长度为255, 使用这种tag重设。
    Num       int           `gorm:"AUTO_INCREMENT"`                 // 自增
    Email     string        `gorm:"type:varchar(100);unique_index"`
    AddressID sql.NullInt64 `gorm:"index:idx_name_add_id"`
    IgnoreMe  int           `gorm:"-"` // 忽略这个字段
        Desction  string        `gorm:"size:2049;comment:'用户描述字段'"`
Status       string `gorm:"type:enum('published','pending','deleted');default:'pending'"`
}

//设置表名,默认是结构体的名的复数形式
func (User) TableName() string {
    return "VIP_USER"
}

func main() {
    db, err := gorm.Open("mysql", "root:123456@/guolianlc?charset=utf8&parseTime=True&loc=Local")
    if err != nil {
        fmt.Println("connect db error: ", err)
    }
    defer db.Close()
    if db.HasTable(&User{}) {
        db.AutoMigrate(&User{})
    } else {
        db.CreateTable(&User{})
    }
}

插入一条测试语句后,查询表结构如下:


image.png

gorm.Model为内建的结构体,结构如下:

// 基本模型的定义
type Model struct {
  ID        uint `gorm:"primary_key"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt *time.Time
}

创建出来的表结构为:

CREATE TABLE `VIP_USER` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `deleted_at` timestamp NULL DEFAULT NULL,
  `user_id` bigint(20) DEFAULT NULL,
  `birthday` timestamp NULL DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `num` int(11) DEFAULT NULL,
  `email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `address_id` bigint(20) DEFAULT NULL,
  `desction` varchar(2049) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户描述字段',
  `status` enum('published','pending','deleted') COLLATE utf8mb4_unicode_ci DEFAULT 'pending',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uix_VIP_USER_email` (`email`),
  KEY `idx_VIP_USER_deleted_at` (`deleted_at`),
  KEY `idx_VIP_USER_user_id` (`user_id`),
  KEY `idx_name_add_id` (`name`,`address_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
组合索引.png

数据插入时,仅仅插入业务数据即可,created_atupdated_at,deleted_at字段不用手动设置值,gorm会帮我们自动维护这些字段的值,当首次插入时,created_atupdated_at字段的值是相同的,都为当前数据记录插入的时间

  var user User = User{
             UserId: 1,
             Birthday: time.Now(),
             Age: 12,
             Name:"zhangsan",
             Num: 12,
             Email:"zhangsan@alibaba.com",
             AddressID:sql.NullInt64{Int64 : int64(1), Valid : err == nil},
             Desction:"first",
        }
        if err := db.Model(&User{}).Create(&user).Error; err != nil{

        }
数据插入后的记录详情.png

当数据执行删除操作时,默认情况下执行的是软删除,仅仅设置deleted_at字段的值,为执行删除操作的时间

if err := db.Model(&User{}).Where("user_id=?", 1).Delete(&User{}).Error; err != nil {
        }
执行删除操作.png

如果业务上需要,读取包含软删除的数据,可以在查询时加上

var usr = make([]*User,0)
        if err := db.Unscoped().Model(&User{}).Where("user_id=?", 1).Find(&usr).Error; err != nil {}
        for _, usser := range usr {
            fmt.Println(usser)
        }

Output:

&{{1 2019-05-15 13:34:23 +0800 CST 2019-05-15 13:34:23 +0800 CST 2019-05-15 13:42:13 +0800 CST} 1 2019-05-15 13:34:23 +0800 CST 12 zhangsan 12 zhangsan@alibaba.com {1 true} 0 first}

如果需要永久的删除数据,也就是物理删除,可以在Unscoped()的基础上,执行Deleted()

 if err := db.Unscoped().Model(&User{}).Where("user_id=?", 1).Delete(&User{}).Error; err != nil {}
物理删除.png

5.增删改查

if err := tx.Model(&model.Teatures{}).Create(teatureRecord).Error; err != nil {
                ErrMsg := fmt.Sprintf("%s", err)
                if strings.HasPrefix(ErrMsg, "Error 1062: Duplicate entry") {
                    continue
                }
                logrus.Errorln("updateUser, sava user teature err: ", err)
                tx.Rollback()
                return
            }

if err := tx.Where("created_at=?", int64Time).Delete(&model.User{}).Error; err != nil {
        tx.Rollback()
        logrus.Errorln("updateUser , delete user err: ", err)
        return
    }

if err := common.Db.Model(&model.User{}).
                        Where("created_at=? and usr_id=? and usr_name=? and usr_code=?", int64Time, UserId, UserName, UserCode).
                        Updates(
                            map[string]interface{}{
                                ......
                            }).Error; err != nil {
                        logrus.Errorln("UpdateUsers, update user record err: ", err)
                        return
                    }

本示例中,给出的更改语法使用的是map字典,当然你也可以传入数据库字典结构体,但是需要注意的是:
在实际应用中,我们的文章编辑后台,在删除某个字段后,比如文章的摘要,进行更新提交时,发现更改并未生效,查看后台gorm转化成的sql语句,发现并没有更新摘要字段,这是因为,当结构体的某个字段为零值的时候,传入到updates方法中,并没有显示该字段,而udpates方法是根据该结构体有值的字段进行更新的,没有值的字段,并没有做任何操作,所以上述进行的更新也未起作用,这些细节需要格外注意

    dbUsers := make([]*model.User, 0)
    if err := common.Db.Model(&model.User{}).Where("created_at=?", int64Time).Find(&dbUsers).Error; err != nil {
        logrus.Errorln(err)
        return
    }

6.事务操作

        tx := common.Db.Begin()
    // 1.删除数据

    if err := tx.Where("created_at=?", int64Time).Delete(&model.User{}).Error; err != nil {
        tx.Rollback()
        logrus.Errorln("updateUser , delete user err: ", err)
        return
    }
    // 2. 插入新数据
    for useOrder, user := range users {
        for teatureOrder, teature := range user.Teatures {
            var teatureRecord = &model.Teatures{
                ......
            }
            
            if err := tx.Model(&model.Teatures{}).Create(teatureRecord).Error; err != nil {
                ErrMsg := fmt.Sprintf("%s", err)
                if strings.HasPrefix(ErrMsg, "Error 1062: Duplicate entry") {
                    continue
                }
                logrus.Errorln("updateUser, sava user teature err: ", err)
                tx.Rollback()
                return
            }
        }
    }
    // 提交事务操作
    tx.Commit()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352