Go With MongoDB 2
- 插入内嵌的document
和关系型数据库最大的不同,document数据库(非关系型数据库)不支持模型化的对象的关系访问.在关系型数据库中,创建一种联系,可以使用父表(parent table)和子表,这样父表的每一个记录都会和子表的多个记录相互关联.
为了这种数据上的对应,需要在子表定义额外的key值指向父表的一个primary key.
MongoDB有着更加灵活的结构.所以数据模型可以用不同的方式定义获取同样的对象.你可以根据应用的上下文选择正确的模式定义数据模型.为了在链接的数据建立关系,可以在主要的document内嵌document,或者带两个document之间引用.这两者的应用情形根据实际情况去应用.
下面的例子展示了一个数据模型使用内嵌的document去描述链接的数据的关系.Category和Task数据的联系
Category有多个Task实体.
// mongodb
package main
import (
"log"
"time"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Task struct {
Description string
Due time.Time
}
type Category struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Name string
Description string
Tasks []Task
}
func main() {
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
//获取一个集合
c := session.DB("taskdb").C("categories")
//
doc := Category{
bson.NewObjectId(),
"Open-Source",
"Task for open-source prijects",
[]Task{
Task{"Create project in mgo", time.Date(2016, time.May, 10, 0, 0, 0, 0, time.UTC)},
Task{"Create REST API", time.Date(2016, time.May, 20, 0, 0, 0, 0, time.UTC)},
},
}
err = c.Insert(&doc)
if err != nil {
log.Fatal(err)
}
}
创建了一个Category 结构体,包含了一个Task元素类型的数组.文档的嵌入可以获取父文档和关联子文档,只需要进行一次查询.
- 读取文档
Collection的Find方法允许查询MongoDB的collections.当调用这个方法的时候,可以提供一个文档进行过滤collection数据. Find方法使用一个document进行查询.提供一个document查询collection,需要提供一个可以序列化为BSON数据的对象,比如map,struct值.
- 检索所有记录
当Find方法的参数为nil时,就会检索collection的所有document.下面的是一个例子:检索上面的例子保存的所有document
iter := c.Find(nil).Iter()
result := Category{}
for iter.Next(&result) {
fmt.Printf("Category:%s,decription:%Ss\n", result.Name, result.Description)
tasks := result.Tasks
for _, v := range tasks {
fmt.Printf("Task:%s Due:%s\n", v.Description, v.Due)
}
}
if err = iter.Close(); err != nil {
log.Fatal(err)
}
Iter方法用来枚举documents.Iter执行查询,得到所有的可以枚举的值.当在document中内嵌了父子关系,可以用一个查询语句访问.
- 排序记录
Documents可以使用Sort方法进行排序.Sort方法会根据提供的字段来进行排序.
//sort
iter := c.Find(nil).Sort("name").Iter()
for iter.Next(&result) {
fmt.Printf("Category:%s,decription:%Ss\n", result.Name, result.Description)
tasks := result.Tasks
for _, v := range tasks {
fmt.Printf("Task:%s Due:%s\n", v.Description, v.Due)
}
}
if err = iter.Close(); err != nil {
log.Fatal(err)
}
如果要根据字段进行反向排序,只要在字段名前加上"-"
iter := c.Find(nil).Sort("-name").Iter()
- 检索单个记录
result := Category{}
err := c.Find(bson.M{"name":"Open-Source"}).One(result)
if err != nil{
log.Fatal(err)
}
fmt.Printf("Category:%s,Description:%s\n",result.name,result.Description)
task := result.Tasks
for _,v := range tasks{
fmt.Printf("Task:%s Due:%v\n",v.Description,v.Due)
}
bson.M (M-->map)类型用来查询数据.在这里,使用name字段查询collection. One方法执行查询并且进行解析到result中.还有一个FindId方法更加方便单个数据的查询.直接使用id查询collection中对应的document
query := c.Find(bson.M{"_id":id})
result := Category{}
err = c.FindId(obj_id).One(&result)
- 更新查询操作
Update方法可以对document的数据进行更新.
func (c *Collection) Update(selectorinterface{},updateinterface{}) error
Update方法从collection中查找document,使用提供的选择器进行查找,再用提供的document进行进行更新.部分更新可以使用"%set"关键字进行更新document.
//update a document
err := c.Update(bson.M{"_id": id},
bson.M{"$set":bson.M{
"description":"Create open-source projects",
"tasks":[]Task{
Task{"Evaluate Negroni Project", time.Date(2015, time.August, 15, 0, 0, 0,
0, time.UTC)},
Task{"Explore mgo Project", time.Date(2015, time.August, 10, 0, 0, 0, 0,
time.UTC)},
Task{"Explore Gorilla Toolkit", time.Date(2015, time.August, 10, 0, 0, 0, 0,
time.UTC)},
},
}}
)
部分更新:description和tasks.Update方法会根据提供的id进行查找,之后修改对应的字段,写入提供的document的对应的值.
- 删除一个document
Remove方法可以从collection中删除一个document.
RemoveAll方法则是删除全部的document,如果参数为nil,全部删除
c.RemoveAll(nil)
func (c *Collection) Remove(selector interface{}) error //err := c.Remove(bson.M{"_id": id})
func (c *Collection) RemoveAll(selector interface{}) (info *ChangeInfo, err error)
MongoDB的下标索引
MongoDB数据库和关系型数据库有着高效的读取操作,为了更好的操作MongoDB数据库,还可以给collection通过添加索引提供效率.collection的索引可以在进行高效查询操作.MongoDB可以在collection水平上定义索引,也可以在collection张document中或者任意字段定义索引.
所有的collection都默认有一个_id字段的所以 .如果不定义这个字段,MongoDB进程(mongod)会自动创建一个_id字段,值类型是ObjectId. _id索引是唯一的.
如果频繁的使用某种过滤行为查询collections,这时应该考虑使用索引,以便更好的操作.mgo数据库驱动提供了EnsureIndex方法,创建索引,参数是mgo.Index类型.
例子:
// mongodb
package main
import (
"fmt"
"log"
"time"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Task struct {
Description string
Due time.Time
}
type Category struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Name string
Description string
//Tasks []Task
}
func main() {
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
//获取一个集合
c := session.DB("taskdb").C("categories")
c.RemoveAll(nil)
//index
index := mgo.Index{
Key: []string{"name"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
}
//create Index
err = c.EnsureIndex(index)
if err != nil {
panic(err)
}
//插入三个值
err = c.Insert(&Category{bson.NewObjectId(), "R & D", "R & D Tasks"},
&Category{bson.NewObjectId(), "Project", "Project Tasks"},
&Category{bson.NewObjectId(), "Open Source", "Tasks for open-source projects"})
if err != nil {
panic(err)
}
result := Category{}
err = c.Find(bson.M{"name": "Open-Source"}).One(&result)
if err != nil {
log.Fatal(err)
} else {
fmt.Println("Description:", result.Description)
}
创建了一个mgo.Index,调用了方法EnsureIndex方法.Index类型的Key属性,可以用一个切片作为字段名.在这里name字段作为一个index.由于字段是一个切片,可以提供多个字段给实例Index.Unique属性确保只有一个document有一个唯一的index.默认的index是升序的.如果需要降序,可以在字段前加"-"
key : []string{"-name"}
- 管理session
Dial方法和MongoDB数据库建立了链接后会返回一个mgo.Session对象,可以使用这个对象管理所有的CRUD操作,
session管理了MongoDB服务器群的链接池. 一个链接池是数据库链接的缓存,所以当新的请求链接数据库的操作是会重用已用的链接.所以当开发web应用,如果使用单个的全局的Session对错来进行全部的CRUD操作是非常糟糕的.
一个推荐的管理session对象的流程:
1.使用Dial方法获取一个Session对象.
2.在一个独立的HTTP请求的生命周期,使用New,Copy或者Clone方法创建Session,会获取Dial方法创建的session.这样就能正确使用连接池里面的Session对象.
3.在HTTP请求的生命周期里面,使用获取到的Session对象进行CRUD操作.
New方法会创建一个新的Session对象,有同样的参数.Copy方法和New方法工作方式类似,但是copy会保留Session原有的信息.Clone方法和Copy一样,但是会从用原来的Sesssion的socket.
下面的例子是一个HTTP服务器使用一个copy的Session对象.一个struct类型持有Session对象,在请求的Handler里面非常的轻松管理数据库操作.
// mongodb
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
var session *mgo.Session
type Category struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Name string
Description string
//Tasks []Task
}
type DataStore struct {
session *mgo.Session
}
/*
type Task struct {
Description string
Due time.Time
}
*/
//获取数据库的collection
func (d *DataStore) C(name string) *mgo.Collection {
return d.session.DB("taskdb").C(name)
}
//为每一HTTP请求创建新的DataStore对象
func NewDataStore() *DataStore {
ds := &DataStore{
session: session.Copy(),
}
return ds
}
func (d *DataStore) Close() {
d.session.Close()
}
//插入一个记录
func PostCategory(w http.ResponseWriter, r *http.Request) {
var category Category
err := json.NewDecoder(r.Body).Decode(&category)
if err != nil {
panic(err)
}
ds := NewDataStore()
defer ds.Close()
c := ds.C("categories")
err = c.Insert(&category)
if err != nil {
panic(err)
}
w.WriteHeader(http.StatusCreated)
}
func GetCategories(w http.ResponseWriter, r *http.Request) {
var categories []Category
ds := NewDataStore()
defer ds.Close()
c := ds.C("categories")
iter := c.Find(nil).Iter()
result := Category{}
for iter.Next(&result) {
categories = append(categories, result)
}
w.Header().Set("Content-Type", "application/json")
j, err := json.Marshal(categories)
if err != nil {
panic(err)
}
w.WriteHeader(http.StatusOK)
w.Write(j)
}
func main() {
var err error
session, err = mgo.Dial("localhost")
if err != nil {
panic(err)
}
r := mux.NewRouter()
r.HandleFunc("/api/categories", GetCategories).Methods("GET")
r.HandleFunc("/api/categories", PostCategory).Methods("POST")
server := &http.Server{
Addr: ":9090",
Handler: r,
}
log.Println("Listening...")
server.ListenAndServe()
}
定义了一个DataStore的结构体,管理mgo.Session,还有两个方法:Close和C , Close方法主要是Session对象调用Close方法,进行资源的释放.defer函数表示,在HTTP请求生命周期结束的时候会调用.
NewDataStore方法会创建一个新的DataStore对象,通过Copy函数获取Dial函数的Session对象.
对于每个路由的handler,一个新的Session对象都是通过DataStore类型进行使用.简单的说,使用全局的Session对象的方式不好,推荐在HTTP请求的声明周期里使用Copy一个Session对象的方式.这种方法就会存在多个Session对象.