GORM(推荐)

^_^ 前序

特点

  • 全特性 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说明:

  1. 如果数据库中没有UserTable结构体映射成的表,则此时自动迁移就是创建一个表;
  2. 如果数据库中有UserTable结构体映射成的表,但是 结构体中 的字段 比 表中的字段 有后来增加的,则此时自动迁移就是 将 结构体 中 增加的字段 同步增加到 表中。

注意: 自动迁移 只会创建、添加, 不会 编辑 和 删除。

上面代码生成的表:
表名: user_tables
表结构:

FieldTypeNullKeyDefaultExtra
idint(10) unsignedNOPRINULLauto_increment
user_namevarchar(255)YESNULL
ageint(11)YESNULL
is_ittinyint(1)YESNULL



^_^ 映射规则(模型定义)重点!

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{})

对应的表结构:

FieldTypeNullKeyDefaultExtracomment
idint(11)NOPRINULLauto_increment主键
namevarchar(10)YEStom
id_cardchar(18)NOUNINULL
uidtinyint(4)YES0int8 =对应=>tinyint(4)
pidsmallint(5)YES0int16 =对应=>int(11)
fidmediumint(7)YES0int32 =对应=>int(11)
cidbigint(20)YES0int64 =对应=>bigint(20)
feedecimal(5,2)YES0.00
create_atdatetimeYESNULL创建时间

简单说明:

  • 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()
	}

事物串行

事物 默认锁
  1. 如果并行执行的两个事物中都是操作,那么两个事物的执行不会出现阻塞的情况。
  2. 如果并行执行的两个事物中有操作,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))
gorm scopes是Golang中使用的一种功能,用定义和应用查询作用域。它可以帮助我们在查询数据库时,根据不同的条件和需求,动态地构建查询语句。 使用gorm scopes,我们可以将一组查询条件封装成一个作用域(scope),然后在需要的时候应用到查询中。这样可以使代码更加模块化和可复用,同时也能提高查询的灵活性和可读性。 在gorm中,我们可以通过定义结构体的方法来创建作用域。这些方法需要接收一个gorm.DB类型的参数,并返回一个gorm.DB类型的结果。在方法内部,我们可以使用gorm提供的各种查询方法来构建查询条件,例如Where、Order、Limit等。 下面是一个使用gorm scopes的示例: ```go type User struct { ID uint Name string Age int } func (db *gorm.DB) AgeGreaterThan(age int) *gorm.DB { return db.Where("age > ?", age) } func main() { db, err := gorm.Open("mysql", "user:password@tcp(localhost:3306)/database") if err != nil { panic(err) } defer db.Close() var users []User db.Scopes(db.AgeGreaterThan(18)).Find(&users) } ``` 在上面的示例中,我们定义了一个名为AgeGreaterThan的作用域,它接收一个年龄参数,并返回一个添加了查询条件的gorm.DB对象。然后,在main函数中,我们通过调用Scopes方法并传入AgeGreaterThan作用域,来应用该作用域到查询中。 这样,最终执行的查询语句将会是`SELECT * FROM users WHERE age > 18`,并将结果存储到users变量中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值