通过gorm操作mysql

GORM是Golang的ORM(Object Relational Mapping)操作库,使用上主要就是把struct类型和数据库表记录进行映射,操作数据库的时候不需要直接手写Sql代码,本例子以gorm版本1.23.8为例子

GORM库地址:https://github.com/go-gorm/gorm

官方文档地址:https://gorm.io/zh_CN/docs/

一、gorm安装:

操作MySQL需要安装两个包:

  • MySQL驱动包
  • GORM包 使用go get命令安装依赖包
//安装MySQL驱动
go get -u gorm.io/driver/mysql
//安装gorm包
go get -u gorm.io/gorm

导入包:

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

二、gorm模型定义:

1、ORM简介:

ORM框架操作数据库都需要预先定义模型,模型可以理解成数据模型,作为操作数据库的媒介

  • 从数据库读取的数据会先保存到预先定义的模型对象,然后我们就可以从模型对象得到我们想要的数据
  • 插入数据到数据库也是先新建一个模型对象,然后把想要保存的数据先保存到模型对象,然后把模型对象保存到数据库

gorm模型定义是通过struct实现的,这样我们就可以通过gorm库实现struct类型和mysql表数据的映射

注:gorm负责将对模型的读写操作翻译成sql语句,然后gorm再把数据库执行sql语句后返回的结果转化为我们定义的模型对象

2、模型定义:

gorm模型定义主要就是在struct类型定义的基础上增加字段标签说明实现,首先定义一个订单表:

CREATE TABLE `productorder` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID,订单Id',
  `name` varchar(50) NOT NULL COMMENT '订单名称',
  `quantity` varchar(50)  NOT NULL COMMENT '订单数量',
  `createtime` int NOT NULL DEFAULT 0  COMMENT '订单创建时间',
   PRIMARY KEY (`id`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '商品订单';

模型定义如下(注:结构体字段名首字母一定要大写):

type productorder struct {
        ID         int       //表字段名为id
        Name       string    //表字段名为name
        Quantity   string    //表字段名为quantity
        Createtime int       //表字段名为create_time
}

默认gorm对struct字段名使用Snake Case命名风格转换成mysql表字段名,因此转换后都是小写的名字,多个单词之间用下划线分隔,例如CreateTime 转换为就是create_time

注意:Snake Case命名风格,就是各个单词之间用下划线(_)分隔,例如: CreateTime的Snake Case风格命名为create_time

注意:如果结构体名为productOrder,那么对应的数据库表名就是product_order

3、gorm模型标签:

上述模型中CreateTime映射到的表字段为create_time,如果要变成正常的createtime,可通过模型标签实现,语法如下:

gorm:"标签定义"     //多个标签定义可以使用分号(;)分隔

gorm常用标签如下:

标签说明例子
column指定列名gorm:”column:createtime”
primaryKey指定主键gorm:”column:id;primary_key”
忽略字段gorm:”-” 可以忽略struct字段,被忽略的字段不参与gorm的读写操作

上述模型修改后的内容如图:

注意:在gorm和column中不能出现空格,否则会失效

4、定义表名:

默认情况下,模型对应的表名为复数形式,比如模型名为productorder,那么对应的表名为productorders,如果要去掉复数形式,有两种方式:

第一种方式,通过调用Tabler接口的TableName()函数,返回一个新的表名,如下:

func (productorder) TableName() string {
	return "productorder"         //返回单数形式的表名
}

默认情况下都给模型定义表名,有时候定义模型只是单纯的用于接收手写sql查询的结果,这个时候是不需要定义表名;手动通过gorm函数Table()指定表名,也不需要给模型定义TableName函数

  • 在gorm 1.19版本,TableName方法在gorm目录下的scope.go里
  • 在gorm 1.23版本, TableName方法在gorm目录下的schema.go里

第二种方式通过在连接数据库时定义SingularTable: true实现,可参考下面文章

三、gorm连接数据库:

gorm支持多种数据库,本文主要讲解连接mysql,主要分为两个步骤:

  • 配置DSN (Data Source Name)
  • 使用gorm.Open连接数据库

1、配置DSN,语法格式如下:

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
        user := "root"          //用户
        password := "123456789" //密码
        dbName := "test"        //数据库名
        host := "127.0.0.1"     //主机名
        port := "3306"          //端口
        dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", user, password, host, port, dbName)
        //charset=utf8 客户单字符集编码为utf8
        //parseTime=true,支持把数据库datetime和date类型转换为golang的time.Time类型
        //loc=Local 使用系统本地市区
        //readTimeout 读超时时间,0代表不限制,本例子中没设置此参数,可根据需要添加
        //writeTimeout - 写超时时间,0代表不限制,本例子中没设置此参数,可根据需要添加

2、使用gorm.Open连接数据库:

        var err error
        var db *grom.DB              //定义指针变量
        db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
                NamingStrategy: schema.NamingStrategy{
                        SingularTable: true,   //设置表名为单数形式
                        TablePrefix:   "sys_", //设置表前缀
                },
        })
        if err != nil {
                log.Fatal(err)
        }
  • gorm.Open()返回类型即为*gorm.DB和error,可通过go doc gorm Open查询文档
  • schema需要导入包gorm.io/gorm/schema
  • SingularTable: true 设置表名为单数形式,即为上文中设置表名的第二种方法
  • TablePrefix:设置表前缀

