TIL 10 - MongoDB(Golang)

프동프동·2023년 2월 10일
1

TIL

목록 보기
10/46
post-thumbnail

MongoDB

mongo

  • 특징
    • NoSQL
    • Document 형식의 데이터베이스
      • JSON과 유사한 형식
      • 필드-값 형태
    • 여러 Document를 컬렉션(Collection)이라는 그룹으로 묶어서 관리
    • 분산형
    • 데이터 관계 무관
    • 고정되지 않은 테이블 스키마
    • TDD - 테스트 주도 개발에 적합한 DB
      • 빠르게 구축 및 유지보수 가능
  • 사용 방법
    • 관련 패키지
      • mongodb driver - 전반적인 컨트롤을 위한 패키지
        $ go get go.mongodb.org/mongo-driver/mongo
      • mongodb option - 접속시 옵션처리를 위한 패키지 :
        $ go get go.mongodb.org/mongo-driver/mongo/options
      • bson - 쿼리 작업을 위한 패키지 :
        $ go get go.mongodb.org/mongo-driver/bson
    • import
      import (
      	"context"
      	"go.mongodb.org/mongo-driver/bson"
      	"go.mongodb.org/mongo-driver/mongo"
      	"go.mongodb.org/mongo-driver/mongo/options"
      )
    • 몽고 DB 연결
      Mongo_URL := "mongodb://127.0.0.1:27017"
      client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(Mongo_URL))
    • 보안 접속
      //mongodb password가 설정되있는경우
      credential := options.Credential{
      		Username: "codz",
      		Password: "states",
      	}
      
      connect := func(dataSource string) (*mongo.Client, error) {
      		if client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(dataSource).SetAuth(credential)); err != nil {
      			return nil, err
      		} else if err = client.Ping(context.Background(), nil); err != nil { //커넥션 유지를 위한 Ping
      			return nil, err
      		} else {
      			return client, nil
      		}
      }
      
      client, err := connect("mongodb://127.0.0.1:27017")
    • DataBase-Collection 접속
      //db -> collection 단계적 접근
      db := client.Database("go-ready") // database 접속
      col := db.Collection("tPerson")   // collection 접속
      
      //collection으로 바로 접근
      coll := client.Database("go-ready").Collection("tPerson")
      
      //한 DB내 여러 collection 접근방식
      db := client.Database("go-ready")
      colPerson := db.Collection("tPerson")
      colStudent := db.Collection("tStudent")
      colWoman := db.Collection("tWoman")
    • 몽고 DB 연결 종료
      client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
      if err != nil {
      	panic(err)
      }
      defer func() {
      	if err = client.Disconnect(context.TODO()); err != nil { //연결해제
      		panic(err)
      	}
      }()

Create

  • InsertOne : 하나의 데이터 삽입
    func (coll *Collection) InsertOne(ctx context.Context, document interface{},
    	opts ...*options.InsertOneOptions) (*InsertOneResult, error)
    • 사용 방법
      res, err := coll.InsertOne(context.TODO(), bson.D{{"name", "Alice"}})
      	if err != nil {
      		log.Fatal(err)
      	}
  • InsertMany : 여러 데이터 삽입
    func (coll *Collection) InsertMany(ctx context.Context, documents []interface{},
    	opts ...*options.InsertManyOptions) (*InsertManyResult, error)
    • 사용 방법
      	docs := []interface{}{
      		bson.D{{"name", "Alice"}},
      		bson.D{{"name", "Bob"}},
      	}
      	// 삽입 시 정렬 여부
      	opts := options.InsertMany().SetOrdered(false)
      	// bson 형태의 객체 리스트 전달
      	res, err := coll.InsertMany(context.TODO(), docs, opts)
      	if err != nil {
      		log.Fatal(err)
      	}

