Beego框架ORM详细介绍

3,073 阅读10分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

一、介绍

对象关系映射(Object Relational Mapping,简称ORM), 它的作用是映射数据库和对象之间的关系,方便我们在实现数据库操作的时候不用去写复杂的sql语句,把对数据库的操作上升到对于对象的操作。

beego ORM 是一个强大的 Go 语言 ORM 框架。它的灵感主要来自 Django ORM 和 SQLAlchemy。它支持go语言中所有的类型存储,允许直接使用原生的SQL语句,采用GRUD风格能够轻松上手,能自动Join关联表,并允许跨数据库兼容查询。

beego支持的数据库类型

使用不同的数据库,需要导入不同的数据库驱动:

import (
    // 导入mysql驱动
    _ "github.com/go-sql-driver/mysql"
    // 导入sqlite3驱动
    _ "github.com/mattn/go-sqlite3"
    // 导入Postgres驱动
    _ "github.com/lib/pq"
)

如需使用上面某种数据库,请先go get,并且导入库的时候需要在前面使用 "_" 符号。

二、ORM特性

  • 支持 Go 的所有类型存储
  • 轻松上手,采用简单的 CRUD 风格
  • 自动 Join 关联表
  • 跨数据库兼容查询
  • 允许直接使用 SQL 查询/映射
  • 严格完整的测试保证 ORM 的稳定与健壮

三、数据库链接

本次以MySQL数据库为例对Beego整合ORM和连接数据库进行演示

3.1 安装包

(1)因为beego orm是独立的模块,所以需要单独安装包。

// 安装beego orm包
go get github.com/beego/beego/v2/client/orm

(2)安装mysql驱动

go get github.com/go-sql-driver/mysql

注意:beego orm包操作什么数据库,就需要单独安装对应的数据库驱动。

3.2 导入包

import (
    // 导入orm包
    "github.com/beego/beego/v2/client/orm"
    
    // 导入mysql驱动
    _ "github.com/go-sql-driver/mysql"
)

本次在main.go文件中进行导入。

3.3 连接数据库

操作数据库之前首先需要配置好mysql数据库连接参数,通常在beego项目中,我们都会在main.go文件,对数据库进行配置,方便整个项目操作数据库。

package main

import (
	_ "beegodemo/routers"
	"github.com/beego/beego/v2/server/web"
	// 导入orm包
	"github.com/beego/beego/v2/client/orm"
	// 导入mysql驱动
	_ "github.com/go-sql-driver/mysql"
)

// 通过init函数配置mysql数据库连接信息
func init() {
    // 这里注册一个default默认数据库,数据库驱动是mysql.
    // 第三个参数是数据库dsn, 配置数据库的账号密码,数据库名等参数
    //  dsn参数说明:
    //      username    - mysql账号
    //      password    - mysql密码
    //      db_name     - 数据库名
    //      127.0.0.1:3306 - 数据库的地址和端口
	orm.RegisterDataBase("default", "mysql", "username:password@tcp(127.0.0.1:3306)/db_name?charset=utf8&parseTime=true&loc=Local")
	
	// 打开调试模式,开发的时候方便查看orm生成什么样子的sql语句
	orm.Debug = true
}

func main() {
	web.Run()
}
MySQL数据库连接参数详解

注册数据库的函数原型:

func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error

参数说明

参数名说明
aliasName数据库的别名,用来在 ORM 中切换数据库使用
driverName驱动名字
dataSource数据库连接字符串
params附加参数

注意:ORM 必须注册一个别名为 default 的数据库,作为默认使用的数据库。

mysql数据库连接字符串DSN (Data Source Name):

username:password@protocol(address)/dbname?param=value

参数说明

参数名说明
username数据库账号
password数据库密码
protocol连接协议,一般就是tcp
address数据库地址,可以包含端口。例: localhost:3306 , 127.0.0.1:3306
dbname数据库名字
param=value最后面问号(?)之后可以包含多个键值对的附加参数,多个参数之间用&连接。

常用附加参数说明

