^_^ 前序
特点
- 全特性 ORM (几乎包含所有特性)
- 模型关联 (一对一, 一对多,一对多(反向), 多对多, 多态关联)
- 钩子 (Before/After Create/Save/Update/Delete/Find)
- 预加载
- 事务
- 复合主键
- SQL 构造器
- 自动迁移
- 日志
- 基于 GORM 回调编写可扩展插件
- 全特性测试覆盖
- 开发者友好
版本
jinzhu/gorm 是v1版本,gorm.io/gorm是v2版本,该篇说的是v1版本
V1官网
V2官网
what is ORM of Golang ?
ORM: Object Relational Mapping(对象 关系 映射)
GORM: go的结构体 和 数据库的数据 的形成映射关系,进而可以 以GO的风格 去操作数据库。
映射关系:
结构体 类型 <—> 数据表
结构体 字段 <—> 数据列名
结构体 实例 <—> 数据记录
安装
Install: go get github.com/jinzhu/gorm
一、GORM 操作 Mysql
小试牛刀
package main
import (
"fmt"
"github.com/jinzhu/gorm" //引入 gorm包
_ "github.com/jinzhu/gorm/dialects/mysql" // 引入 mysql 对应的 驱动程序(GORM 内置)
)
type UserTable struct {
ID uint //ID 字段 默认为 主键
UserName string
age int
IsHuman bool
}
func main() {
var arr1 = "mysql"
var arr2 = "root:111111@(localhost)/aaa?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(arr1,arr2) // 通过Open方法连接数据库
if err != nil {
panic(err)
} else {
fmt.Println("数据库连接成功")
}
defer db.Close()
db.SingularTable(true) // 不允许给 表名 加复数形式
db.LogMode(true) // 启用Logger,每个数据库操作都会显示详细日志,无需一个个调用Debug方法。
tableOfUser := new(UserTable)
db.AutoMigrate(tableOfUser) // 自动迁移
AutoMigrate说明:
- 如果数据库中没有UserTable结构体映射成的表,则此时自动迁移就是创建一个表;
- 如果数据库中有UserTable结构体映射成的表,但是 结构体中 的字段 比 表中的字段 有后来增加的,则此时自动迁移就是 将 结构体 中 增加的字段 同步增加到 表中。
注意: 自动迁移 只会创建、添加, 不会 编辑 和 删除。
上面代码生成的表:
表名: user_tables
表结构:
Field | Type | Null | Key | Default | Extra |
---|---|---|---|---|---|
id | int(10) unsigned | NO | PRI | NULL | auto_increment |
user_name | varchar(255) | YES | NULL | ||
age | int(11) | YES | NULL | ||
is_it | tinyint(1) | YES | NULL |
^_^ 映射规则(模型定义)重点!
1. 结构体------> 数据表
映射到数据库 的 结构体字段首字母必须 大写
db, _ := gorm.Open(arr1, arr2)
db.LogMode(true)
db.SingularTable(true)
type WangHaiOu struct {
Id int `gorm:"auto_increment;primary_key;comment:'主键'"`
Name string `gorm:"type:varchar(10);default:'tom'"`
IdCard string `gorm:"type:char(18);unique;not null"`
Uid int8 `gorm:"type:tinyint(4);default:0;comment:'int8 =对应=>tinyint(4)'"`
Pid int16 `gorm:"type:smallint(5);default:0;comment:'int16 =对应=>int(11)'"`
Fid int32 `gorm:"type:mediumint(7);default:0;comment:'int32 =对应=>int(11)'"`
Cid int64 `gorm:"type:bigint(20);default:0;comment:'int64 =对应=>bigint(20)'"`
Fee float64 `gorm:"type:decimal(5,2);default:0;"`
CreateAt time.Time `gorm:"comment:'创建时间'"`
}
db.DropTableIfExists(&WangHaiOu{})
db.Set("gorm:table_options", "ENGINE=InnoDB AUTO_INCREMENT=5128 DEFAULT CHARSET=utf8mb4").CreateTable(&WangHaiOu{})
db.CreateTable(&WangHaiOu{})
对应的表结构:
Field | Type | Null | Key | Default | Extra | comment |
---|---|---|---|---|---|---|
id | int(11) | NO | PRI | NULL | auto_increment | 主键 |
name | varchar(10) | YES | tom | |||
id_card | char(18) | NO | UNI | NULL | ||
uid | tinyint(4) | YES | 0 | int8 =对应=>tinyint(4) | ||
pid | smallint(5) | YES | 0 | int16 =对应=>int(11) | ||
fid | mediumint(7) | YES | 0 | int32 =对应=>int(11) | ||
cid | bigint(20) | YES | 0 | int64 =对应=>bigint(20) | ||
fee | decimal(5,2) | YES | 0.00 | |||
create_at | datetime | YES | NULL | 创建时间 |
简单说明:
column
指定列名
如果不指定列名,结构体的字段名:UserName 会映射为: user_name
column指定的名字无论大小写,原样到数据库Sex string `gorm:"column:gender;"`
type
指定数据类型
不论结构体字段原先是什么类型,最后到数据库中的数据类型 type说了算Sex int `gorm:"type:varchar(100);"`
size
指定string类型字段的大小
go 中string类型 到数据库中 默认 varchar(255)Sex string `gorm:"type:varchar(100);"`
数据类型的对应关系:
int —》 int
int8 —》 tinyint
int16 —》 int
int32 —》 int
int64 —》 bigint
uint64 —》 bigint unsigned
float32/float64 —》 double
-
(减号) 忽略该结构体的字段,
在映射到数据库的过程中 直接 就无视该字段。Sex string `gorm:"-"`
primary_key
指定主键auto_increment
指定列的 自增index
创建索引unique
指定值的唯一性default
指定默认值not null
指定列为非空Id uint `gorm:"primary_key;auto_increment;not null;"`
// 为模型`User`创建表
db.CreateTable(&User{})
// 创建表`users'时将“ENGINE = InnoDB”附加到SQL语句
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&User{})
// 删除模型`User`的表
db.DropTable(&User{})
// 删除表`users`
db.DropTable("users")
2 映射中的默认事件
表名 : 结构体名称的复数形式。
结构体名 UserName —》user_names
结构体名 UserNames —》user_names
可以通过 SingularTable(true) 禁用默认表名的复数形式
也可以通过DefaultTableNameHandler方法自定义表名生成的规则ID :字段默认为 主键
gorm.Model : GORM内置了一个gorm.Model结构体,
包含了ID, CreatedAt, UpdatedAt, DeletedAt四个字段,
可以将它嵌入到自己的模型中。
^_^ 数据的増删改查
type Us struct {
ID uint // 字段`ID`为默认主键
Name string
Old int
}
1. 増 (Create)
type Aaa struct {
Id int
Name string
Time time.Time
}
data := Aaa{ "tom", time.Now()}
db.Create(&data) // 注意: 是 地址类型变量
fmt.Println(a.Id) // 返回 插入此条数据的 主键id值 (这算是一个重点,用的比较多)
// 若 数据库中 time 字段的类型: datetime,则存储的数据: 年-月-日 时-分-秒
// 若 数据库中 time 字段的类型: date,则存储的数据: 年-月-日
注意:
- 主键值回显问题
如果主键字段 是id , 增加一条数据,id字段会被自动赋值为 该条记录的主键值。
如果主键字段 不是id , 要想实现 主键值回显, 则需要加入 标志该字段为主键信息的标签gorm:“column:uid;primary_key;AUTO_INCREMENT
”
- 参数为地址类型变量
db.Create(&data) 注意: 是 地址类型变量
如果这个地方 参数类型 写成了 把址变量 写成了 值变量,会出现using unaddressable value
错误,表示传递的指针值不对。
2. 删 (Delete)
//删除表上所有记录
db.Delete(new(Us))
//删除指定记录
db.Where("id = ?", "1").Delete(new(Us))
说明:
增删操作
对应表的机制是 通过 结构体 的 名字,例如:UserTable 结构体对应的就是mysql中的user_table表。
3. 改(Update)
//根据主键更新字段,如果表中没有该主键的记录,则添加该记录
user := Us{11, "tom11", 110}
db.Save(&user)
// 更新一个字段
db.Table("user").Where( "id = ?", 10 ).Update("name", "tom")
// 更新多个字段 时 用map来实现
db.Table("user").Where( "id = ?", 10 ).Update(map[string]interface{} {"name":"tom", "age": 10, } )
// 动态更新字段时 推荐用下面的 结构体 的方式:
说明:
- 如果上面去掉where函数,那么整个表都会得到更新
- Update()方法的传参 : map的情况
不可用
其地址变量形式;struct的情况: 可以是其本身也可以是址变量。- Table 和 Model 都是用来 获取 对应的 数据表 的, 只是二者的参数不同:
- Table的参数是一个字符串,即 表名 的字符串形式 ==》 Table(“user”)
- Model的参数是一个结构体,即 结构体指针变量==》 Model( new(User) )
结构体更新
type test struct {
Id int
Name string
Age string
}
m := test{
Name: "afaa",
Age: "fcaa",
}
// 通过 select 方法:只更新 name 字段 的数据 。
db.Model(new(test)).Where("id = ?", 22).Select("name").Update(m)
// 通过 Omit方法:更新数据时 忽略 name字段数据的更新。
db.Model(new(test)).Where("id = ?", 22).Omit("name").Update(m)
- 注意:
- 这里一定要注意,数据表对应的方法 不要用 Table方法,
一定要用 Model 方法
, 因为Table 下
elect 和 Omit 不生效的。- 当结构体更细数据时,如果 字段 为零值,那么 会自动忽略该字段,无论 Table 还是 Model 下。
4. 查(First、Find)
// SELECT name, age FROM users;
db.Select("name, old").Find(&users)
db.Select([]string{"name", "age"}).Find(&users)
db.Table("users").Select("name, age").Where("age = ?", 3).Scan(&result)
// 获取第一个匹配记录SELECT * FROM users WHERE name = 'tom' limit 1;
db.Where("name = ?", "tom").First(&user)
// 获取所有匹配记录SELECT * FROM users WHERE name = 'tom';
db.Where("name = ?", "tom").Find(&users)
// <> 不等于
db.Where("name <> ?", "tom").Find(&users)
// IN 字符范围 (注意:不能数数组,只能是切片)
db.Where("name in (?)", []string{"tom", "tom2"}).Find(&users)
// BETWEEN 数值范围
db.Where("old BETWEEN ? AND ?", 5, 10).Find(&users)
// LIKE 模糊
db.Where("name LIKE ?", "%tom%").Find(&users)
// AND 多条件
db.Where("name = ? AND age >= ?", "tom", "22").Find(&users)
// 多条件查询:结构体 模式
db.Where(&Us{Name: "tom", Old: 22}).Find(&users)
// 多条件查询:map 模式
db.Where(map[string]interface{}{"name": "tom", "old": 22}).Find(&users)
// 多条件查询: 链式 模式
db.Where("name = ?","tom").Where("old = ?",10).Find(&users)
// Time 时间
// 可以是 时间体格式
db.Where("created_at > ?", time.Now()).Find(&users)
// 可以是 字符串 格式
db.Where("created_at BETWEEN ? AND ?", "2021-8-20 10:01:02", "2021-8-30 15:22:13").Find(&users)
// 可以为空, 则 忽略该 时间的限制搜索条件
db.Where("created_at > '' ", ).Find(&users)
// 扩展: 如果where中直接为空,则直接 忽略 该where函数。
db.Where("").Find()
变动态条件字段查询
:
map 和 struct 在处理值 0
和 空字符
上 的策略是不一样的:
// map
data := test{}
m := map[string]interface{}{
"id": 0,
"name": "tom",
"age": "",
}
db.Debug().Where(m).Find(&data)
// SELECT * FROM `test` WHERE (`test`.`id` = 0) AND (`test`.`name` = 'tom') AND (`test`.`age` = '')
// struct (此处不支持匿名结构体!!!!!!)
data := test{}
m := test{
Id: 0,
Name: "tom",
Age: "",
}
db.Debug().Where(m).Find(&data)
//SELECT * FROM `test` WHERE (`test`.`name` = 'tom')
实用的 __非原生__ 多类型 多变动态条件 字段查询
:
var sql1 string
if inData.Name != "" {
sql1 = fmt.Sprintf("name like '%%%s%%'", inData.Name)
}
var sql2 string
if inData.StartTime != "" {
sql2 = fmt.Sprintf("create_at >= '%s'", inData.StartTime)
}
var sql3 string
if inData.EndTime != "" {
sql3 = fmt.Sprintf("create_at <= '%s'", inData.EndTime)
}
class := make([]models.CommodityClass, 0, 50)
var total int
err = tools.DB.Where(sql1).Where(sql2).Where(sql3).Where("deleted = 0").
Offset((inData.Curpage - 1) * inData.PageSize).
Limit(inData.PageSize).
Find(&class).
Count(&total).Error
说明:Where(“”) 如果其参是空字符串的话,则该Where自动选择不执行。
实用的 __原生__ 多类型 多变动态条件 字段查询
:
func main() {
var a = 0
var b = 0
var c = 1
var sql string = "select * from test "
preLen := len(sql) //关键点
if a > 0 {
sql += SqlDynamicCondition(len(sql) == preLen, "age = 'cat'")
}
if b > 0 {
sql += SqlDynamicCondition(len(sql) == preLen, "name = 'tom'")
}
if c > 0 {
sql += SqlDynamicCondition(len(sql) == preLen, "id = 22")
}
fmt.Println(sql)
aaa := make([]Test, 0, 10)
db.Raw(sql).Scan(&aaa)
fmt.Println(aaa)
}
func SqlDynamicCondition(b bool, cond string) string {
if b {
return "where " + cond
} else {
return " and " + cond
}
}
- map 通过 一些手段 可以实现 struct 的策略
比如前面的传值只有时间,则筛选数据的条件就只有时间;
比如前面的传值有时间 和 区域,则筛选数据的条件是 时间 + 区域 ;// map实现这一点的核心机制: for k, v := range m { if v == " " || v == 0 { delete(m, k) } }
- struct 通过 一些手段 可以实现 map 的策略
// 如果不想让struct忽略 空值 的字段,可以在后面加 将 条件用where方法补上:
db.Debug().Where(m).Where("id = 0 and age = '' ").Find(&data)
其他说明:
- 如果是 First()查找 用 模型结构体变量 接受结果;
- 如果是 Find()查找 用 模型结构体切片 接受结果;
var user User
db.Debug().Where("name = ?", "tom").First(&user)
fmt.Println(user)
var users []User = make([]User, 3)
db.Debug().Where("name <> ?", "tom").Find(&users) // &
fmt.Println(users)
重点强调:
结构体 对应定位 数据表 时,最重要的就是 结构体名 和 数据表名 的对应,
GORM 转换规则:结构体名:
ABCd
—GORM转换之后—> 数据表名:a_b_cd
属 性 名 :
ABCd
—GORM转换之后—> 字段名:a_b_cd
我清楚地记得在 mysql手册中 强调过 mysql 的命名规范:
库名、表名、字段名: 小写字母 + 下划线 (
禁止出现大写, 禁止用- 连接
)这会该领悟到深意了吧,如果数据库 的 命名 不尊出规范,按照GORM的转换规则,结构体名 永远都 定位不到 数据表名。
重点强调 的 扩展重点
- 查询时 如果 结构体名 定位不到 结构体名,那么一点数据都查不到,
如果 有的 属性名 定位到了 字段名, 有的没定位到,那么仅仅是 定位到的属性可以获取到数据。
也就是说查询时 结构体中的 属性 和 数据表 中的字段 不必 一一对应
。- 添加数据时, 如果 属性 和 字段 对应不严禁 会添加失败,但是如果 结构体有的属性 都完全对应上了,但是相比于数据表,结构体有缺失的属性,且缺失的属性不是 非空的字段, 那么是不会出错的。
^_^ 常用api
Debug
: 查看GORM相关方法 操作数据库背 后执行的SQL语句, 这个是相当重要的。
db.Debug().Delete(new(Us))
db.LogMode(true)
启用Logger,每个数据库操作都会显示详细日志,无需一个个调用Debug方法。
- Scan : 将结果
扫描
到另一个结构中(和Raw方法挺般配的) - Raw: 执行
原生
SQL语句,查询
用Raw。
sum := struct {
TotalFee float64
Scores int
PostFee float64
DiscountFee float64
PayFee float64
BuyNum int
}{}
var sql = `select sum(total_fee) total_fee,
sum(scores) scores,
sum(post_fee) post_fee,
sum(discount_fee) discount_fee,
sum(pay_fee) pay_fee,
sum(buy_num) buy_num
from shop_order_single_kids
where fid = ?`
err := tools.DB.Raw(sql, 10).Scan(&sum).Error // 这种方式下可以使用 匿名结构体哦
应用场景:
用匿名结构体承接查询操作的结果时,往往需要执行原生的sql语句:
aaa 表中的数据
+-----+------+------------+
| id | name | time |
+-----+------+------------+
| 2 | cat | NULL |
| 11 | ttom | 2021-05-07 |
| 111 | tom | 2021-05-07 |
+-----+------+------------+
obj := struct {
Name string
Time time.Time
}{}
db.Raw("SELECT * FROM aaa WHERE id = ?", 111).Scan(&obj)
fmt.Println(obj) //{tom 2021-05-07 00:00:00 +0800 CST}
- Exec: 执行原生SQL语句,除了
查询
都用Exec。
db.Exec("DROP TABLE users;")
db.Exec("UPDATE orders SET name = ? WHERE id IN (?)", "tom", []int{11,22})
- Offset( n interface{} ): 跳过n条数据 开始查询
- Limit( n interface{} ): 查出 n 条数据 便可以停止查询
db.Where("name = ? and age = ?", "tom", 10) .Offset(5) .Limit(10) .Find(new(Users))
- Order(): 指定排序
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
说明:
sql语句中要求 order 语句要在 limit语句之后,
orm的好处就是 没有这么多的要求,对于 这方面倒是没有要求。
- Count: 统计符合条件的记录的总条数
var sum int
db.Table("users").Where("name = ? and age = ?", "tom", 10).Count(&sum)
Row
:统计指定列的数值总和
var age float64
db.Debug().Model(new(Users)).Where("uid in (?)", ids).
Select("COALESCE(sum(age), 0)").Row().Scan(&age)
// 注意这里 一定要使用 COALESCE 函数,他的作用是 返回第一个 非Null 值 的参数,
// 在不使用COALESCE下,如果 where没有匹配的记录,那么 Null赋值给 age变量 就会报错:
// Scan error on column index 0, name "sums": converting NULL to float64 is unsupported
^_^ 事物
- 开始事务: tx := db.Begin()
- 在事务中做一些数据库操作(从这一点使用’tx’,而不是’db’): tx.Create(…)
- 发生错误时回滚事务:tx.Rollback()
- 提交事务: tx.Commit()
db := tools.DB
tx := db.Begin() // 开始事物
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
panic(err)
}
data := Test{}
// (悲观锁 FOR UPDATE )锁住指定 id 的 User 记录(让后面查该条记录的SQL语句阻塞等待)
err := tx.Set("gorm:query_option", "FOR UPDATE").Where("id = ?", 1).First(&data).Error
if err != nil {
tx.Rollback()
}
err = tx.Commit().Error // 结束事物,释放锁
if err != nil {
tx.Rollback()
}
事物串行
事物 默认锁
- 如果并行执行的两个事物中都是读操作,那么两个事物的执行不会出现阻塞的情况。
- 如果并行执行的两个事物中有读有写操作,A事物先一步发起,那么A事物的写操作,会阻塞 B事物的 写操作,而不会阻塞B事物的读操作。
这催生的结果是:两个事物 对用一个行数据进行 写操作, 哪个事物最后完成,那么 该行数据就 最后的结果 就 让哪个事物说了算。
事物 串行(悲观锁)
有些情况下 我们 希望 事物的读操作 也可以
阻塞
其他并行事物的 读操作, 换言之就是让事物串行。
// 事物A
tx := db.Begin()
t := test{}
// 串行读操作 等同于sql: select * from test where id = 25 for update;
err = tx.Set("gorm:query_option", "FOR UPDATE").Where("id = 25").First(&t).Error
if err != nil {
fmt.Println(err)
tx.Rollback()
}
fmt.Println(t)
err = tx.Commit().Error
if err != nil {
fmt.Println(err)
tx.Rollback()
}
// 事物B
tx := db.Begin()
t := test{}
// 读操作
err = tx.Set("gorm:query_option", "FOR UPDATE").Where("id = 25").First(&t).Error
if err != nil {
fmt.Println(err)
tx.Rollback()
}
fmt.Println(t)
err = tx.Commit().Error
if err != nil {
fmt.Println(err)
tx.Rollback()
}
注意: 锁 锁住的本质是 索引!!!
并行执行两个事物虽然都是 读操作,但是是串行执行。
^_^连接池
package db
import (
"gt/tools"
"os"
"time"
beego "github.com/beego/beego/v2/server/web"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
var db *gorm.DB
func init() {
openConn()
}
func openConn() {
//----获取数据库相关配置--start-------
hostName, err := beego.AppConfig.String("mysqluser")
if err != nil {
panic("配置文件中数据库主机用户账号异常")
}
password, err := beego.AppConfig.String("mysqlpassword")
if err != nil {
panic("配置文件中数据库主机用户密码异常")
}
mysqlurls, err := beego.AppConfig.String("mysqlurls")
if err != nil {
panic("配置文件中数据库主机地址信息异常")
}
mysqldb, err := beego.AppConfig.String("mysqldb")
if err != nil {
panic("配置文件中数据库选择异常")
}
para1 := "mysql"
para2 := hostName + ":" + password + "@(" + mysqlurls + ")/" + mysqldb + "?charset=utf8mb4&parseTime=True&loc=Local"
conn, err := gorm.Open(para1, para2) // 通过Open方法连接数据库
if err != nil {
tools.Console_error("连数据库接失败" + err.Error())
os.Exit(3)
} else {
tools.Console_ok("mysql 连接成功")
}
conn.LogMode(true)
conn.SingularTable(true)
// 建立连接池
// GORM 提供了 DB方法 ,可以从当前 *gorm.DB 连接内,获取一个通用的数据库接口*sql.DB
/*
默认情况下。每次执行sql语句,都会创建一条tcp连接,执行结束就会断掉连接,但是会保留两条连接闲置。
当下次再执行 sql时,先用闲置的连接,不够的时候再去创建连接。
当设置了Db类下的这两个参数,就可以真正的实现连接池了。
*/
db = conn
conn.DB().SetMaxIdleConns(10) //最大空闲连接数
conn.DB().SetMaxOpenConns(100) //最大连接数
conn.DB().SetConnMaxLifetime(time.Second * 300) //设置连接空闲超时
}
/*
.Ping() 调用完毕后会马上把连接返回给连接池。
.Exec() 调用完毕后会马上把连接返回给连接池,但是它返回的Result对象还保留这连接的引用,当后面的代码需要处理结果集的时候连接将会被重用。
.Query() 调用完毕后会将连接传递给sql.Rows类型,当然后者迭代完毕或者显示的调用.Clonse()方法后,连接将会被释放回到连接池。
.Begin() 调用完毕后将连接传递给sql.Tx类型对象,当.Commit()或.Rollback()方法调用后释放连接。
.QueryRow()调用完毕后会将连接传递给sql.Row类型,当.Scan()方法调用之后把连接释放回到连接池。
*/
func DB() *gorm.DB {
// 调用了Ping之后,连接池一定会初始化一个数据库连接
err := db.DB().Ping()
if err != nil {
tools.Console_warn(err.Error())
// 如果ping不同,则 重连一下
db.Close()
openConn()
}
return db
}
^_^行为回调函数
package main
import (
"fmt"
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
var (
arr1 = "mysql"
arr2 = "root:123456@(localhost:3306)/wtt?charset=utf8&parseTime=True&loc=Local"
DB *gorm.DB
)
func init() {
db, _ := gorm.Open(arr1, arr2)
db.LogMode(true)
db.SingularTable(true)
// 替换 回调函数
db.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback)
db.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
DB = db
}
type BlogTag struct {
Id int `gorm:"column:id;primary_key;AUTO_INCREMENT" json:"id"`
Name string `gorm:"column:name" json:"name"` // 标签名称
State int `gorm:"column:state;default:1" json:"state"` // 状态: 0禁用 1启用
CreatedOn int `gorm:"column:created_on;default:0" json:"created_on"` // 创建时间
CreatedBy string `gorm:"column:created_by" json:"created_by"` // 创建人
ModifiedOn int `gorm:"column:modified_on;default:0" json:"modified_on"` // 修改时间
ModifiedBy string `gorm:"column:modified_by" json:"modified_by"` // 修改人
DeletedOn int `gorm:"column:deleted_on;default:0" json:"deleted_on"` // 删除时间
IsDel int8 `gorm:"column:is_del;default:0" json:"is_del"` // 是否删除: 0否 1是
}
func main() {
aaa := BlogTag{
Name: "tom",
State: 1,
}
err := DB.Create(&aaa).Error
if err != nil {
fmt.Println(err)
}
// 注意: 更新时 要是想回 回调函数, 必须使用 Model(new(BlogTag)), 而不能 Table("blog_tag")
err = DB.Model(new(BlogTag)).Where("id =1").Update("name", "123123123").Error
if err != nil {
fmt.Println(err)
}
}
// 新增行为 的 回调
func updateTimeStampForCreateCallback(scope *gorm.Scope) {
if !scope.HasError() {
nowTime := time.Now().Unix()
// 获取当前是否 包含 当前字段
createTimeField, ok := scope.FieldByName("CreatedOn")
if ok {
// IsBlank: 获知 该字段是否为空
if createTimeField.IsBlank {
// 若为空,通过 Set 给该字段 赋值
createTimeField.Set(nowTime)
}
}
// 获取当前是否 包含 当前字段
createTimeField, ok = scope.FieldByName("CreatedBy")
if ok {
// IsBlank: 获知 该字段是否为空
if createTimeField.IsBlank {
// 若为空,通过 Set 给该字段 赋值
createTimeField.Set("wanghaiou")
}
}
}
}
// 更新行为的 回调
func updateTimeStampForUpdateCallback(scope *gorm.Scope) {
_, ok := scope.Get("gorm:update_column")
if !ok {
scope.SetColumn("ModifiedOn", time.Now().Unix())
scope.SetColumn("ModifiedBy", "wttwtt")
}
}
二、GORM 操作 PostGresSQL
小试牛刀
package main
import (
"fmt"
"log"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
)
// postgres 的 模式: 可以理解为 数据表的 命名空间
// public 模式(默认)下的 数据表
type User struct {
Id int `gorm:"primary_key"`
Name string `gorm:"type:varchar(50);not null;index:ip_idx"`
Age int `gorm:"not null"`
Addr string `gorm:"type:varchar(50);not null;"`
}
func main() {
// ========================== 连接数据库 ==============================
arr1 := "postgres"
arr2 := "host=127.0.0.1 port=5432 user=postgres dbname=postgres password=123456 sslmode=disable"
db, err := gorm.Open(arr1, arr2)
if err != nil {
log.Println(err)
} else {
log.Println("连接成功!")
}
defer db.Close()
// ========================== 创建数据表 ==============================
if !db.HasTable(&User{}) {
if err := db.CreateTable(&User{}).Error; err != nil {
panic(err)
} else {
log.Println("数据表 创建成功!")
}
}
// ========================== 增 ==============================
user := &User{
Id: 1,
Name: "code",
Age: 20,
Addr: "大连",
}
if err := db.Create(user).Error; err != nil {
log.Println("插入失败!", err)
return
} else {
log.Println("插入成功!")
}
// ========================== 查 ==============================
var u User
err = db.Where("id = ?", 1).First(&u).Error
if err != nil {
fmt.Println(gorm.ErrRecordNotFound == err)
fmt.Println(err)
} else {
fmt.Println(u)
}
// ========================== 改 ==============================
err = db.Table("users").Where("id = 1").Update("name", "aahah").Error
if err != nil {
fmt.Println(err)
}
// ========================== 删 ==============================
err = db.Where("id = ?", 1).Delete(new(User)).Error
if err != nil {
fmt.Println(err)
} else {
fmt.Println("删除成功")
}
}
上面 增删查改的 数据操作 针对的 数据表 都是 public 模式下的。
如果 自己 自定义了一个 goods 模式,并且在 goods模式下 创建了 goods_skus表,
如果要 操作goods_skus表, 则需要 通过 tabler()方法 对 模式 和 数据包 进行 手动指定,例如:
db.Tabler("goods.goods_skus").Create(user)
db.Tabler("goods.goods_skus").Where("id = ?", 1).First(&u)
db.Table("goods.goods_skus").Where("id = 1").Update("name", "aahah")
db.Table("goods.goods_skus").Where("id = ?", 1).Delete(new(User))