注:gorm.Config{} 如果没有特别设置,可以留空

3、连接池:

在高并发实践中,为了提高数据库连接的使用率,避免重复建立数据库连接带来的性能消耗,会经常使用数据库连接池技术来维护数据库连接

配置如下:

        sqlDB,_ := db.DB()
        //SetMaxIdleConns 设置空闲连接池中连接的最大数量
        sqlDB.SetMaxIdleConns(10)
        //SetMaxOpenConns 设置打开数据库连接的最大数量
        sqlDB.SetMaxOpenConns(100)
        //SetConnMaxLifetime 设置了连接可复用的最大时间
        sqlDB.SetConnMaxLifetime(time.Hour)

注:db.DB()将返回一个通用的数据库接口*sql.DB,直接通过go doc sql即可查看用法,使用连接池技术后,千万不要使用完db后调用db.Close关闭数据库连接,这样会导致整个数据库连接池关闭,导致连接池没有可用的连接。

完整的连接数据库并执行命令创建sql的内容如下:

1	package main
     2	
     3	import (
     4		"fmt"
     5		"gorm.io/driver/mysql"
     6		"gorm.io/gorm"
     7		"gorm.io/gorm/schema"
     8		"log"
     9		"time"
    10	)
    11	
    12	//注意:如果此结构体需要导出,那么productorder的首字母需要大写
    13	type productorder struct {
    14		ID         int    `gorm:"primary_key"`
    15		Name       string //表字段名为name
    16		Quantity   string //表字段名为quantity
    17		CreateTime int    `gorm:"column:createtime"`
    18	}
    19	
    20	//func (productorder) TableName() string {
    21	//	return "productorder"
    22	//}
    23	
    24	var db *gorm.DB
    25	//开发时,一般都是将初始化sql语句放在init()函数中,先执行
    26	func init() {
    27		user := "root"             //用户
    28		password := "5182086abcD#" //密码
    29		dbName := "test"           //数据库名
    30		host := "127.0.0.1"        //主机名
    31		port := "3306"             //端口
    32		dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", user, password, host, port, dbName)
    33		//charset=utf8 客户单字符集编码为utf8
    34		//parseTime=true,支持把数据库datetime和date类型转换为golang的time.Time类型
    35		//loc=Local 使用系统本地市区
    36		//readTimeout 读超时时间,0代表不限制
    37		//writeTimeout - 写超时时间,0代表不限制
    38		var err error
    39		db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
    40			NamingStrategy: schema.NamingStrategy{    //schema包的结构体NamingStrategy
    41				SingularTable: true,   //设置表名为单数形式
    42				TablePrefix:   "sys_", //设置表前缀
    43			},
    44		})
    45		if err != nil {
    46			log.Fatal(err)
    47		}
    48		sqlDB, err := db.DB()
    49		//SetMaxIdleConns 设置空闲连接池中连接的最大数量
    50		sqlDB.SetMaxIdleConns(10)
    51		//SetMaxOpenConns 设置打开数据库连接的最大数量
    52		sqlDB.SetMaxOpenConns(100)
    53		//SetConnMaxLifetime 设置了连接可复用的最大时间
    54		sqlDB.SetConnMaxLifetime(time.Hour)
    55	}
    56	func main() {
    57		p := productorder{
    58			ID:         1,
    59			Name:       "phone",
    60			Quantity:   "100部",
    61			CreateTime: 20220728,
    62		}
    63		db.Create(&p)
    64	}

