golang 操作mysql

2020-08-10  本文已影响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
 db.Model(&product).Update("Price", 2000)  // 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()
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")

这个也看不太明白

关于模型的介绍

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

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
}

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 字段了

上一篇下一篇

猜你喜欢

热点阅读