天天看點

mongo go 查詢指定字段_Go 每日一庫之 xorm

mongo go 查詢指定字段_Go 每日一庫之 xorm

簡介

Go 标準庫提供的資料庫接口

database/sql

比較底層,使用它來操作資料庫非常繁瑣,而且容易出錯。因而社群開源了不少第三方庫,如上一篇文章中的

sqlc

工具,還有各式各樣的 ORM (Object Relational Mapping,對象關系映射庫),如

gorm

xorm

。本文介紹

xorm

xorm

是一個簡單但強大的 Go 語言 ORM 庫,使用它可以大大簡化我們的資料庫操作。

快速使用

先安裝:

$ go get xorm.io/xorm
           

由于需要操作具體的資料庫(本文中我們使用 MySQL),需要安裝對應的驅動:

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

使用:

package main

import (
  "log"
  "time"

  _ "github.com/go-sql-driver/mysql"
  "xorm.io/xorm"
)

type User struct {
  Id      int64
  Name    string
  Salt    string
  Age     int
  Passwd  string    `xorm:"varchar(200)"`
  Created time.Time `xorm:"created"`
  Updated time.Time `xorm:"updated"`
}

func main() {
  engine, err := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  if err != nil {
    log.Fatal(err)
  }

  err = engine.Sync2(new(User))
  if err != nil {
    log.Fatal(err)
  }
}
           

使用

xorm

來操作資料庫,首先需要使用

xorm.NewEngine()

建立一個引擎。該方法的參數與

sql.Open()

參數相同。

上面代碼中,我們示範了

xorm

的一個非常實用的功能,将資料庫中的表與對應 Go 代碼中的結構體做同步。初始狀态下,資料庫

test

中沒有表

user

,調用

Sync2()

方法會根據

User

的結構自動建立一個

user

表。執行後,通過

describe user

檢視表結構:

mongo go 查詢指定字段_Go 每日一庫之 xorm

如果表

user

已經存在,

Sync()

方法會對比

User

結構與表結構的不同,對表做相應的修改。我們給

User

結構添加一個

Level

字段:

type User struct {
  Id      int64
  Name    string
  Salt    string
  Age     int
  Level   int
  Passwd  string    `xorm:"varchar(200)"`
  Created time.Time `xorm:"created"`
  Updated time.Time `xorm:"updated"`
}
           

再次執行這個程式後,用

describe user

指令檢視表結構:

mongo go 查詢指定字段_Go 每日一庫之 xorm

發現表中多了一個

level

字段。

此修改隻限于添加字段。

删除表中已有的字段會帶來比較大的風險。如果我們

User

結構的

Salt

字段删除,然後執行程式。出現下面錯誤:

[xorm] [warn]  2020/05/07 22:44:38.528784 Table user has column salt but struct has not related field
           

資料庫操作

查詢&統計

xorm

提供了幾個查詢和統計方法,

Get/Exist/Find/Iterate/Count/Rows/Sum

。下面逐一介紹。

為了代碼示範友善,我在

user

表中插入了一些資料:

mongo go 查詢指定字段_Go 每日一庫之 xorm

後面的代碼為了簡單起見,忽略了錯誤處理,實際使用中不要漏掉!

Get

Get()

方法用于查詢單條資料,并使用傳回的字段為傳入的對象指派:

type User struct {
  Id      int64
  Name    string
  Salt    string
  Age     int
  Passwd  string    `xorm:"varchar(200)"`
  Created time.Time `xorm:"created"`
  Updated time.Time `xorm:"updated"`
}

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  user1 := &User{}
  has, _ := engine.ID(1).Get(user1)
  if has {
    fmt.Printf("user1:%vn", user1)
  }

  user2 := &User{}
  has, _ = engine.Where("name=?", "dj").Get(user2)
  if has {
    fmt.Printf("user2:%vn", user2)
  }

  user3 := &User{Id: 5}
  has, _ = engine.Get(user3)
  if has {
    fmt.Printf("user3:%vn", user3)
  }

  user4 := &User{Name: "pipi"}
  has, _ = engine.Get(user4)
  if has {
    fmt.Printf("user4:%vn", user4)
  }
}
           

上面示範了 3 種使用

Get()

