GoFrame 數(shù)據(jù)庫(kù)ORM-結(jié)果處理

2022-04-02 15:51 更新

一、數(shù)據(jù)結(jié)構(gòu)

查詢結(jié)果的數(shù)據(jù)結(jié)構(gòu)如下:

type Value  = *gvar.Var              // 返回?cái)?shù)據(jù)表記錄值
type Record   map[string]Value       // 返回?cái)?shù)據(jù)表記錄鍵值對(duì)
type Result   []Record               // 返回?cái)?shù)據(jù)表記錄列表
  1. ?Value/Record/Result?用于?ORM?操作的結(jié)果數(shù)據(jù)類型。
  2. ?Result?表示數(shù)據(jù)表記錄列表,?Record?表示一條數(shù)據(jù)表記錄,?Value?表示記錄中的一條鍵值數(shù)據(jù)。
  3. ?Value?是?*gvar.Var?類型的別名類型,方便于后續(xù)的數(shù)據(jù)類型轉(zhuǎn)換。

二、Record數(shù)據(jù)記錄

接口文檔: https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb

?gdb?為數(shù)據(jù)表記錄操作提供了極高的靈活性和簡(jiǎn)便性,除了支持以?map?的形式訪問(wèn)/操作數(shù)據(jù)表記錄以外,也支持將數(shù)據(jù)表記錄轉(zhuǎn)換為?struct?進(jìn)行處理。我們以下使用一個(gè)簡(jiǎn)單的示例來(lái)演示該特性。

首先,我們的用戶表結(jié)構(gòu)是這樣的(簡(jiǎn)單設(shè)計(jì)的示例表):