Read

  • Find : filter에 일치하는 내용을 가져온다.
    func (coll *Collection) Find(ctx context.Context, filter interface{},
    	opts ...*options.FindOptions) (cur *Cursor, err error)
    • 특징
      • fitler 값에 빈 값을 입력하면 모든 값을 가지고 온다.
    • 사용 방법
      cursor, err := coll.Find(context.TODO(), bson.D{{"name", "Bob"}}, opts)
      if err != nil {
      	log.Fatal(err)
      }
      
      // cursor에 반환된 모든 값을 가져와 result 값에 저장 후 출력한다.
      var results []bson.M
      if err = cursor.All(context.TODO(), &results); err != nil {
      	log.Fatal(err)
      }
  • FindOne : 하나의 문서에 대해 단일 결과를 반환한다.
    func (coll *Collection) FindOne(ctx context.Context, filter interface{},
    	opts ...*options.FindOneOptions) *SingleResult
    • 사용 방법
      // 검색한 데이터를 저장할 result 공간 할당
      var result bson.M
      // 정상적으로 데이터를 찾으면 result에 결과가 대입된다.
      err := coll.FindOne(
      	context.TODO(),
      	bson.D{{"_id", id}}
      ).Decode(&result)

Update

  • UpdateOne : 하나의 내용을 업데이트한다.
    func (coll *Collection) UpdateOne(ctx context.Context, filter interface{}, update interface{},
    	opts ...*options.UpdateOptions) (*UpdateResult, error)
    • 사용 방법
      filter := bson.D{{"_id", id}}
      update := bson.D{{"$set", bson.D{{"email", "newemail@example.com"}}}}
      
      result, err := coll.UpdateOne(context.TODO(), filter, update, opts)
      if err != nil {
      	log.Fatal(err)
      }
  • UpdateMany : 문서 전체에서 조건에 맞는 부분을 수정한다
    func (coll *Collection) UpdateMany(ctx context.Context, filter interface{}, update interface{},
    	opts ...*options.UpdateOptions) (*UpdateResult, error)
    • 사용 방법
      today := time.Now().Format("01-01-1970")
      // 생일이 today인 조건 생성
      filter := bson.D{{"birthday", today}}
      // 업데이트 내용 명시
      update := bson.D{{"$inc", bson.D{{"age", 1}}}}
      
      result, err := coll.UpdateMany(context.TODO(), filter, update)
      if err != nil {
      	log.Fatal(err)
      }

Delete

  • DeleteOne : 하나의 내용을 삭제한다.
    func (coll *Collection) DeleteOne(ctx context.Context, filter interface{},
    	opts ...*options.DeleteOptions) (*DeleteResult, error)
    • 사용 방법
      res, err := coll.DeleteOne(context.TODO(), bson.D{{"name", "bob"}})
      if err != nil {
      	log.Fatal(err)
      }
  • DeleteMany : 해당하는 조건의 내용을 모두 삭제한다.
    func (coll *Collection) DeleteMany(ctx context.Context, filter interface{},
    	opts ...*options.DeleteOptions) (*DeleteResult, error)
    • 사용 방법
      res, err := coll.DeleteMany(context.TODO(), bson.D{{"name", "bob"}})
      if err != nil {
      	log.Fatal(err)
      }

bson

몽고DB는 bson(Binary Json)을 이용하고 있다.

  • JSON 문서를 바이너리로 인코딩한 포멧
  • 주로 JSON 형태로 데이터를 저장하거나 네트워크 전송하는 용도로 사용

bson.D

  • 하나의 BSON Document
  • 순서가 중요한 경우 사용

bson.M

  • 순서가 없는 Map 형태
  • 순서를 유지하지 않는다.

bson.A

  • 하나의 BSON array 형태

bson.E

  • D 타입 내부에서 사용하는 하나의 Element

context 패키지

  • 컨텍스트에 대한 이해도를 높이기 위해 내용 정리

작업을 지시할 때 작업 가능 시간, 작업 취소 등의 조건을 지시할 수 있는 작업 명세 역할

