持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
一、介绍
对象关系映射(Object Relational Mapping,简称ORM), 它的作用是映射数据库和对象之间的关系,方便我们在实现数据库操作的时候不用去写复杂的sql语句,把对数据库的操作上升到对于对象的操作。
beego ORM 是一个强大的 Go 语言 ORM 框架。它的灵感主要来自 Django ORM 和 SQLAlchemy。它支持go语言中所有的类型存储,允许直接使用原生的SQL语句,采用GRUD风格能够轻松上手,能自动Join关联表,并允许跨数据库兼容查询。
beego支持的数据库类型
- MySQL:github.com/go-sql-driv…
- PostgreSQL:github.com/lib/pq
- Sqlite3:github.com/mattn/go-sq…
使用不同的数据库,需要导入不同的数据库驱动:
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 | 最后面问号(?)之后可以包含多个键值对的附加参数,多个参数之间用&连接。 |
常用附加参数说明
参数名 | 默认值 | 说明 |
---|---|---|
charset | none | 设置字符集,相当于 SET NAMES 语句 |
loc | UTC | 设置时区,可以设置为Local,表示根据本地时区走 |
parseTime | false | 是否需要将 mysql的 DATE 和 DATETIME 类型值转换成GO的time.Time类型。 |
readTimeout | 0 | I/O 读超时时间, sql查询超时时间. 单位 ("ms", "s", "m", "h"), 例子: "30s", "0.5m" or "1m30s". |
timeout | 0 | 连接超时时间,单位("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;
表被成功创建
五、基本操作
- 在
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)
}
- 调用添加接口进行测试
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)
}
- 调用接口进行测试
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
}
- 测试根据根据传入字段更新特定值
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
}
- 接口测试根据传入的字段查询出对应的信息
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
}
- 测试删除接口
六、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 类型使用 dateCreated time.Time `orm:"auto_now_add;type(date)"`
-
设置为 datetime 时,
time.Time
字段的对应 db 类型使用 datetimeCreated time.Time `orm:"auto_now_add;type(datetime)"`
-
说明:多个tag之间用分号(;)分割,可参考:orm:"pk;auto"