的方式:

  • 使用主鍵:

    engine.ID(1)

    查詢主鍵(即

    id

    )為 1 的使用者;
  • 使用條件語句:

    engine.Where("name=?", "dj")

    查詢

    name = "dj"

    的使用者;
  • 使用對象中的非空字段:

    user3

    設定了

    Id

    字段為 5,

    engine.Get(user3)

    查詢

    id = 5

    的使用者;

    user4

    設定了字段

    Name

    "pipi"

    engine.Get(user4)

    查詢

    name = "pipi"

    的使用者。

運作程式:

user1:&{1 dj salt 18 12345 2020-05-08 21:12:11 +0800 CST 2020-05-08 21:12:11 +0800 CST}
user2:&{1 dj salt 18 12345 2020-05-08 21:12:11 +0800 CST 2020-05-08 21:12:11 +0800 CST}
user3:&{5 mxg salt 54 12345 2020-05-08 21:13:31 +0800 CST 2020-05-08 21:13:31 +0800 CST}
user4:&{3 pipi salt 2 12345 2020-05-08 21:13:31 +0800 CST 2020-05-08 21:13:31 +0800 CST}
           

查詢條件的使用不區分調用順序,但是必須在

Get()

方法之前調用。實際上後面介紹的查詢&統計方法也是如此,可以在調用實際的方法前添加一些過濾條件。除此之外

xorm

支援隻傳回指定的列(

xorm.Cols()

)或忽略特定的列(

xorm.Omit()

):

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  user1 := &User{}
  engine.ID(1).Cols("id", "name", "age").Get(user1)
  fmt.Printf("user1:%vn", user1)

  user2 := &User{Name: "pipi"}
  engine.Omit("created", "updated").Get(user2)
  fmt.Printf("user2:%vn", user2)
}
           

上面第一個查詢使用

Cols()

方法指定隻傳回

id

name

age

這 3 列,第二個查詢使用

Omit()

方法忽略列

created

updated

另外,為了便于排查可能出現的問題,

xorm

提供了

ShowSQL()

方法設定将執行的 SQL 同時在控制台中輸出:

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")
  engine.ShowSQL(true)

  user := &User{}
  engine.ID(1).Omit("created", "updated").Get(user)
  fmt.Printf("user:%vn", user)
}
           

運作程式:

[xorm] [info]  2020/05/08 21:38:29.349976 [SQL] SELECT `id`, `name`, `salt`, `age`, `passwd` FROM `user` WHERE `id`=? LIMIT 1 [1] - 4.0033ms
user:&{1 dj salt 18 12345 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC}
           

由輸出可以看出,執行的 SQL 語句為:

SELECT `id`, `name`, `salt`, `age`, `passwd` FROM `user` WHERE `id`=? LIMIT 1
           

該語句耗時 4.003 ms。在開發中這個方法非常好用!

有時候,調試資訊都輸出到控制台并不利于我們查詢,

xorm

可以設定日志選項,将日志輸出到檔案中:

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")
  f, err := os.Create("sql.log")
  if err != nil {
    panic(err)
  }

  engine.SetLogger(log.NewSimpleLogger(f))
  engine.Logger().SetLevel(log.LOG_DEBUG)
  engine.ShowSQL(true)

  user := &User{}
  engine.ID(1).Omit("created", "updated").Get(user)
  fmt.Printf("user:%vn", user)
}
           

這樣

xorm

就會将調試日志輸出到

sql.log

檔案中。注意

log.NewSimpleLogger(f)

xorm

的子包

xorm.io/xorm/log

提供的簡單日志封裝,而非标準庫

log

Exist

Exist()

方法查詢符合條件的記錄是否存在,它的傳回與

Get()

方法一緻,都是

(bool, error)

。不同之處在于

Get()

會将查詢得到的字段指派給傳入的對象。相比之下

Exist()

方法效率要高一些。如果不需要擷取資料,隻要判斷是否存在建議使用

Exist()

方法。

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  user1 := &User{}
  has, _ := engine.ID(1).Exist(user1)
  if has {
    fmt.Println("user with id=1 exist")
  } else {
    fmt.Println("user with id=1 not exist")
  }

  user2 := &User{}
  has, _ = engine.Where("name=?", "dj2").Get(user2)
  if has {
    fmt.Println("user with name=dj2 exist")
  } else {
    fmt.Println("user with name=dj2 not exist")
  }
}
           

Find

Get()

方法隻能傳回單條記錄,其生成的 SQL 語句總是有

LIMIT 1

Find()

方法傳回所有符合條件的記錄。

Find()

