介绍Beego中的model。

1. Model介绍

beego ORM 是一个强大的 Go 语言 ORM 框架,orm模块主要是处理MVC中的M(models)

对象-关系映射(Object-Relational Mapping,简称ORM)。当我们实现一个应用程序时(不使用O/R Mapping),我们可能会写特别多数据访问层的代码,从数据库保存、删除、读取对象信息,而这些代码都是重复的。而使用ORM则会大大减少重复性代码。

ORM的特性:

  • 支持范围广:支持 Go 的所有类型存储
  • 上手容易:采用简单的 CRUD 风格,允许直接使用 SQL 查询/映射
  • 兼容性好:跨数据库兼容查询
  • 自动 Join 关联表

2. 基本应用

2.1 数据库设置

首先需要安装orm模块和mysql驱动:

// 安装beego orm包
go get github.com/astaxie/beego/orm
// 安装mysql驱动
go get github.com/go-sql-driver/mysql

同时,也需要导入包:

import (
    // 导入orm包
    "github.com/astaxie/beego/orm"

    // 导入mysql驱动
    _ "github.com/go-sql-driver/mysql"
)

接下来是配置连接信息,配置过程和GoWeb实战1方法相似:

const(
    userName = "test"
    password = "asdfg12345"
    ip ="cdb-axt937vt.gz.tencentcdb.com"
    port="10059"
    dbName = "test"
)

func init(){
    connectInfo:=[]string{userName,":",password,"@tcp(",ip,":",port,")/", dbName, "?charset=utf8&parseTime=true&loc=Local"}
    path:=strings.Join(connectInfo,"")
    orm.RegisterDataBase("default","mysql",path)
    // 打开调试模式,开发的时候方便查看orm生成什么样子的sql语句
    orm.Debug=true
}

数据库注册函数的原型如下:

func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error
参数名 说明
aliasName 数据库的别名,用来在 ORM 中切换数据库使用
driverName 驱动名字
dataSource 数据库连接字符串
params 附加参数

常用的附加参数有:

参数名 默认值 说明
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”.

2.2 准备工作

首先我们创建一个表,用于测试:

CREATE TABLE `online_orders` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `shop_id` int(10) unsigned NOT NULL COMMENT '店铺id',
  `customer_id` int(10) unsigned NOT NULL COMMENT '用户id',
  `nickname` varchar(20) DEFAULT NULL COMMENT '用户昵称',
  `address` varchar(200) NOT NULL DEFAULT '' COMMENT '用户地址',
  `init_time` datetime NOT NULL COMMENT '创建订单的时间',
   PRIMARY KEY (`id`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

id为无符号整形,长度最长为10,不允许空NOT NULL,自增。varchar是可变字符串类型。datetime类型用于存储时间,存储方式是:

YYYY-MM-DD HH:MM:SS

建完后可以看到:


接下来需要构建结构体承载数据:

type Order struct{
    Id int
    ShopId int
    UserId int `orm:"colunm(customer_id)"`
    Nickname string
    Address string
    InitTime time.Time
}

ORM中结构体和表明的转化规则是驼峰转蛇形,除了开头以外,遇到大写需要_代替:

AuthUser -> auth_user
Auth_User -> auth__user
DB_AuthUser -> d_b__auth_user

所以,ShopId对应的是shop_id,又因为UserId在表中对应的是customer_id,不匹配,所以需要通过orm标签指定表字段名。

然后我们再为结构体添加一个函数,指定Order结构体默认绑定的表名:

func (o *Order) TableName() string {
    return "orders"
}

最后在main函数中,注册模型

orm.RegisterModel(new(Order))

除了前面提到的用column设置名字外,还有其他有用的参数设置:

  • 忽略字段-
  • 设置自增键auto
  • 设置为主键pk
  • 在数据库默认非空下,允许为空null

比如:

type User struct {
...
    AnyField string `orm:"-"`
...
}

2.3 CRUD操作

(1)插入数据

单条插入:

orm.RegisterModel(new(Order))
o := orm.NewOrm()
order:=Order{
    ShopId:1,
    UserId:1002,
    Nickname:"BOB",
    Address: "北京东路",
    InitTime:time.Now(),
}
id,err:=o.Insert(&order)
if err != nil {
    fmt.Println("插入失败,err:",err)
} else {
    // 插入成功会返回插入数据自增字段,生成的id
    fmt.Println("新插入数据的id为:", id)
}

批量插入:

o := orm.NewOrm()

orders := []Order{
    {ShopId:1, UserId:1001, Nickname:"大锤1", Address:"深圳南山区", InitTime: time.Now()},
    {ShopId:1, UserId:1002, Nickname:"大锤2", Address:"深圳南山区", InitTime: time.Now()},
    {ShopId:1, UserId:1003, Nickname:"大锤3", Address:"深圳南山区", InitTime: time.Now()},
}

// 调用InsertMulti函数批量插入, 第一个参数指的是要插入多少数据
nums, err := o.InsertMulti(3, orders)

(2)更新数据

更新所有字段:

orm.RegisterModel(new(Order))

o:=orm.NewOrm()
order:=Order{}
order.Id=1
order.Nickname="希特"
order.Address="四川资阳"
order.InitTime=time.Now()
num,err:=o.Update(&order)
//shop_id和custom_id将会变为0
if err != nil {
    fmt.Println("更新失败,err:",err)
} else {
    fmt.Println("更新数据影响的行数:", num)
}

更新指定字段:

num, err := o.Update(&order, "Nickname", "Address")

(3)查询数据

orm.RegisterModel(new(Order))

o:=orm.NewOrm()
order:=Order{}
// 先对主键id赋值, 查询数据的条件就是where id=2
order.Id=1

err:=o.Read(&order)
if err == orm.ErrNoRows {
    fmt.Println("查询不到")
} else if err == orm.ErrMissPK {
    fmt.Println("找不到主键")
} else {
    fmt.Println(order.Id, order.Nickname)
}

(4)删除数据

orm.RegisterModel(new(Order))

o := orm.NewOrm()
order := Order{}
// 先对主键id赋值, 删除数据的条件就是where id=2
order.Id = 1

if num, err := o.Delete(&order); err != nil {
    fmt.Println("删除失败")
} else {
    fmt.Println("删除数据影响的行数:", num)
}

3. 高级查询

首先重新定义一个用户表

CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `username` varchar(30) NOT NULL COMMENT '账号',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `city` varchar(50) DEFAULT NULL COMMENT '城市',
  `init_time` datetime NOT NULL COMMENT '创建的时间',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后再定义结构体:

type User struct{
    Id int
    Username string
    Password string
    City string
    InitTime time.Time
}
func (o *User) TableName() string{
    return "users"
}

3.1 过滤查询

过滤查询主要用的是QuerySeter,下面是一个简单的条件查询,查询来自武汉的且生成时间晚于2020年的用户:

qs:=o.QueryTable("users")
var users []User
num,err:=qs.Filter("city","WUHAN").Filter(("init_time__gt", "2020-01-01 22:00:00").All(&users,"id","username")
if err!=nil{
    panic(err)
}
fmt.Println("结果行数:", num)

可以看到Filter能连携使用,还能处理大于小于等条件。使用等于条件时:

qs.Filter("id", 1) // 相当于条件 id = 1

使用不等条件时,用双下划线 __ 作为分隔符,尾部连接操作符:

qs.Filter("id__gt", 1) // 相当于条件 id > 1
qs.Filter("id__gte", 1) // 相当于条件 id >= 1
qs.Filter("id__lt", 1) // 相当于条件 id < 1
qs.Filter("id__lte", 1) // 相当于条件 id <= 1
qs.Filter("id__in", 1,2,3,4,5) // 相当于In语句 id in (1,2,3,4,5)

此外还有其他操作符:

  • exact / iexact 等于,i表示大小写不敏感
  • contains / icontains 包含,i表示大小写不敏感
  • gt / gte 大于 / 大于等于
  • lt / lte 小于 / 小于等于
  • startswith / istartswith 以…起始
  • endswith / iendswith 以…结束
  • in 在其中
  • isnull

比如:

qs.Filter("Username__icontains", "大锤") // 相当于条件 name LIKE '%大锤%'
qs.Filter("profile__age__in", ids)
// WHERE profile.age IN (17, 18, 19, 20)

3.2 复杂查询

有的时候我们需要将条件组合起来查询,上面的例子多个Filter函数调用只能生成and连接的查询条件。beego orm为我们提供了Condition对象,用于生成查询条件。

//  创建一个Condition对象
cond := orm.NewCondition()

// 组织查询条件, 并返回一个新的Condition对象
cond1 := cond.And("Id__gt", 100).Or("City","shenzhen")
// 相当于条件 id > 100 or city = 'shenzhen'

var users []User

qs.SetCond(cond1). // 设置查询条件
  Limit(10). // 限制返回数据函数
  All(&users) // 查询多行数据

此外还可以连协Count,Group等功能:

num, _ := o.QueryTable("users").Filter("Id__gt", 1).Filter("Id__lt", 100).Count()

num, err := qs.Filter("Id__gt", 1).
        Filter("Id__lt", 100).
        GroupBy("City").   // 根据city字段分组
        OrderBy("-InitTime").   // order by字段名前面的减号 - , 代表倒序。
        Limit(10). // 限制返回行数
        All(&users)

4. 事务

// 创建orm对象
o := orm.NewOrm()

//  开始事务
o.Begin()

// 开始执行各种sql语句,更新数据库,这里可以使用beego orm支持任何一种方式操作数据库

// 例如,更新订单状态
_, err1 := o.QueryTable("orders").Filter("Id", 1001).Update(orm.Params{
    "Status": "SUCCESS",
})

// 给用户加积分
_, err2 := o.Raw("update users set points = points + ? where username=?", "tizi365", 100).Exec()

// 检测事务执行状态
if err1 != nil || err2 != nil {
    // 如果执行失败,回滚事务
    o.Rollback()
} else {
    // 任务执行成功,提交事务
    o.Commit()
}