Golang에서 MongoDB를 제어하거나 연결할 때 사용할 수 있는 드라이버에는 여러 종류가 있습니다.
공식적이며 가장 권장되는 표준 드라이버인 mongo-drvier(go.mongodb.org/mongo-driver), 과거에 표준 처럼 사용 되었던 mgo(github.com/globalsign/mgo), 공식 드라이버를 기반으로 한 ORM 스타일 래퍼 mgm(github.com/kamva/mgm/v3) 등이 있습니다.
mgo의 경우에는 문법이 간단하고 사용하기 편했지만 유지보수가 종료됨에 따라 최신 MongoDB 버전과 호환성이 맞지 않아 이번 내용에서 제외하고, 이 가이드는 Go에서 공식 MongoDB 드라이버(go.mongodb.org/mongo-driver)를 사용하여 MongoDB에 접근하고 쿼리하는 방법을 설명합니다.

go mod init your-project
go get go.mongodb.org/mongo-driver/mongo
go get go.mongodb.org/mongo-driver/bson
import (
"context"
"log"
"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"
)
package inits
import (
"context"
"fmt"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var MongoClient *mongo.Client
var MongoDb *mongo.Database
func MongoInit(uri string, dbName string) error {
// 컨텍스트 설정 (타임아웃 10초)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 클라이언트 옵션 설정
clientOptions := options.Client().ApplyURI(uri)
// MongoDB에 연결
client, err := mongo.Connect(ctx, clientOptions)
if err != nil {
return fmt.Errorf("MongoDB 연결 실패: %v", err)
}
// 연결 테스트
err = client.Ping(ctx, nil)
if err != nil {
return fmt.Errorf("MongoDB Ping 실패: %v", err)
}
MongoClient = client
MongoDb = client.Database(dbName)
fmt.Println("MongoDB 연결 성공")
return nil
}
func MongoClose() error {
if MongoClient != nil {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
return MongoClient.Disconnect(ctx)
}
return nil
}
// 로컬 MongoDB
uri := "mongodb://localhost:27017"
// MongoDB Atlas (클라우드)
uri := "mongodb+srv://username:password@cluster.mongodb.net/"
// 인증이 필요한 로컬 MongoDB
uri := "mongodb://username:password@localhost:27017/database"
package models
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type BaseModel struct {
// ObjectId
ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
// soft/hard delete 처리
Able bool `json:"able" bson:"able"`
// 데이터 생성시간
CreatedTime time.Time `json:"createdTime" bson:"createdTime"`
// 데이터 수정시간
UpdatedTime time.Time `json:"updatedTime" bson:"updatedTime"`
}
// MongoModel 인터페이스
type MongoModel interface {
CollectionName() string
}
// 예시: User 모델
type User struct {
BaseModel `bson:",inline"`
Name string `json:"name" bson:"name"`
Email string `json:"email" bson:"email"`
Age int `json:"age" bson:"age"`
}
func (u User) CollectionName() string {
return "users"
}
func Create(model MongoModel, doc interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
collection := MongoDb.Collection(model.CollectionName())
// BaseModel 필드 자동 설정
if baseDoc, ok := doc.(*BaseModel); ok {
now := time.Now()
baseDoc.CreatedTime = now
baseDoc.UpdatedTime = now
baseDoc.Able = true
}
result, err := collection.InsertOne(ctx, doc)
if err != nil {
return fmt.Errorf("문서 생성 실패: %v", err)
}
fmt.Printf("문서 생성 성공. ID: %v\n", result.InsertedID)
return nil
}
// 사용 예시
func CreateUser() {
user := User{
Name: "홍길동",
Email: "hong@example.com",
Age: 30,
}
err := Create(&user, &user)
if err != nil {
log.Printf("사용자 생성 실패: %v", err)
}
}
func FindById(model MongoModel, id string, result interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
objectId, err := primitive.ObjectIDFromHex(id)
if err != nil {
return fmt.Errorf("잘못된 ObjectID: %v", err)
}
collection := MongoDb.Collection(model.CollectionName())
err = collection.FindOne(ctx, bson.M{"_id": objectId}).Decode(result)
if err != nil {
if err == mongo.ErrNoDocuments {
return fmt.Errorf("문서를 찾을 수 없음")
}
return fmt.Errorf("문서 조회 실패: %v", err)
}
return nil
}
// 조건으로 단일 문서 조회
func FindOne(model MongoModel, filter bson.M, result interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
collection := MongoDb.Collection(model.CollectionName())
err := collection.FindOne(ctx, filter).Decode(result)
if err != nil {
return fmt.Errorf("문서 조회 실패: %v", err)
}
return nil
}
func FindMany(model MongoModel, filter bson.M, results interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
collection := MongoDb.Collection(model.CollectionName())
cursor, err := collection.Find(ctx, filter)
if err != nil {
return fmt.Errorf("문서 조회 실패: %v", err)
}
defer cursor.Close(ctx)
err = cursor.All(ctx, results)
if err != nil {
return fmt.Errorf("문서 디코딩 실패: %v", err)
}
return nil
}
// 페이지네이션과 정렬
func FindWithOptions(model MongoModel, filter bson.M, opts *options.FindOptions, results interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
collection := MongoDb.Collection(model.CollectionName())
cursor, err := collection.Find(ctx, filter, opts)
if err != nil {
return fmt.Errorf("문서 조회 실패: %v", err)
}
defer cursor.Close(ctx)
return cursor.All(ctx, results)
}
// 사용 예시
func GetUsersPaginated(page, limit int64) ([]User, error) {
var users []User
// 정렬 및 페이지네이션 옵션
opts := options.Find().
SetSort(bson.D{{"createdTime", -1}}). // 생성시간 내림차순
SetLimit(limit).
SetSkip((page - 1) * limit)
filter := bson.M{"able": true} // 활성화된 사용자만
err := FindWithOptions(&User{}, filter, opts, &users)
return users, err
}
func UpdateById(model MongoModel, id primitive.ObjectID, update bson.M) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 업데이트 시간 자동 설정
update["updatedTime"] = time.Now()
collection := MongoDb.Collection(model.CollectionName())
result, err := collection.UpdateOne(
ctx,
bson.M{"_id": id},
bson.M{"$set": update},
)
if err != nil {
return fmt.Errorf("문서 업데이트 실패: %v", err)
}
if result.MatchedCount == 0 {
return fmt.Errorf("업데이트할 문서를 찾을 수 없음")
}
fmt.Printf("문서 업데이트 성공. 수정된 문서 수: %d\n", result.ModifiedCount)
return nil
}
// 조건으로 여러 문서 업데이트
func UpdateMany(model MongoModel, filter bson.M, update bson.M) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
update["updatedTime"] = time.Now()
collection := MongoDb.Collection(model.CollectionName())
result, err := collection.UpdateMany(
ctx,
filter,
bson.M{"$set": update},
)
if err != nil {
return fmt.Errorf("문서 업데이트 실패: %v", err)
}
fmt.Printf("업데이트 완료. 매칭: %d, 수정: %d\n", result.MatchedCount, result.ModifiedCount)
return nil
}
// 소프트 삭제 (able 필드를 false로 변경)
func SoftDeleteById(model MongoModel, id primitive.ObjectID) error {
return UpdateById(model, id, bson.M{"able": false})
}
// 하드 삭제 (실제 문서 삭제)
func DeleteById(model MongoModel, id primitive.ObjectID) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
collection := MongoDb.Collection(model.CollectionName())
result, err := collection.DeleteOne(ctx, bson.M{"_id": id})
if err != nil {
return fmt.Errorf("문서 삭제 실패: %v", err)
}
if result.DeletedCount == 0 {
return fmt.Errorf("삭제할 문서를 찾을 수 없음")
}
return nil
}
// 조건으로 여러 문서 삭제
func DeleteMany(model MongoModel, filter bson.M) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
collection := MongoDb.Collection(model.CollectionName())
result, err := collection.DeleteMany(ctx, filter)
if err != nil {
return fmt.Errorf("문서 삭제 실패: %v", err)
}
fmt.Printf("삭제 완료. 삭제된 문서 수: %d\n", result.DeletedCount)
return nil
}
func AdvancedQuery() {
// 나이가 18~65 사이이고 이름이 "김"으로 시작하는 사용자
filter := bson.M{
"age": bson.M{
"$gte": 18,
"$lte": 65,
},
"name": bson.M{
"$regex": "^김",
},
"able": true,
}
var users []User
err := FindMany(&User{}, filter, &users)
if err != nil {
log.Printf("쿼리 실행 실패: %v", err)
}
}
// OR 조건
func OrQuery() {
filter := bson.M{
"$or": []bson.M{
{"age": bson.M{"$lt": 20}},
{"age": bson.M{"$gt": 60}},
},
}
var users []User
FindMany(&User{}, filter, &users)
}
// 배열 필드 쿼리
func ArrayQuery() {
// tags 배열에 "golang"이 포함된 문서
filter := bson.M{
"tags": bson.M{"$in": []string{"golang"}},
}
// tags 배열의 모든 요소가 조건을 만족하는 문서
filter2 := bson.M{
"tags": bson.M{"$all": []string{"golang", "mongodb"}},
}
}
func TextSearch(searchTerm string) ([]User, error) {
var users []User
// 텍스트 인덱스가 설정되어 있어야 함
filter := bson.M{
"$text": bson.M{
"$search": searchTerm,
},
}
// 관련도 점수로 정렬
opts := options.Find().SetSort(bson.D{{"score", bson.D{{"$meta", "textScore"}}}})
err := FindWithOptions(&User{}, filter, opts, &users)
return users, err
}
func AggregateUsers() error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
collection := MongoDb.Collection("users")
// 집계 파이프라인
pipeline := []bson.M{
{
"$match": bson.M{
"able": true,
},
},
{
"$group": bson.M{
"_id": "$age",
"count": bson.M{"$sum": 1},
"avgAge": bson.M{"$avg": "$age"},
},
},
{
"$sort": bson.M{
"count": -1,
},
},
}
cursor, err := collection.Aggregate(ctx, pipeline)
if err != nil {
return fmt.Errorf("집계 실행 실패: %v", err)
}
defer cursor.Close(ctx)
var results []bson.M
err = cursor.All(ctx, &results)
if err != nil {
return fmt.Errorf("집계 결과 디코딩 실패: %v", err)
}
for _, result := range results {
fmt.Printf("나이: %v, 개수: %v, 평균: %v\n",
result["_id"], result["count"], result["avgAge"])
}
return nil
}
type AgeGroup struct {
ID string `bson:"_id"`
Count int `bson:"count"`
AvgAge float64 `bson:"avgAge"`
MinAge int `bson:"minAge"`
MaxAge int `bson:"maxAge"`
}
func GroupUsersByAgeRange() ([]AgeGroup, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
collection := MongoDb.Collection("users")
pipeline := []bson.M{
{
"$match": bson.M{"able": true},
},
{
"$addFields": bson.M{
"ageGroup": bson.M{
"$switch": bson.M{
"branches": []bson.M{
{"case": bson.M{"$lt": []interface{}{"$age", 20}}, "then": "10대"},
{"case": bson.M{"$lt": []interface{}{"$age", 30}}, "then": "20대"},
{"case": bson.M{"$lt": []interface{}{"$age", 40}}, "then": "30대"},
{"case": bson.M{"$lt": []interface{}{"$age", 50}}, "then": "40대"},
},
"default": "50대 이상",
},
},
},
},
{
"$group": bson.M{
"_id": "$ageGroup",
"count": bson.M{"$sum": 1},
"avgAge": bson.M{"$avg": "$age"},
"minAge": bson.M{"$min": "$age"},
"maxAge": bson.M{"$max": "$age"},
},
},
{
"$sort": bson.M{"avgAge": 1},
},
}
cursor, err := collection.Aggregate(ctx, pipeline)
if err != nil {
return nil, fmt.Errorf("집계 실행 실패: %v", err)
}
defer cursor.Close(ctx)
var results []AgeGroup
err = cursor.All(ctx, &results)
return results, err
}
func CreateIndexes() error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
collection := MongoDb.Collection("users")
// 단일 필드 인덱스
indexModel1 := mongo.IndexModel{
Keys: bson.D{{"email", 1}}, // 1: 오름차순, -1: 내림차순
Options: options.Index().SetUnique(true), // 유니크 인덱스
}
// 복합 인덱스
indexModel2 := mongo.IndexModel{
Keys: bson.D{
{"name", 1},
{"age", -1},
},
}
// 텍스트 인덱스
indexModel3 := mongo.IndexModel{
Keys: bson.D{
{"name", "text"},
{"email", "text"},
},
}
// TTL 인덱스 (시간 기반 자동 삭제)
indexModel4 := mongo.IndexModel{
Keys: bson.D{{"createdTime", 1}},
Options: options.Index().SetExpireAfterSeconds(30 * 24 * 3600), // 30일 후 삭제
}
indexes := []mongo.IndexModel{indexModel1, indexModel2, indexModel3, indexModel4}
result, err := collection.Indexes().CreateMany(ctx, indexes)
if err != nil {
return fmt.Errorf("인덱스 생성 실패: %v", err)
}
fmt.Printf("생성된 인덱스: %v\n", result)
return nil
}
// 인덱스 조회
func ListIndexes() error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
collection := MongoDb.Collection("users")
cursor, err := collection.Indexes().List(ctx)
if err != nil {
return fmt.Errorf("인덱스 조회 실패: %v", err)
}
defer cursor.Close(ctx)
var indexes []bson.M
err = cursor.All(ctx, &indexes)
if err != nil {
return fmt.Errorf("인덱스 디코딩 실패: %v", err)
}
for _, index := range indexes {
fmt.Printf("인덱스: %+v\n", index)
}
return nil
}
TTL(수명) 인덱스의 만료 삭제는 MongoDB 서버(mongod)의 백그라운드 TTL 모니터가 수행하기 때문에, Go 프로세스가 꺼져 있어도 mongod가 실행 중이면 만료 문서는 자동으로 삭제됩니다.
[참고문서]
https://www.mongodb.com/ko-kr/docs/manual/core/index-ttl/#std-label-index-feature-ttl