ORM
对象关系映射(Object Relational Mapping,ORM)。指的是对象和关系之间的映射,使用面向对象的 方式操作数据库。
关系模型和Go对象之间的映射 table => struct ,表映射为结构体 row => object ,行映射为实例 column => property ,字段映射为属性
可以认为ORM是一种高级抽象,对象的操作最终还是会转换成对应关系数据库操作的SQL语句,数据库 操作的结构会被封装成对象。
GORM
GORM是一个友好的、功能全面的、性能不错的基于Go语言实现的ORM库。
GitHub:https://github.com/go-gorm/gorm
官方文档:https://gorm.io/zh_CN/docs/connecting_to_the_database.html
连接
package main import ( "fmt" "os" "time" "github.com/rs/zerolog/log" "gorm.io/driver/mysql" // 二次封装,底层依然依赖mysql驱动 "gorm.io/gorm" "gorm.io/gorm/logger" ) var db0 *gorm.DB func init() { var err error dsn := "gopher:123456@tcp(172.***:3306)/gopher?charset=utf8&parseTime=True&loc=Local" db0, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), }) if err != nil { log.Err(err).Send() } }
在"gorm.io/driver/mysql/mysql.go"中, import了 "github.com/go-sql-driver/mysql",也就是说驱动也导入了,Dialector的Initialize方法中使用了sql.Open
模型定义
GORM倾向于约定优于配置
- 约定使用名为ID或Id的属性会作为主键
- 约定使用snake_cases作为表名
- 结构体命名为employee,那么数据库表名就是employees
- 约定使用snake_case作为字段名,字段首字母大写采用大驼峰
- 属性名为FirstName,默认对应数据库表的字段名为first_name
如果不遵从以上约定就要自定义配置
// 不符合约定的定义,很多都需要配置,直接用不行 type Emp struct { // 默认表名emps emp_no int // 不是ID为主键,需要配置 first_name string // 首字母未大写,也需要配置 last_name string gender byte birth_date string } // 符合约定的定义如下 type student struct { // 默认表名students ID int // Id也可以 Name string // 字段首字母要大写 Age int }
// 表明配置,表名并没有遵守约定
func (Emp) TableName() string {
return "employees"
}
使用 gorm:"primaryKey" 来指定字段为主键,默认使用名为ID的属性作为主键。primaryKey是tag名, 大小写不敏感,但建议小驼峰。如果未按照约定定义字段,需要定义结构体属性时指定数据库字段名称是什么。
type Emp struct { // 约定ID、Id为主键,整数自增 Em_num int `gorm:"primaryKey;column:emp_no"` // 非约定,自定义 Birth_day time.Time `gorm:"column:birth_date"` FirstName string // 首字母大写,对应字段first_name LastName string Gender uint8 HireDate time.Time } // 表名约定 func (Emp) TableName() string { return "employees" }
迁移
下面是新建一个students表,结构体中属性类型和数据库表中字段类型的对应关系
// 迁移后,主键默认不为空,其他字段默认都是能为空的 type Student struct { ID int // 缺省主键bigint AUTO_INCREMENT Name string `gorm:"not null;type:varchar(48);comment:姓名"` Age byte // byte=>tinyint unsigned Birthday time.Time // datetime Gender byte `gorm:"type:tinyint"` } // db.Migrator().DropTable(&Student{}) db.Migrator().CreateTable(&Student{}) CREATE TABLE `students` ( `id` bigint AUTO_INCREMENT, `name` varchar(48) NOT NULL COMMENT '姓名', `age` tinyint unsigned, `birthday` datetime(3) NULL, `gender` tinyint, PRIMARY KEY (`id`) )
由于int => bigint、string => longtext,这些默认转换不符合我们的要求,所以,在tag中使用type指定 字段类型。 属性是用来构建结构体实例的,生成的SQL语句也要使用这些数据。而tag是用来生成迁移
Name string `gorm:"size:48"` 定义为varchar(48) Age int `gorm:"size:32"` 定义为4字节的int Age int `gorm:"size:64"` 定义为8字节的bigint
结构体属性类型用来封装实例的属性数据,Tag中类型指定迁移到数据库表中字段的类型。
type Student struct { Id int `gorm:"not null;autoIncrement"` Name string `gorm:"not null;type:varbinary(24);comment:姓名"` Age uint8 `gorm:"not null;default 0"` Birth_day time.Time `gorm:"type time"` Address string `gorm:"size:255"` } func main() { // 打开日志文件 fi, err := os.OpenFile("logs/info.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Err(err).Msg("info日志文件打开失败") } li := logg.Olog(fi) defer fi.Close() fe, err := os.OpenFile("logs/error.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Err(err).Msg("error日志文件打开失败") } defer fe.Close() le := logg.Olog(fe) // 创建表 err = db0.Migrator().CreateTable(Student{}) if err != nil { le.Err(err).Send() } else { msg := fmt.Sprintf("%v表创建成功", "students") li.Info().Msg(msg) } }
执行结果: