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