参数名默认值说明
charsetnone设置字符集,相当于 SET NAMES 语句
locUTC设置时区,可以设置为Local,表示根据本地时区走
parseTimefalse是否需要将 mysql的 DATE 和 DATETIME 类型值转换成GO的time.Time类型。
readTimeout0I/O 读超时时间, sql查询超时时间. 单位 ("ms", "s", "m", "h"), 例子: "30s", "0.5m" or "1m30s".
timeout0连接超时时间,单位("ms", "s", "m", "h"), 例子: "30s", "0.5m" or "1m30s".

示例

root:123456@(127.0.0.1:3306)/test?charset=utf8&timeout=5s&loc=Local&parseTime=true

3.4 其他设置(非必须)

(1)数据库连接池设置

数据库连接词参数主要有下面两个:

参数说明示例
SetMaxIdleConns根据数据库的别名,设置数据库的最大空闲连接orm.SetMaxIdleConns("default", 20)
SetMaxOpenConns根据数据库的别名,设置数据库的最大数据库连接orm.SetMaxOpenConns("default", 100)

(2)数据库调试模式

orm.Debug = true

打开调试模式,当执行orm查询的时候,会打印出对应的sql语句。

四、定义模型

4.1 创建结构体字段

  • 在models目录中创建文件userDemo.go,并创建对应结构体字段
package models

import (
	"github.com/astaxie/beego/orm"
	"time"
)

type UserDemo struct {
	Id int32 				`json:"id" pk:"auto;column(id)"`							//主键自增,列名设为id
	Name string				`json:"name" orm:"size(15);column(name)"`					//设置varchar长度为15,列名为name
	Password string			`json:"password" orm:"size(15);column(password)"`			//设置varchar长度为15,列名为password
	Age int32				`json:"age" orm:"column(age)"`								//int32默认生成int类型长度为11,列名为age
	Email string			`json:"email" orm:"size(30);column(email);null"`			//设置varchar长度为30,列名为email,可为空(默认不能为空)
	Tel string				`json:"tel" orm:"size(11);column(tel)"`						//设置varchar长度为11,列名为tel
	Address string			`json:"address" orm:"null"`									//设置字段可为空
	CreateTime time.Time	`json:"createTime" orm:"auto_now_add;type(datetime);null"`	//auto_now_add第一次保存时才设置时间,可为空
	UpdataTime time.Time	`json:"updataTime" orm:"auto_now;type(datetime);null"`		//auto_now每次model保存时都会对时间自动更新,可为空
}
  • json标签表示以JSON格式返回字段的字段名
  • orm标签在生成表时进行映射,会在后面章节做详细介绍

作为一个GO语言小白必须要吐槽一下这里的time.Time字段类型。在进行下面的功能操作时,不论是从前端接收时间类型参数,还是通过JSON方式返回数据,在进行时间格式化的时候都很麻烦。

个人觉得还是把时间类型定义成string类型比较好,需要进行时间操作在单独进行类型转换。

4.2 注册数据库表

  • userDemo.go文件的init()函数中注册数据库表

用orm.RegisterModel()函数,参数是结构体对象,如果有多个表,可以用 ,隔开,多new几个对象:

func init() {
	//向orm注册UserDemo模型
    
    //以下两种方式都可以
	//orm.RegisterModel(new(UserDemo))
	orm.RegisterModel(&UserDemo{})
    //使用RegisterModelWithPrefix为表名设置前缀:prefix_user_demo
	//orm.RegisterModelWithPrefix("prefix_", new(UserDemo))
}

4.3 生成表

  • 在main.go文件的init()函数中通过orm.RunSyncdb()函数生成表
orm.RunSyncdb("default",false,true)

参数说明

  • 参数一:数据库的别名和连接数据库的第一个参数相对应。
  • 参数二:是否强制更新,一般我们写的都是false,如果写true的话,每次项目编译一次数据库就会被清空一次,fasle的话会在数据库发生重大改变(比如添加字段)的时候更新数据库。
  • 参数三:生成表过程是否可见,如果设置成true,生成表的时候执行的SQL语句就会在终端看到。

注意:因为注册表方法在models包下,所以想要使注册方法RegisterModel生效需要在main.go文件下引入如下包。

import _ "go_project/models"
  • go_project为项目名称,根据自己项目名称进行相应修改

main.go文件完整代码

package main

import (
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/orm"
	_ "go_project/models"
	_ "go_project/routers"
	//导入mysql驱动,这是必须的
	_ "github.com/go-sql-driver/mysql"
)