注意:如果第20-22行不注释掉,那么第41和42行设置就不会生效,注释后,表前缀就要变为sys_productorder,表名即为模型名的单数形式,查询sql后如下:

4、gorm调试模式:

为了方便调试,了解gorm操作到底执行了怎么样的sql语句,开发的时候需要打开调试日志,这样gorm会打印出执行的每一条sql语句

使用Debug函数执行即可,如下:

db.Debug().Create(&p) //调用Debug()后在调用执行命令语句,执行即可看到sql语句

注:调试时候可以加Debug(),实际生产不用加

、自动更新时间:

1、通过gorm包的Model内置好的字段实现:

GORM 约定使用 CreatedAt、UpdatedAt 追踪创建/更新时间。如果定义了这种字段,GORM 在创建、更新时会自动填充当前时间,将上面例子中的模型修改如下:

  • 创建时间字段为CreatedAt,类型为time.Time,映射到表中字段为createdat
  • 更新时间字段为UpdatedAt,类型为time.Time,映射到表中字段为updatedat

注意:字段不可以乱写,必须写为CreatedAt,UpdatedAt,这两个字段是gorm自带的结构体Model中的字段,如果不重新写,也可以直接通过继承的方式,只需要在自定义结构体中gorm.Model即可

表结构中添加对应的字段,如图:

在实例化结构体模型的时候,不需要指定CreatedAt,UpdatedAt字段,只需要指定原有字段即可,如图:

执行命令go run productorder.go,查看表中数据,创建时间和更新时间都自动填充为当前时间,如图:

将命令修改为更新命令,将phone5改为phone1,命令如下:

db.Debug().Model(&productorder{}).Where("name = ?","phone5").Update("name","phone1")

再次查看表中数据,发现name改了后,对应的更新时间也变化了,如图:

注:通过结构体创建数据可以自动创建时间(createdat),不需要指定字段,但是通过map创建数据则需要指定createdat,否则无法显示创建时间,通过map更新数据不需要指定updatedat

2、通过自定义字段实现:

要通过自定义字段实现,需要配置 autoCreateTime、autoUpdateTime 标签

修改结构体模型,添加自定义创建时间字段和自定义修改时间字段,并配置autoCreateTime、autoUpdateTime 标签,如图:

在数据库表中创建对应的映射字段customupdatetime、customcreatetime,如图:

通过如下命令执行创建sql,不需要添加自定义时间字段,如图:

查看数据库表,看到时间字段已经自动填充,如图:

再次执行如下命令更新数据库表:

db.Debug().Model(&productorder{}).Where("name = ?","phone5").Update("name","phone1")

查看数据库表更新时间字段,已经改变,如图:

五、常用的gorm命令如下:

创建数据

1、根据结构体指针创建单条记录,如图:

注:productorder为结构体

2、批量插入数据,需要将一个切片传递给Create方法,首先看下没有批量插入数据之前的表内容:

定义结构体类型切片,将结构体添加到切片中,然后插入到数据库,如图:

插入后的内容如图:

3、通过map来创建单条数据,如图:

注:需要通过Model方法执行要运行的数据库操作模型,可以理解为指定数据表,Create()中尽量使用指针,本例子中忘记写了指针符号&

查看数据库内容如下:

4、通过map创建多条数据,如图:

查看sql数据如下:

注:通过map方式创建数据的时候,需要在map中定义createdat字段,并设置值为time.Now(),如上图,这样才可以在数据库中显示创建时间,通过map更新数据则不需要,执行后更新时间会自动变化

查询数据

1、查询表中第一条数据,注意要使用结构体指针,如图:

执行结果如下:

注:查询后的数据会保存到模型中,也就是结构体中,因此可直接获取值

2、查询表中最后一条数据,如图:

注意:First()和Last()会根据主键排序,并且只有在目标struct是指针或者通过db.Model()指定model时候,此方法才有效

First(),Take(),Last()方法如果查询不到记录,会返回ErrRecordNotFound错误,如图:

注:看语句结尾要用.Error,前面通过err接收,如果要避免ErrRecordNotFound错误,可以使用下面的Find()方法

3、查询全部数据,用Find()方法,如图:

运行后结果如下:

