LoginSignup
0
0

More than 3 years have passed since last update.

GolangのMongoDriverでreflectを使ってデータを取得する方法

Last updated at Posted at 2019-06-14
package main

import (
    "context"
    "fmt"
    "log"
    "reflect"
    "time"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)

type Foo struct {
    ID   *primitive.ObjectID `json:"_id" bson:"_id,omitempty"`
    Name string              `json:"name" bson:"name"`
}

func main() {
    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
    ctx, _ = context.WithTimeout(context.Background(), 2*time.Second)
    if err = client.Ping(ctx, readpref.Primary()); err != nil {
        log.Fatalf("%+v\n", err)
    }
    collection := client.Database("testing").Collection("foo")

    // ここまでサンプルどおり ---------------------------------

    fooType := reflect.TypeOf(Foo{})
    fooSlice := createSlice(reflect.SliceOf(fooType))

    // この行がメイン
    d := find(collection, fooSlice, fooType).([]Foo)
    fmt.Println(d)
}

// 黒魔術感ある
// 単純にMakeSliceだけだと到達可能なアドレスを返せないので、makedSlice.CanSet() == false
// なので、新しくreflect.valueをNewして、作ったmakedSliceをセットする形を取っている。
func createSlice(sliceType reflect.Type) reflect.Value {
    makedSlice := reflect.MakeSlice(sliceType, 0, 16)
    reflectionValue := reflect.New(makedSlice.Type())
    reflectionValue.Elem().Set(makedSlice)
    slicePtr := reflect.ValueOf(reflectionValue.Interface())
    return slicePtr.Elem()
}

func find(col *mongo.Collection, reflectSlice reflect.Value, reflectType reflect.Type) interface{} {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    cur, err := col.Find(ctx, bson.M{})
    if err != nil {
        log.Fatalf("%+v\n", err)
    }
    defer cur.Close(ctx)
    for cur.Next(ctx) {
        v := reflect.New(reflectType)
        if err := cur.Decode(v.Interface()); err != nil {
            log.Fatalf("%+v\n", err)
        }
        reflectSlice.Set(reflect.Append(reflectSlice, v.Elem()))
    }
    if err := cur.Err(); err != nil {
        log.Fatalf("%+v\n", err)
    }
    return reflectSlice.Interface()
}

Mongoとかデータの準備とか必要ですが、Golang1.12以上なら、コピペで動くと思います。

それにしても、Golangのリフレクションは、わかりづらい。使いづらい。

Genericsの導入計画があるらしいので、それが実現すればこんなコード書かずに済むんだろうなぁとか、思ったりしています。

パフォーマンスですが、1万件のデータのfindで試したところ、

  • 型指定:55ms
  • リフレクション:58ms

3msぐらいしか違いが無かったので使っても良いのではないでしょうか?

あと、createSlice関数の中身ですが、なぜあれをやらないと動かないのか?が、いまいち理解できていません。。。

※ 2019-06-17追記 黒魔術コードについての理解を深める記事を書きました。
https://qiita.com/daijinload/items/f822e4a208d17e432599

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0