CREATE TABLE `user` (
  `uid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '昵稱',
  `site` varchar(255) NOT NULL DEFAULT '' COMMENT '主頁(yè)',
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

其次,我們的表數(shù)據(jù)如下:

uid  name   site
1    john   https://goframe.org

最后,我們的示例程序如下:

package main

import (
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
)

type User struct {
	Uid  int
	Name string
}

func main() {
	var (
		user *User
		ctx  = gctx.New()
	)
	err := g.DB().Model("user").Where("uid", 1).Scan(&user)
	if err != nil {
		g.Log().Header(false).Fatal(ctx, err)
	}
	if user != nil {
		g.Log().Header(false).Print(ctx, user)
	}
}

執(zhí)行后,輸出結(jié)果為:

{"Uid":1,"Name":"john"}

這里,我們自定義了一個(gè)?struct?,里面只包含了?Uid?和?Name?屬性,可以看到它的屬性并不和數(shù)據(jù)表的字段一致,這也是?ORM?靈活的特性之一:支持指定屬性獲取。

通過(guò)?gdb.Model.Scan?方法可以將查詢到的數(shù)據(jù)記錄轉(zhuǎn)換為?struct?對(duì)象或者?struct?對(duì)象數(shù)組。由于這里傳遞的參數(shù)為?&user?即?**User?類型,那么將會(huì)轉(zhuǎn)換為一個(gè)?struct?對(duì)象,如果傳遞為?[]*User?類型的參數(shù),將會(huì)轉(zhuǎn)換為數(shù)組結(jié)果。

屬性字段映射規(guī)則:

需要注意的是,?map?中的鍵名為?uid?,?name?,?site?,而?struct?中的屬性為?Uid?,?Name?,那么他們之間是如何執(zhí)行映射的呢?主要是以下幾點(diǎn)簡(jiǎn)單的規(guī)則:

  1. ?struct?中需要匹配的屬性必須為公開(kāi)屬性(首字母大寫(xiě));
  2. 記錄結(jié)果中鍵名會(huì)自動(dòng)按照  不區(qū)分大小寫(xiě)  且  忽略?-?/?_?/空格符號(hào)的形式與?struct?屬性進(jìn)行匹配;
  3. 如果匹配成功,那么將鍵值賦值給屬性,如果無(wú)法匹配,那么忽略該鍵值;

以下是幾個(gè)匹配的示例:

記錄鍵名    struct屬性     是否匹配
name       Name           match
Email      Email          match
nickname   NickName       match
NICKNAME   NickName       match
Nick-Name  NickName       match
nick_name  NickName       match
nick_name  Nick_Name      match
NickName   Nick_Name      match
Nick-Name  Nick_Name      match

由于數(shù)據(jù)庫(kù)結(jié)果集轉(zhuǎn)?struct?的底層是依靠?gconv.Struct?方法實(shí)現(xiàn)的。

三、Result數(shù)據(jù)集合

?Result/Record?數(shù)據(jù)類型根據(jù)數(shù)據(jù)結(jié)果集操作的需要,往往需要根據(jù)記錄中特定的字段作為鍵名進(jìn)行數(shù)據(jù)檢索,因此它包含多個(gè)用于轉(zhuǎn)換?Map/List?的方法,同時(shí)也包含了常用數(shù)據(jù)結(jié)構(gòu)?JSON/XML?的轉(zhuǎn)換方法。

接口文檔: https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb

由于方法比較簡(jiǎn)單,這里便不再舉例說(shuō)明。需要注意的是兩個(gè)方法?Record.Map?及?Result.List?,這兩個(gè)方法也是使用比較頻繁的方法,用以將?ORM?查詢結(jié)果信息轉(zhuǎn)換為可做展示的數(shù)據(jù)類型。由于結(jié)果集字段值底層為?[]byt?e類型,雖然使用了新的?Value?類型做了封裝,并且也提供了數(shù)十種常見(jiàn)的類型轉(zhuǎn)換方法,但是大多數(shù)時(shí)候需要直接將結(jié)果?Result?或者?Record?直接作為?json?或者?xml?數(shù)據(jù)結(jié)構(gòu)返回,就需要做轉(zhuǎn)換才行。

使用示例:

package main

import (
	"database/sql"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
)

type User struct {
	Uid  int
	Name string
	Site string
}

func main() {
	var (
		user []*User
		ctx  = gctx.New()
	)
	err := g.DB().Model("user").Where("uid", 1).Scan(&user)
	if err != nil && err != sql.ErrNoRows {
		g.Log().Header(false).Fatal(ctx, err)
	}
	if user != nil {
		g.Log().Header(false).Print(ctx, user)
	}
}

執(zhí)行后,輸出結(jié)果為:

[{"Uid":1,"Name":"john","Site":"https://goframe.org"}]

四、結(jié)果為空判斷

使用?gf ORM?對(duì)返回結(jié)果為空判斷非常簡(jiǎn)便,大部分場(chǎng)景下直接判斷返回的數(shù)據(jù)是否為?nil?或者長(zhǎng)度為?0?,或者使用?IsEmpty/IsNil?方法。

數(shù)據(jù)集合

r, err := g.Model("order").Where("status", 1).All()
if err != nil {
	return err
}
if len(r) == 0 {
    // 結(jié)果為空
}

也可以使用?IsEmpty?方法:

r, err := g.Model("order").Where("status", 1).All()
if err != nil {
	return err
}
if r.IsEmpty() {
    // 結(jié)果為空
}

數(shù)據(jù)記錄

r, err := g.Model("order").Where("status", 1).One()
if err != nil {
    return err
}
if len(r) == 0 {
    // 結(jié)果為空
}

也可以使用?IsEmpty?方法:

r, err := g.Table("order").Where("status", 1).One()
if err != nil {
    return err
}
if r.IsEmpty() {
    // 結(jié)果為空
}

數(shù)據(jù)字段值

返回的是一個(gè)"泛型"變量,這個(gè)只能使用?IsEmpty?來(lái)判斷是否為空了。

r, err := g.Model("order").Where("status", 1).Value()
if err != nil {
	return err
}
if r.IsEmpty() {
    // 結(jié)果為空
}

字段值數(shù)組

查詢返回字段值數(shù)組本身類型為?[]gdb.Value?類型,因此直接判斷長(zhǎng)度是否為?0?即可。

// Array/FindArray
r, err := g.Table("order").Fields("id").Where("status", 1).Array()
if err != nil {
    return err
}
if len(r) == 0 {
    // 結(jié)果為空
}

struct對(duì)象

關(guān)于?Struct?轉(zhuǎn)換對(duì)象來(lái)說(shuō)會(huì)有一點(diǎn)不一樣,我們直接看例子吧。

當(dāng)傳遞的對(duì)象本身就是一個(gè)空指針時(shí),如果查詢到數(shù)據(jù),那么會(huì)在內(nèi)部自動(dòng)創(chuàng)建這個(gè)對(duì)象;如果沒(méi)有查詢到數(shù)據(jù),那么這個(gè)空指針仍舊是一個(gè)空指針,內(nèi)部并不會(huì)做任何處理。

var user *User
err := g.Model("order").Where("status", 1).Scan(&user)
if err != nil {
    return err
}
if user == nil {
    // 結(jié)果為空
}

當(dāng)傳遞的對(duì)象本身已經(jīng)是一個(gè)初始化的對(duì)象,如果查詢到數(shù)據(jù),那么會(huì)在內(nèi)部將數(shù)據(jù)賦值給這個(gè)對(duì)象;如果沒(méi)有查詢到數(shù)據(jù),那么此時(shí)就沒(méi)辦法將對(duì)象做?nil?判斷空結(jié)果。因此?ORM?會(huì)返回一個(gè)?sql.ErrNoRows?錯(cuò)誤,提醒開(kāi)發(fā)者沒(méi)有查詢到任何數(shù)據(jù)并且對(duì)象沒(méi)有做任何賦值,對(duì)象的所有屬性還是給定的初始化數(shù)值,以便開(kāi)發(fā)者可以做進(jìn)一步的空結(jié)果判斷。

var user = new(User)
err := g.Model("order").Where("status", 1).Scan(&user)
if err != nil && err != sql.ErrNoRows {
    return err
}
if err == sql.ErrNoRows {
    // 結(jié)果為空
}

所以我們推薦開(kāi)發(fā)者不要傳遞一個(gè)初始化過(guò)后的對(duì)象給?ORM?,而是直接傳遞一個(gè)對(duì)象的指針的指針類型(?**struct?類型),?ORM?內(nèi)部會(huì)根據(jù)查詢結(jié)果智能地做自動(dòng)初始化。

struct數(shù)組

當(dāng)傳遞的對(duì)象數(shù)組本身是一個(gè)空數(shù)組(長(zhǎng)度為0),如果查詢到數(shù)據(jù),那么會(huì)在內(nèi)部自動(dòng)賦值給數(shù)組;如果沒(méi)有查詢到數(shù)據(jù),那么這個(gè)空數(shù)組仍舊是一個(gè)空數(shù)組,內(nèi)部并不會(huì)做任何處理。

var users []*User
err := g.Model("order").Where("status", 1).Scan(&users)
if err != nil {
    return err
}
if len(users) == 0 {
    // 結(jié)果為空
}

當(dāng)傳遞的對(duì)象數(shù)組本身不是空數(shù)組,如果查詢到數(shù)據(jù),那么會(huì)在內(nèi)部自動(dòng)從索引0位置覆蓋到數(shù)組上;如果沒(méi)有查詢到數(shù)據(jù),那么此時(shí)就沒(méi)辦法將數(shù)組做長(zhǎng)度為0判斷空結(jié)果。因此?ORM?會(huì)返回一個(gè)?sql.ErrNoRows?錯(cuò)誤,提醒開(kāi)發(fā)者沒(méi)有查詢到任何數(shù)據(jù)并且數(shù)組沒(méi)有做任何賦值,以便開(kāi)發(fā)者可以做進(jìn)一步的空結(jié)果判斷。

var users = make([]*User, 100)
err := g.Model("order").Where("status", 1).Scan(&users)
if err != nil {
    return err
} 
if err == sql.ErrNoRows {     
    // 結(jié)果為空
}


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)