func init() {
	//初始化数据库连接
	orm.RegisterDataBase("default", "mysql", "username:password@tcp(127.0.0.1:3306)/db_name?charset=utf8")
	//生成表
	orm.RunSyncdb("default",false,true)
    // 打开调试模式,开发的时候方便查看orm生成什么样子的sql语句
	orm.Debug = true
}

func main() {
	beego.Run()
}

执行main函数,控制台输出创建表语句:

create table `user_demo` 
    -- --------------------------------------------------
    --  Table Structure for `go_project/models.UserDemo`
    -- --------------------------------------------------
    CREATE TABLE IF NOT EXISTS `user_demo` (
        `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
        `name` varchar(15) NOT NULL DEFAULT '' ,
        `password` varchar(15) NOT NULL DEFAULT '' ,
        `age` integer NOT NULL DEFAULT 0 ,
        `email` varchar(30),
        `tel` varchar(11) NOT NULL DEFAULT '' ,
        `address` varchar(255),
        `create_time` datetime,
        `updata_time` datetime
    ) ENGINE=InnoDB;

表被成功创建

08.Beego框架ORM介绍01.jpg

五、基本操作

  • controllers文件夹下创建UserController.go文件,用来处理接收到的参数和返回数据
  • 在models文件夹下创建userDemo.go文件,用来操作数据库

5.1 添加数据

  • UserController.go文件增加如下方法
func (d *UserController) Create(){
	// 定义保存json数据的struct对象
	userDemo := &models.UserDemo{}

	// 获取body内容
	bodyData := d.Ctx.Input.RequestBody

	// 反序列json数据,结果保存至userDemo
	if err := json.Unmarshal(bodyData, userDemo); err == nil {
		// 解析参数失败
		log.Fatal(err)
		return
	}

	id, err := userDemo.Insert()
	if err != nil {
		log.Fatal(err)
		return
	}
	//如果添加成功,返回对应的ID
	d.Data["json"] = id
	d.ServeJSON()
}
  • models/userDemo.go文件下新增添加方法
func (u *UserDemo) Insert() (int64, error) {
	return orm.NewOrm().Insert(u)
}
  • 调用添加接口进行测试

08.Beego框架ORM介绍02.jpg

5.2 批量添加

  • UserController.go文件
/*
	批量添加
*/
func (d *UserController) CreateBatch(){
	users := []models.UserDemo{}
	bodyData := d.Ctx.Input.RequestBody

	err := json.Unmarshal(bodyData, &users)
	if err != nil {
		log.Fatal(err)
		return
	}
	num, err := models.InsertBatch(&users)
	if err != nil {
		log.Fatal(err)
		return
	}
	d.Data["json"] = num
	d.ServeJSON()
}
  • models/userDemo.go文件新增InsertBatch()函数(注意,这里是函数,不是方法)
func InsertBatch(users *[]UserDemo) (int64, error) {
	// 调用InsertMulti函数批量插入, 第一个参数指的是并行插入的行数,如果为1表示顺序插入
	return orm.NewOrm().InsertMulti(1,users)
}
  • 调用接口进行测试

08.Beego框架ORM介绍03.jpg

5.3 更新操作

更新操作有两种方式:

  • 更新所有字段,当不传值时默认为空值,对数据库原有值进行覆盖操作
  • 根据非空字段进行更新相应参数(比较常用)
  • UserController.go文件
func (d *UserController) Update(){
	userDemo := &models.UserDemo{}

	bodyData := d.Ctx.Input.RequestBody

	err := json.Unmarshal(bodyData, userDemo)
	if err != nil {
		log.Fatal(err)
		return
	}

	//更新所有值
	//_, err = userDemo.Update()
	//更新特定字段
	err = userDemo.UpdateChoose()

	if err != nil {
		log.Fatal(err)
		return
	}
	d.Data["json"] = "更新成功"
	d.ServeJSON()
}
  • models/userDemo.go文件
//该方式会更新所有字段,当不传值时会置空
func (u *UserDemo) Update() (int64, error) {
	return orm.NewOrm().Update(u)
}

//根据非空字段进行更新相应参数
func (u *UserDemo) UpdateChoose(fields ...string) error {
	param := make([]string, 0, 6)
	if len(fields) > 0{
		param = fields
	} else {
		if u.Name != "" {
			param = append(param, "name")
		}
		if u.Password != "" {
			param = append(param, "password")
		}
		if u.Age > 0 {
			param = append(param, "age")
		}
		if u.Email != "" {
			param = append(param, "email")
		}
		if u.Tel != "" {
			param = append(param, "tel")
		}
		if u.Address != "" {
			param = append(param, "address")
		}
	}

	o := orm.NewOrm()
	if _, err := o.Update(u, param...); err != nil {
		return err
	}
	return nil
}
  • 测试根据根据传入字段更新特定值

08.Beego框架ORM介绍04.jpg

5.4 查询操作

查询操作有如下两种方式:

  • 查询表中所有字段中的数据,并返回
  • 查询出规定某些字段的数据,对于未被查询的其他字段返回空

本次是针对单条数据的查询

  • UserController.go文件
func (d *UserController) Read(){
	id, _ := d.GetInt32("id")
	if id < 0 {
		return
	}
	user := models.UserDemo{Id: id}
	//方式一:查询出所有字段
	//err := user.Read()
	//方式二:根据传入的字段查询出对应的信息
	err := user.Read("id","name","age","email","tel","address")
	if err != nil {
		log.Fatal(err)
		return
	}
	d.Data["json"] = user
	d.ServeJSON()
}
  • models/userDemo.go文件
func (d *UserDemo) Read(fields ...string) error {
	if len(fields) == 0 {
		err := orm.NewOrm().Read(d)
		return err
	}

	//拼接数据格式
	columns := utils.SliceToFlat(fields)
	//拼接查询语句
	sql := fmt.Sprintf("select id,%s from %s where id=?", columns, TALE_NAME)

	return orm.NewOrm().Raw(sql, d.Id).QueryRow(d)
}
  • 拼接数据格式工具
package utils

func SliceToFlat(data []string) string {
	length := len(data)

	if length == 0 {
		return ""
	}

	result := ""
	for k, v := range data {
		result += v
		if k < (length - 1) {
			result += ","
		}
	}

	return result
}
  • 接口测试根据传入的字段查询出对应的信息

08.Beego框架ORM介绍05.jpg

5.5 删除数据

  • UserController.go文件
func (d *UserController) Delete(){
	id, _ := d.GetInt32("id")
	if id < 0 {
		return
	}
	//根据Id进行删除操作
	user := models.UserDemo{Id: id}
	//调用删除方法
	err := user.Delete()
	if err != nil {
		log.Fatal(err)
		return
	}
	d.Data["json"] = "删除成功"
	d.ServeJSON()
}
  • models/userDemo.go文件
func (u *UserDemo) Delete() error {
	if _, err := orm.NewOrm().Delete(u); err != nil {
		return err
	}
	return nil
}
  • 测试删除接口

08.Beego框架ORM介绍06.jpg

六、ORM标签

标签说明示例
auto当 Field 类型为 int, int32, int64, uint, uint32, uint64 时,可以设置字段为自增健orm:"auto"
pk设置为主键,适用于自定义其他类型为主键orm:"pk;atuo"(主键自增长)
-设置 - 即可忽略 struct 中的字段orm:"-"
null数据库表默认为 NOT NULL,设置 null 代表允许为空orm:"null"
index为单个字段增加索引orm:"index"
unique为单个字段增加 unique 键,让该属性的内容不能重复orm:"unique"
column为字段设置 db 字段的名称,就是设置列名orm:"column(user_name)"
size设置字段大小orm:"size(15)"
digits / decimals设置 float32, float64 类型的浮点精度orm:"digits(12);decimals(4)"
auto_now每次 model 保存时都会对时间自动更新orm:"auto_now_add;type(datetime)"
auto_now_add第一次保存时才设置时间orm:"auto_now;type(datetime)"
default为字段设置默认值,类型必须符合(目前仅用于级联删除时的默认值)
  • type标签

    • 设置为 date 时,time.Time 字段的对应 db 类型使用 date

      Created time.Time `orm:"auto_now_add;type(date)"`
      
    • 设置为 datetime 时,time.Time 字段的对应 db 类型使用 datetime

      Created time.Time `orm:"auto_now_add;type(datetime)"`
      

说明:多个tag之间用分号(;)分割,可参考:orm:"pk;auto"