注:Find()方法可以接受struct和slice数据

5、查询全部数据中,name=phone5的数据,通过切片遍历出来,如图:

执行结果如下:

6、通过Error来判断语法是否错误,下面估计将=写成==,如图:

执行结果如下:

7、通过Select根据Where条件查询id字段数据,如下:

运行结果如下:

注意:上图中根据Select来查询id字段,那么实际查询到数据后,会将id字段映射到结构体模型的ID字段,其余的字段并没有查询,因此其余字段的值还是默认的零值,如果此时打印SystemType的值将为空的字符串(如果是全部查询,那么每个字段都可以打印出来值)

更新数据

本例子以数据库test中的表abc为例子,如图:

1、更新多个列值:

下面例子表示更新id为1列名为system_type和ip_address列的值,如图:

运行结果如下,已经更新成功:

注意:更新多个列用的是Updates方法,Updates 方法支持 struct 和 map[string]interface{} 参数,当使用struct更新时,GORM只会更新非零值的字段

2、更新单个列值

还是以上图中为例,更新id为1的列hostname的值为window100,如图:

运行后结果如下,已经更新成功:

注:更新单个列用的是update,并且需要指定字段名hostname

更多用法如下:

//查询全部数据中,name=phone5的数据的第一条数据
db.Where("name = ?","phone5").First(&p)
//查询全部数据中,name不等于phone5的所有记录
db.Where("name <> ?","phone5").Find(&p)

执行原生SQL

原生SQL就是直接操作数据库的SQL语句,通过gorm执行此语句,指定原生SQL有两种方式,一种是Raw(),一种是Exec()

通过Raw()来执行SQL:

首先查看数据库temp_database的表role内容如下:

1、定义结构体,查询数据库获取role_name的值,如图:

定义结构体切片,用于存储从数据库中查询的数据,如图:

  • 39行:通过db.Raw()来执行原生SQL,通过Scan映射到对应的切片中

最后打印的结果就是结构体切片,如图:

2、根据条件,获取id为10000的用户角色,如图:

3、通过两个条件进行判断,并通过query来分行写语句,如图:

注:10000和”init”分别对应id和role_flag后面的值

4、通过db.Raw()来指定update语句,将id为10000的role_flag修改为abcd,如图:

通过Exec()指定SQL:

1、指定update命令,修改id为10000的role_flag的值为gong,如图:

注:db.Exec()后是没有Scan()的,此处和Raw()不一样的

2、gorm也支持表达式,可进行运算,下面修改id为表达式的值,如图:

Raw()和Exec()的区别:

  • Raw() :可执行原生SQL或可执行命令,并返回结果或影响的行数,然后调用Scan()将结果映射到结构体中,一般用于查询数据SELECT
  • Exec() :可执行原生SQL或可执行命令,无需映射到结构体,不返回结果,一般用于INSERT、update、delete语句

例句:通过RAW()执行原生SQL来判断主从状态,如下:

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
	"time"
)

// 此处的结构体名可自定义
type slavestatus struct {
	SlaveIORunning      string `gorm:"column:Slave_IO_Running"`
	SlaveSQLRunning     string `gorm:"column:Slave_SQL_Running"`
	SecondsBehindMaster int    `gorm:"column:Seconds_Behind_Master"`
}

var db *gorm.DB

func init() {
	user := "root"
	password := "Abcd1234"
	dbName := "mysql"    //此时这个库名也可以随便写,只要存在就行
	host := "127.0.0.1"
	port := "3306"
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local", user, password, host, port, dbName)
	var err error
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal(err)
	}
	sqlDB, err := db.DB()
	//SetMaxIdleConns 设置空闲连接池中连接的最大数量
	sqlDB.SetMaxIdleConns(10)
	//SetMaxOpenConns 设置打开数据库连接的最大数量
	sqlDB.SetMaxOpenConns(100)
	//SetConnMaxLifetime 设置了连接可复用的最大时间
	sqlDB.SetConnMaxLifetime(time.Hour)
}
func main() {
	var ss slavestatus
	db.Raw("show slave status").Scan(&ss)
	fmt.Println("从库IO线程:", ss.SlaveIORunning)
	fmt.Println("从库SQL线程:", ss.SlaveSQLRunning)
	fmt.Println("从库落后主库时间:", ss.SecondsBehindMaster)
}

标签