需要傳入對象切片的指針或 map 的指針:

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  slcUsers:= make([]User, 1)
  engine.Where("age > ? and age < ?", 12, 30).Find(&slcUsers)
  fmt.Println("users whose age between [12,30]:", slcUsers)

  mapUsers := make(map[int64]User)
  engine.Where("length(name) = ?", 3).Find(&mapUsers)
  fmt.Println("users whose has name of length 3:", mapUsers)
}
           

map

的鍵為主鍵,是以如果表為複合主鍵就不能使用這種方式了。

Iterate

Find()

一樣,

Iterate()

也是找到滿足條件的所有記錄,隻不過傳入了一個回調去處理每條記錄:

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  engine.Where("age > ? and age < ?", 12, 30).Iterate(&User{}, func(i int, bean interface{}) error {
    fmt.Printf("user%d:%vn", i, bean.(*User))
    return nil
  })
}
           

如果回調傳回一個非

nil

的錯誤,後面的記錄就不會再處理了。

Count

Count()

方法統計滿足條件的記錄數量:

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  num, _ := engine.Where("age >= ?", 50).Count(&User{})
  fmt.Printf("there are %d users whose age >= 50", num)
}
           

Rows

Rows()

方法與

Iterate()

類似,不過傳回一個

Rows

對象由我們自己疊代,更加靈活:

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  rows, _ := engine.Where("age > ? and age < ?", 12, 30).Rows(&User{})
  defer rows.Close()

  u := &User{}
  for rows.Next() {
    rows.Scan(u)

    fmt.Println(u)
  }
}
           

Rows()

的使用與

database/sql

有些類似,但是

rows.Scan()

方法可以傳入一個對象,比

database/sql

更友善。

Sum

xorm

提供了兩組求和的方法:

  • Sum/SumInt

    :求某個字段的和,

    Sum

    傳回

    float64

    SumInt

    傳回

    int64

  • Sums/SumsInt

    :分别求某些字段的和,

    Sums

    傳回

    []float64

    SumsInt

    傳回

    []int64

例如:

type Sum struct {
  Id    int64
  Money int32
  Rate  float32
}

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")
  engine.Sync2(&Sum{})

  var slice []*Sum
  for i := 0; i < 100; i++ {
    slice = append(slice, &Sum{
      Money: rand.Int31n(10000),
      Rate:  rand.Float32(),
    })
  }
  engine.Insert(&slice)

  totalMoney, _ := engine.SumInt(&Sum{}, "money")
  fmt.Println("total money:", totalMoney)

  totalRate, _ := engine.Sum(&Sum{}, "rate")
  fmt.Println("total rate:", totalRate)

  totals, _ := engine.Sums(&Sum{}, "money", "rate")
  fmt.Printf("total money:%f & total rate:%f", totals[0], totals[1])
}
           

插入

使用

engine.Insert()

方法,可以插入單條資料,也可以批量插入多條資料:

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")
  user := &User{Name: "lzy", Age: 50}

  affected, _ := engine.Insert(user)
  fmt.Printf("%d records inserted, user.id:%dn", affected, user.Id)

  users := make([]*User, 2)
  users[0] = &User{Name: "xhq", Age: 41}
  users[1] = &User{Name: "lhy", Age: 12}

  affected, _ = engine.Insert(&users)
  fmt.Printf("%d records inserted, id1:%d, id2:%d", affected, users[0].Id, users[1].Id)
}
           

插入單條記錄傳入一個對象指針,批量插入傳入一個切片。需要注意的是,批量插入時,每個對象的

Id

字段不會被自動指派,是以上面最後一行輸出

id1

id2

均為 0。另外,一次

Insert()

調用可以傳入多個參數,可以對應不同的表。

更新

更新通過

engine.Update()

實作,可以傳入結構指針或

map[string]interface{}

。對于傳入結構體指針的情況,

xorm

隻會更新非空的字段

。如果一定要更新空字段,需要使用

Cols()

方法顯示指定更新的列。使用

Cols()

方法指定列後,即使字段為空也會更新:

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")
  engine.ID(1).Update(&User{Name: "ldj"})
  engine.ID(1).Cols("name", "age").Update(&User{Name: "dj"})

  engine.Table(&User{}).ID(1).Update(map[string]interface{}{"age": 18})
}
           

由于使用

map[string]interface{}

類型的參數,

xorm

無法推斷表名,必須使用

Table()

方法指定。第一個

Update()

方法隻會更新

name

