美文网首页
golang 操作mysql

golang 操作mysql

作者: 天空蓝雨 | 来源:发表于2020-08-10 22:02 被阅读0次

    mysql 和 mongodb 是两个 非常重要的数据库,一个是关系数据库,一个是非关系数据库。也是日常开发的必备了
    今天介绍 go 如何连接 mysql,如何使用 orm 进行操作。
    参考:
    GORM 中文文档
    gin 和 gorm 构建 RESTful API

    star 2w

    支持的数据库:


    应该就这些吧

    安装

    go get -u github.com/jinzhu/gorm
    

    或者使用 mod 的方式 (推荐,方便)

    module src
    require (
        github.com/jinzhu/gorm v1.9.15
    )
    

    快速开始

    • 导入包:
    import (
        "github.com/jinzhu/gorm"
        _ "github.com/jinzhu/gorm/dialects/sqlite"  // 需要那个数据库的驱动,就导入那个,只需要初始化即可,所以前面加个 _ 
    )
    
    • 定义数据库表结构:
    type Product struct {
      gorm.Model  // 继承 gorm 内置结构体
      Code string
      Price uint
    }
    
    • 连接数据库
    db, err := gorm.Open("sqlite3", "test.db")
      if err != nil {
        panic("连接数据库失败")
      }
      defer db.Close()
    

    Open 第一个参数是数据库类型,第二个参数是链接的数据库结构,比如:"root:520@/yxl?charset=utf8&parseTime=True&loc=Local"
    有个疑问需要解决的:
    Open 怎么链接这个数据库的
    先酱一下,第一个参数:
    Open 函数源码:

    func Open(dialect string, args ...interface{}) (db *DB, err error) {
    if len(args) == 0 {
            err = errors.New("invalid database source")
            return nil, err
        }
    .....
    dbSQL, err = sql.Open(driver, source)
    .....
    

    然后再看一下 sql.Open(driver, source) 里面的 Open 源码 :

    // sql.go 文件内
    var (
        driversMu sync.RWMutex
        drivers   = make(map[string]driver.Driver)
    )
    
    func Open(driverName, dataSourceName string) (*DB, error) {
        driversMu.RLock()
        driveri, ok := drivers[driverName]  // map  类型里面的是接口类型
    .....
    

    看出来,第一个参数是 数据库的驱动的接口字符串,然后里面直接map 方式取出来的
    第二个参数明天看吧

    pass

    • 自动迁移模式
    db.AutoMigrate(&Product{})
    
    • 新建数据
    db.Create(&Product{Code: "L1212", Price: 1000})
    
    • 读取 (单个数据)
     var product Product
      db.First(&product, 1) // 查询id为1的product
      db.First(&product, "code = ?", "L1212") // 查询code为l1212的product
    
    • 更新 - 更新product的price为2000
     db.Model(&product).Update("Price", 2000)  // product 是已经查到的数据了
    
    • 删除 - 删除product
    db.Delete(&product)  // product  同样是已经查询到的一个
    
    生成数据库的结构

    下面具体介绍每个部分

    • 支持 以下数据库的连接操作
    import _ "github.com/jinzhu/gorm/dialects/mysql"
    // import _ "github.com/jinzhu/gorm/dialects/postgres"
    // import _ "github.com/jinzhu/gorm/dialects/sqlite"
    // import _ "github.com/jinzhu/gorm/dialects/mssql"
    

    主要讲解一下mysql 的连接操作

    db, err := gorm.Open("mysql", "user:password@/dbname?
    charset=utf8&parseTime=True&loc=Local")
      defer db.Close()
    

    Sqlite3 (轻量级数据库,用在手机等移动应用 )

    db, err := gorm.Open("sqlite3", "/tmp/gorm.db")
      defer db.Close()
    
    • 迁移
      迁移就是把对应的数据库模型更新到连接的数据库 中(包括创建 新的数据表和 添加表的字段 )
      ps:自动迁移仅仅会创建表,缺少列和索引,并且不会改变现有列的类型或删除未使用的列以保护数据
    db.AutoMigrate(&User{})
    
    db.AutoMigrate(&User{}, &Product{}, &Order{})
    
    // 创建表时添加表后缀
    db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
    
    • 检查表是否存在
    // 检查模型`User`表是否存在
    db.HasTable(&User{})
    
    // 检查表`users`是否存在
    db.HasTable("users")
    

    两种方式也比较贴心

    • 创建表
    // 为模型`User`创建表
    db.CreateTable(&User{})
    

    我怎么感觉和迁移表没什么区别 ?

    • 删除表
    // 删除模型`User`的表
    db.DropTable(&User{})
    
    // 删除表`users`
    db.DropTable("users")
    
    // 删除模型`User`的表和表`products`
    db.DropTableIfExists(&User{}, "products")
    
    • 修改列
    // 修改模型`User`的description列的数据类型为`text`
    db.Model(&User{}).ModifyColumn("description", "text")
    

    和修改字段的值差不多 :

     db.Model(&todo).Update("title", c.PostForm("title"))  // &todo 为已经查询到的数据
    
    • 删除列
    // 删除模型`User`的description列
    db.Model(&User{}).DropColumn("description")
    
    • 添加外键
    // 添加主键
    // 1st param : 外键字段
    // 2nd param : 外键表(字段)
    // 3rd param : ONDELETE
    // 4th param : ONUPDATE
    db.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")
    

    这个看不太明白

    • 索引
    // 为`name`列添加索引`idx_user_name`
    db.Model(&User{}).AddIndex("idx_user_name", "name")
    
    // 为`name`, `age`列添加索引`idx_user_name_age`
    db.Model(&User{}).AddIndex("idx_user_name_age", "name", "age")
    
    // 添加唯一索引
    db.Model(&User{}).AddUniqueIndex("idx_user_name", "name")
    
    // 为多列添加唯一索引
    db.Model(&User{}).AddUniqueIndex("idx_user_name_age", "name", "age")
    
    // 删除索引
    db.Model(&User{}).RemoveIndex("idx_user_name")
    

    这个也看不太明白

    关于模型的介绍

    其实这个模型有很多东西要说的,但是说多了也记不住,挑几个好记的。

    • gorm.Model
      这个是gorm 内置的一个结构体,他长这样子:
    type Model struct {
      ID        uint `gorm:"primary_key"`
      CreatedAt time.Time
      UpdatedAt time.Time
      DeletedAt *time.Time
    }
    

    你可以直接把他嵌入你的模型(当然随你,自己定义也可以啦

    自定义字段
    type User struct {
      ID        uint 
      CreatedAt time.Time
      Name      string
    }
    

    使用 gorm.Model

    // 添加字段 `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`
    type User struct {
      gorm.Model
      Name string
    }
    
    
    • 表名是结构体名称的复数形
      type User struct {} // 默认表名是users
      type todoModel{} // 默认表名是 todo_models

    • 自定义表名
      1、给结构体添加 TableName() string 函数 string 即为表名

    func (User) TableName() string {
      return "profiles"
    }
    

    2、更具字段的值决定表名,可以复用结构体的继承:

    func (u User) TableName() string {
        if u.Role == "admin" {
            return "admin_users"
        } else {
            return "users"
        }
    }
    

    这样一个结构体就可以针对字段,来对应多个表了, 提高代码复用
    3、全局禁用表名复数
    db.SingularTable(true) // 如果设置为true,User的默认表名为user,使用TableName设置的表名不受影响
    4、更改默认表名
    有时候你可能全局更改表名的规则
    通过修改 gorm.DefaultTableNameHandler 函数为你自己定义的函数即可

    gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string  {
        return "prefix_" + defaultTableName;
    }
    

    defaultTableName 应该就是最开始默认的表名了
    表的列名是 字段名全小写 加下划线的形式

    CreatedAt time.Time // 列名为 `created_at`
    

    碰到 非首字母为大写的,会在他前面加一个下划线,并把大写字母转化为小写字母
    5、当然和 一般序列化一样,我们也可以自定义字段名
    使用 column 标记字段名称

    // 重设列名
    type Animal struct {
        AnimalId    int64     `gorm:"column:beast_id"`         // 设置列名为`beast_id`
        Birthday    time.Time `gorm:"column:day_of_the_beast"` // 设置列名为`day_of_the_beast`
        Age         int64     `gorm:"column:age_of_the_beast"` // 设置列名为`age_of_the_beast`
    }
    

    6、 默认情况的主键
    默认情况
    ID uint // 字段ID为默认主键
    设置自己的主键:

     AnimalId int64 `gorm:"primary_key"` // 设置AnimalId为主键
    

    7、默认CreatedAt 为创建的时间
    CreatedAt字段的记录将被设置为当前时间

    db.Create(&user) // 将会设置`CreatedAt`为当前时间
    
    // 要更改它的值, 你需要使用`Update`
    db.Model(&user).Update("CreatedAt", time.Now())
    

    8、字段 UpdatedAt 默认为更新的时间

    db.Save(&user) // 将会设置`UpdatedAt`为当前时间
    db.Model(&user).Update("name", "jinzhu") // 将会设置`UpdatedAt`为当前时间
    

    这样看的话,创建的时候, UpdatedAt CreatedAt 的值是一样的
    9、字段 DeletedAt 酱记录删除的时间
    这里的删除是 软删除,即将字段DeletedAt设置为当前时间,查询的时候是查不到的

    关于模型的外键,主键这里就不酱了。因为内容太多还是自己看看吧

    CURD 这个是最重要的

    创建数据

    1、create
    create 适合新增一条心数据

    user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
    db.Create(&user)
    

    2、save
    其实save 和 create 都可以新增一条数据,区别是:
    save 在数据存在的时候,是用来更新的,二create 只能保存一个心数据

    查询

    最重要的莫过于查询了
    gorm 提供的查询很形象,都是关键的单词
    1、First
    直接取出符合条件的第一条数据
    (当他前面有比如 where 的条件时,会在 first find 这种取数据的操作执行那些条件,根据逻辑是是这样的,因为 where 那些都没有表名,表名肯定是 传入的结构体中获取的)
    First 默认是主键排序, 第一个

    // 获取第一条记录,按主键排序
    db.First(&user)
    //// SELECT * FROM users ORDER BY id LIMIT 1;
    

    First 第一个参数是要查询数据的结构体,如果有第二个参数,如果只是单独的值,那就是 主键 的值

    2、Last

    // 获取最后一条记录,按主键排序
    db.Last(&user)
    //// SELECT * FROM users ORDER BY id DESC LIMIT 1;
    

    3、Find

    db.Find(&users)
    //// SELECT * FROM users;
    

    4、Where
    Where 用于写入过滤条件,后面可以和 first 或者 find 来搭配
    where 的参数 第一个参数,如果是结构体或者 map 对象,或单个值(默认主键)那就是对应的查询条件。
    如果不止一个参数,那第一个参数就是带占位符的字符串,第二、三... 个 参数是查询条件中 ? 的占位符的值
    4.1 Where(Struct)

    db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
    

    这里用 User 结构体就是为了方便,获取表名还是 后面的 First 吧(我猜的,这个要看源码才明白的)
    4.2 Where(map)

    db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
    

    4.3 Where(主键的Slice)

    db.Where([]int64{20, 21, 22}).Find(&users)
    

    4.4 Where 多个参数
    in

    db.Where("name in (?)", []string{"jinzhu", "jinzhu 2"}).Find(&users)
    

    LIKE

    db.Where("name LIKE ?", "%jin%").Find(&users)
    

    and

    db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
    

    Time

    db.Where("updated_at > ?", lastWeek).Find(&users)
    db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
    

    5 、 反条件 Not

    Not 可以有一个参数或者几个参数,基本同where
    一个参数可以是 结构体或者 map 或者 主键的值

    // Struct
    db.Not(User{Name: "jinzhu"}).First(&user)
    //// SELECT * FROM users WHERE name <> "jinzhu";
    
    db.Not([]int64{1,2,3}).First(&user)
    

    两个参数,一个是语句,后面的占位值

    db.Not("name = ?", "jinzhu").First(&user)
    

    一个是字段名,后面的是字段值

    // Not In
    db.Not("name", []string{"jinzhu", "jinzhu 2"}).Find(&users)
    // SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
    

    6、内联条件的查询
    主键查询时,应仔细检查所传递的值是否为有效主键,以避免SQL注入
    内联条件就是 把where 里面的条件放在了,first 或 finnd 的第一个参数后面了。
    随便举两个例子吧:

    // Map
    db.Find(&users, map[string]interface{}{"age": 20})
    
    db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
    
    

    7、 Or条件查询
    Or 可以和 where 搭配 (where 自己不能直接 or ? and 是有的)

    db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
    //// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
    

    8、查询链
    所有的条件可以链式调用

    db.Where("role = ?", "admin").Or("role = ?", "super_admin").Not("name = ?", "jinzhu").Find(&users)
    
    

    9、FirstOrInit
    基本同 first ,但是这个区分 找不到 用 给的 struct,map条件初始化 心数据
    不是太理解,需要练习证实一下
    10、Attrs

    // Unfound
    db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)
    //// SELECT * FROM USERS WHERE name = 'non_existing';
    //// user -> User{Name: "non_existing", Age: 20}
    

    不太明白,用的时候在看吧
    11、FirstOrCreate
    获取第一个匹配的记录,或创建一个具有给定条件的新记录,用于struct, map条件
    这个和 FirstOrInit 差不多

    // Unfound
    db.FirstOrCreate(&user, User{Name: "non_existing"})
    //// INSERT INTO "users" (name) VALUES ("non_existing");
    //// user -> User{Id: 112, Name: "non_existing"}
    

    12、Select
    选择返回的字段

    db.Select("name, age").Find(&users)
    //// SELECT name, age FROM users;
    
    db.Select([]string{"name", "age"}).Find(&users)
    //// SELECT name, age FROM users;
    

    13、Order
    指定排序字段

    db.Order("age desc, name").Find(&users)
    //// SELECT * FROM users ORDER BY age desc, name;
    

    重排序设置为true以覆盖定义的条件
    因为链式调用 前面的排序规则会直接作用于后面的条件

    // ReOrder
    db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
    //// SELECT * FROM users ORDER BY age desc; (users1)
    //// SELECT * FROM users ORDER BY age; (users2)
    

    14 、 Limit
    限制 返回个数

    db.Limit(3).Find(&users)
    //// SELECT * FROM users LIMIT 3;
    

    链式查询使用 -1 取消限制个数

    // Cancel limit condition with -1
    db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
    //// SELECT * FROM users LIMIT 10; (users1)
    //// SELECT * FROM users; (users2)
    

    15 、Offset
    用法同 Limit,只不过这个是跳过的数量。拿到后面的数据,和 Limit 是相反的
    16、Count计数

    db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count)
    

    17、Group & Having
    用到再说
    18、 Join
    用到再说,艹

    指定表名

    终于掠过了查询,艹内容太多了
    就是说从哪个表创建数据,修改数据,都不以结构体名为区分了。直接指定表的名字。

    // 使用User结构定义创建`deleted_users`表
    db.Table("deleted_users").CreateTable(&User{})
    var deleted_users []User
    db.Table("deleted_users").Find(&deleted_users)
    //// SELECT * FROM deleted_users;
    

    很简单

    更新

    分为全更新还是部分更新
    全更新:
    1、Save: 把全部字段都重新

    db.First(&user)
    user.Name = "jinzhu 2"
    user.Age = 100
    db.Save(&user)
    

    更新更改字段
    2、Update(单个属性), Updates(多个属性,map 或者 struct)

    db.Model(&user).Update("name", "hello")
    // 使用`map`更新多个属性,只会更新这些更改的字段
    db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
    // 使用`struct`更新多个属性,只会更新这些更改的和非空白字段
    db.Model(&user).Updates(User{Name: "hello", Age: 18})
    // 对于下面的更新,什么都不会更新为"",0,false是其类型的空白值
    db.Model(&user).Updates(User{Name: "", Age: 0, Actived: false})
    

    3、选择(Select)或忽略(Omit)字段
    Select

    db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
    //// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
    
    db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
    //// UPDATE users SET age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
    

    以上更新默认会修改 UpdatedAt字段
    原因是,会默认调用 BeforeUpdate, AfterUpdate Callbacks 方法。不想修改的话需要使用 UpdateColumn(类四 update) 单个属性
    UpdateColumns (类似 updates)多个属性

    删除/软删除
    // 删除存在的记录
    db.Delete(&email)
    //// DELETE from emails where id=10;
    
    db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
    //// DELETE from emails where email LIKE "%jinhu%";
    
    db.Delete(Email{}, "email LIKE ?", "%jinzhu%")
    //// DELETE from emails where email LIKE "%jinhu%";
    

    软删除
    模型有DeletedAt字段,它将自动获得软删除功能
    只将字段DeletedAt的值设置为当前时间

    // 批量删除
    db.Where("age = ?", 20).Delete(&User{})
    //// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
    
    // 软删除的记录将在查询时被忽略
    db.Where("age = 20").Find(&user)
    //// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
    

    如果想绝对删除, 使用Unscoped:

    // 使用Unscoped查找软删除的记录
    db.Unscoped().Where("age = 20").Find(&users)
    //// SELECT * FROM users WHERE age = 20;
    
    // 使用Unscoped永久删除记录
    db.Unscoped().Delete(&order)
    //// DELETE FROM orders WHERE id=10;
    

    使用 Unscoped 后,酱不会过滤掉deleted_at 字段了

    相关文章

      网友评论

          本文标题:golang 操作mysql

          本文链接:https://www.haomeiwen.com/subject/agmqdktx.html