고루틴으로 작업 시작 시 일정시간 동안만 작업을 지시하거나 외부에서 작업을 취소할 때 사용

관련 함수

  • Background()
    func Background() Context
    • 빈 컨텍스트를 반환한다.
  • Todo()
    func TODO() Context
  • WithCancel()
    func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
    • Context에 Cancel 함수를 넣어 같이 반환한다.
    • 사용 방법
      • 5초 뒤에 종료 함수 호출하여 무한 루프 종료시키기
        package main
        
        import (
        	"context"
        	"fmt"
        	"sync"
        	"time"
        )
        
        var wg sync.WaitGroup
        
        func main() {
        	wg.Add(1)
        	// 기본 context()를 생성해서 Cancel 기능을 입힌다 -> 취소 가능한 컨텍스트가 된다.
        	ctx, cancel := context.WithCancel(context.Background())
        	// 고루틴 함수 호출 (context)전달
        	go PrintEverySecond(ctx)
        	// 5초 딜레이
        	time.Sleep(5 * time.Second)
        	// context 생성 시 함께 만들어진 cancel() 함수 호출
        	// cancel() 함수 호출 시 Done() 함수로 끝났음을 시그널을 보낸다.
        	cancel()
        	wg.Wait()
        
        }
        
        func PrintEverySecond(ctx context.Context) {
        	// 1초당 채널로 알림
        	tick := time.Tick(time.Second)
        	// 무한 루프
        	for {
        		select {
        		// Done(): 작업이 끝날 때 채널이 나온다.
        		case <-ctx.Done():
        			wg.Done()
        			return
        		case <-tick:
        			fmt.Println("tick")
        		}
        	}
        
        }
  • WithTimeout()
    func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
    • 시간을 넣으면 해당 시간이 지난 뒤에 ctx.Done() 채널 시그널이 발생한다.
    • Cancel 함수도 같이 반환하여 사용할 수 있다.
  • WithValue()
    func WithValue(parent Context, key, val any) Context
    • key에 값을 대입한 컨텍스트가 반환된다.
    • 사용 방법
      • 해당 키값을 컨텍스트로 받아 반환하기

        package main
        
        import (
        	"context"
        	"fmt"
        	"sync"
        	"time"
        )
        
        var wg sync.WaitGroup
        
        func main() {
        	wg.Add(1)
        
        	// WithValue() : 작업을 지정할 때 특정데이터를 지시할 수 있다.
        	//number key에 9를 대입
        	ctx := context.WithValue(context.Background(), "number", 9)
        	go square(ctx)
        
        	wg.Wait()
        
        }
        
        func square(ctx context.Context) {
        	// ctx.Value() 함수를 이용해 지정된 데이터를 가져올 수 있다.
        	// v는 빈 인터페이스 타입
        	if v := ctx.Value("number"); v != nil {
        		// v는 빈인터페이스이므로 타입변환해서 출력할 수 있다.
        		n := v.(int)
        		fmt.Printf("Square:%d", n*n)
        	}
        	wg.Done()
        }

컨텍스트 랩핑

ctx, cancel := context.WithCancel(context.Background())
ctx = context.WithValue(ctx, "number", 9)
ctx = context.WithValue(ctx, "keyword", "FDongFDong") 
  • Cancel 기능 + “number”:9, + “keyword”:FDongFDong

채널로 발행/구독 패턴 구현

발행(Publisher)/구독자(Subscriber) 패턴 구현, 옵저버 패턴과 유사

  • 사용자의 입력을 받을 때 까지 2초마다 발행자가 구독자에게 데이터를 전달해주고
  • main.go
    package main
    
    import (
    	"context"
    	"fmt"
    	"sync"
    	"time"
    )
    
    var wg sync.WaitGroup
    
    func main() {
    	ctx, cancel := context.WithCancel(context.Background())
    
    	wg.Add(4)
    	publisher := NewPublisher(ctx)
    	subscriber1 := NewSubscriber("AAA", ctx)
    	subscriber2 := NewSubscriber("BBB", ctx)
    
    	go publisher.Update()
    
    	subscriber1.Subscribe(publisher)
    	subscriber2.Subscribe(publisher)
    
    	go subscriber1.Update()
    	go subscriber2.Update()
    
    	go func() {
    		tick := time.Tick(time.Second * 2)
    		for {
    			select {
    			case <-tick:
    				publisher.Publish("Hello Message")
    			case <-ctx.Done():
    				wg.Done()
    				return
    			}
    		}
    	}()
    
    	fmt.Scanln()
    	cancel()
    
    	wg.Wait()
    }
  • publisher.go
    package main
    
    import (
    	"context"
    )
    
    type Publisher struct {
    	ctx context.Context
    	// chan (chan<- string) : 채널에 채널을 넣는 타입
    	// chan<- string : 채널을 넣을 수만 있는 채널(일방향 채널)
    	// string <-chan : 데이터를 뺄 수만 있는 채널(일방향 채널)
    	// chan string : 양방향채널, 데이터를 넣고 뺼수 있는 채널(양방향 채널)
    	subscribeCh chan chan<- string
    	publishCh   chan string
    	subscribers []chan<- string
    }
    
    // Publisher의 인스턴스를 만드는 함수
    func NewPublisher(ctx context.Context) *Publisher {
    	return &Publisher{
    		ctx:         ctx,
    		subscribeCh: make(chan chan<- string),
    		publishCh:   make(chan string),
    		subscribers: make([]chan<- string, 0),
    	}
    }
    
    // 채널을 넣는다.
    func (p *Publisher) Subscribe(sub chan<- string) {
    	p.subscribeCh <- sub
    }
    
    // 데이터를 뿌려주기위함
    // publishCh 채널에 string 값을 넣는다.
    func (p *Publisher) Publish(msg string) {
    	p.publishCh <- msg
    }
    
    func (p *Publisher) Update() {
    	for {
    		select {
    		// subscribeCh 채널에 데이터가 오면
    		case sub := <-p.subscribeCh:
    			// subscribers리스트에 추가한다.
    			p.subscribers = append(p.subscribers, sub)
    		// publishCh에서 데이터가 나오면
    		case msg := <-p.publishCh:
    			// subscriber들에게 데이터를 모두 전달해준다.
    			for _, subscriber := range p.subscribers {
    				subscriber <- msg
    			}
    		case <-p.ctx.Done():
    			wg.Done()
    			return
    		}
    
    	}
    }
  • subscriber.go
    package main
    
    import (
    	"context"
    	"fmt"
    )
    
    type Subscriber struct {
    	ctx   context.Context
    	name  string
    	msgCh chan string
    }
    
    func NewSubscriber(name string, ctx context.Context) *Subscriber {
    	return &Subscriber{
    		ctx:   ctx,
    		name:  name,
    		msgCh: make(chan string),
    	}
    }
    
    func (s *Subscriber) Subscribe(pub *Publisher) {
    	pub.Subscribe(s.msgCh)
    }
    
    func (s *Subscriber) Update() {
    	for {
    		select {
    		case msg := <-s.msgCh:
    			fmt.Printf("%s got Message:%s\n", s.name, msg)
    		case <-s.ctx.Done():
    			wg.Done()
    			return
    		}
    	}
    }
  • 실행 결과
    > ./example01
    AAA got Message:Hello Message
    BBB got Message:Hello Message
    BBB got Message:Hello Message
    AAA got Message:Hello Message
    BBB got Message:Hello Message
    AAA got Message:Hello Message
    BBB got Message:Hello Message
    AAA got Message:Hello Message
    BBB got Message:Hello Message
    AAA got Message:Hello Message
    eBBB got Message:Hello Message
    AAA got Message:Hello Message
    nd
profile
좋은 개발자가 되고싶은

0개의 댓글