字段,其他空字段不更新。第二個

Update()

方法會更新

name

age

兩個字段,

age

被更新為 0。

删除

直接調用

engine.Delete()

删除符合條件的記錄,傳回删除的條目數量:

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  affected, _ := engine.Where("name = ?", "lzy").Delete(&User{})
  fmt.Printf("%d records deleted", affected)
}
           

建立時間、更新時間、軟删除

如果我們為

time.Time/int/int64

這些類型的字段設定

xorm:"created"

标簽,

插入資料時

,該字段會自動更新為目前時間;

如果我們為

tiem.Time/int/int64

這些類型的字段設定

xorm:"updated"

标簽,

插入和更新資料時

,該字段會自動更新為目前時間;

如果我們為

time.Time

類型的字段設定了

xorm:"deleted"

标簽,

删除資料時

,隻是設定删除時間,并不真正删除記錄。

type Player struct {
  Id int64
  Name string
  Age int
  CreatedAt time.Time `xorm:"created"`
  UpdatedAt time.Time `xorm:"updated"`
  DeletedAt time.Time `xorm:"deleted"`
}

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  engine.Sync2(&Player{})
  engine.Insert(&Player{Name:"dj", Age:18})

  p := &Player{}
  engine.Where("name = ?", "dj").Get(p)
  fmt.Println("after insert:", p)
  time.Sleep(5 * time.Second)

  engine.Table(&Player{}).ID(p.Id).Update(map[string]interface{}{"age":30})

  engine.Where("name = ?", "dj").Get(p)
  fmt.Println("after update:", p)
  time.Sleep(5 * time.Second)

  engine.ID(p.Id).Delete(&Player{})

  engine.Where("name = ?", "dj").Unscoped().Get(p)
  fmt.Println("after delete:", p)
}
           

輸出:

after insert: &{1 dj 18 2020-05-08 23:09:19 +0800 CST 2020-05-08 23:09:19 +0800 CST 0001-01-01 00:00:00 +0000 UTC}
after update: &{1 dj 30 2020-05-08 23:09:19 +0800 CST 2020-05-08 23:09:24 +0800 CST 0001-01-01 00:00:00 +0000 UTC}
after delete: &{1 dj 30 2020-05-08 23:09:19 +0800 CST 2020-05-08 23:09:24 +0800 CST 2020-05-08 23:09:29 +0800 CST}
           

建立時間一旦建立成功就不會再改變了,更新時間每次更新都會變化。已删除的記錄必須使用

Unscoped()

方法查詢,如果要真正 删除某條記錄,也可以使用

Unscoped()

執行原始的 SQL

除了上面提供的方法外,

xorm

還可以執行原始的 SQL 語句:

func main() {
  engine, _ := xorm.NewEngine("mysql", "root:[email protected]/test?charset=utf8")

  querySql := "select * from user limit 1"
  reuslts, _ := engine.Query(querySql)
  for _, record := range reuslts {
    for key, val := range record {
      fmt.Println(key, string(val))
    }
  }

  updateSql := "update `user` set name=? where id=?"
  res, _ := engine.Exec(updateSql, "ldj", 1)
  fmt.Println(res.RowsAffected())
}
           

Query()

方法傳回

[]map[string][]byte

,切片中的每個元素都代表一條記錄,

map

的鍵對應列名,

[]byte

為值。還有

QueryInterface()

方法傳回

[]map[string]interface{}

QueryString()

方法傳回

[]map[string]interface{}

運作程式:

salt salt
age 18
passwd 12345
created 2020-05-08 21:12:11
updated 2020-05-08 22:44:58
id 1
name ldj
1 <nil>
           

總結

本文對

xorm

做了一個簡單的介紹,

xorm

的特性遠不止于此。

xorm

可以定義結構體字段與表列名映射規則、建立索引、執行事務、導入導出 SQL 腳本等。感興趣可自行探索。好在

xorm

有比較詳盡的中文文檔。

大家如果發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上送出 issue

參考

  1. xorm GitHub:https://github.com/go-xorm/xorm
  2. xorm 手冊:http://gobook.io/read/gitea.com/xorm/manual-zh-CN/
  3. Go 每日一庫 GitHub:https://github.com/darjun/go-daily-lib

我的部落格:https://darjun.github.io

歡迎關注我的微信公衆号【GoUpUp】,共同學習,一起進步~

mongo go 查詢指定字段_Go 每日一庫之 xorm